Exercise 3: Hydropower, Scheduling and Dispatch

Exercise 3: Hydropower, Scheduling and Dispatch#

Prepare Google Colab Environment#

#@title Connect to Google Drive {display-mode:"form"}
CONNECT_TO_DRIVE = False #@param {type:"boolean"}

import os

if CONNECT_TO_DRIVE:
    from google.colab import drive
    # Mount Google Drive
    drive.mount('/content/drive')

    # Define the desired working directory path
    working_dir = '/content/drive/MyDrive/hello-pypsa'

    # Create the directory if it doesn't exist
    if not os.path.exists(working_dir):
        os.makedirs(working_dir)
        print(f"Directory '{working_dir}' created.")
    else:
        print(f"Directory '{working_dir}' already exists.")

    # Change the current working directory
    os.chdir(working_dir)

    print(f"Current working directory: {os.getcwd()}")
else:
    print("Not connecting to Google Drive.")
import os

#@title Install Packages {display-mode:"form"}
INSTALL_PACKAGES = False #@param {type:"boolean"}

# Check if packages have already been installed in this session to prevent re-installation
if INSTALL_PACKAGES and not os.environ.get('PYPSA_PACKAGES_INSTALLED'):
  !pip install pypsa pypsa[excel] folium mapclassify cartopy
  !pip install git+https://github.com/PriyeshGosai/pypsa_network_viewer.git
  os.environ['PYPSA_PACKAGES_INSTALLED'] = 'true'
elif not INSTALL_PACKAGES:
  print("Skipping package installation.")
else:
  print("PyPSA packages are already installed for this session.")
#@title Download the file for this notebook {display-mode:"form"}
DOWNLOAD_FILE = False #@param {type:"boolean"}

if DOWNLOAD_FILE:
    !gdown "https://drive.google.com/uc?id=1My8j2qRcjjhVhC5bL657oYTk7-OKDxkE"


else:
    print("Skipping file download.")
# !pip install git+https://github.com/pypsa/pypsa

Exercise 3#

print("PyPSA Model")
PyPSA Model
import pypsa
import pandas as pd
pypsa.options.api.new_components_api = True

n = pypsa.Network('hydro_schedule_hlp.xlsx')

# Scale generator dispatch schedule
n.loads.dynamic.p_set *= 1.0

n.generators.dynamic.p_set['RvrIn-Houaylamphan Gnai'] *= 1.2
n.generators.dynamic.p_set['RvrIn-Xepien Xenamnoi'] *= 1

# Step 1: Create the linopy model
n.optimize.create_model(include_objective_constant=True)

# Step 2: Add custom terminal storage constraint
m = n.model
last = n.snapshots[-1]

e = m.variables['Store-e'].sel(snapshot=last)
e_initial = n.stores.static['e_initial']

m.add_constraints(
    e >= e_initial.values,
    name='Store-terminal_energy'
)

# Step 3: Solve
n.optimize.solve_model(include_objective_constant=True)
import plotly.graph_objects as go
import pandas as pd

# --- fig1: Power Plant Dispatch (Lines) ---
proc_pp  = n.processes.static[n.processes.static['type'] == 'power_plant'].index
gen_pp   = n.generators.static[n.generators.static['type'] == 'power_plant'].index
stor_pp  = n.storage_units.static[n.storage_units.static['type'] == 'power_plant'].index

dispatch_df = n.processes.dynamic.p2[proc_pp] * -1
dispatch_df = pd.concat([dispatch_df, n.generators.dynamic.p[gen_pp]], axis=1)

stor_p = n.storage_units.dynamic.p[stor_pp]
dispatch_df = pd.concat([
    dispatch_df,
    stor_p.clip(lower=0).rename(columns=lambda c: c + '_discharge'),
    stor_p.clip(upper=0).rename(columns=lambda c: c + '_charge')
], axis=1)

fig = go.Figure()
for col in dispatch_df.columns:
    fig.add_trace(go.Scatter(x=dispatch_df.index, y=dispatch_df[col], mode='lines', name=col))
fig.update_layout(title='Power Plant Dispatch', xaxis_title='Time', yaxis_title='MW', hovermode='x unified')
# fig.show()

# --- fig2: Power Plant Dispatch (Stacked) ---
fig2 = go.Figure()
for col in dispatch_df.columns:
    fig2.add_trace(go.Scatter(
        x=dispatch_df.index,
        y=dispatch_df[col],
        mode='lines',
        name=col,
        stackgroup='positive' if dispatch_df[col].mean() >= 0 else 'negative',
        fill='tonexty'
    ))
fig2.update_layout(title='Power Plant Dispatch (Stacked)', xaxis_title='Time', yaxis_title='MW', hovermode='x unified')
# fig2.show()

# --- fig3: River In/Outflows ---
rvrIn_cols  = [c for c in n.generators.dynamic.p.columns if c.startswith('RvrIn-')]
rvrOut_cols = [c for c in n.generators.dynamic.p.columns if c.startswith('RvrOut-')]
river_df = pd.concat([
    n.generators.dynamic.p[rvrIn_cols].rename(columns=lambda c: c.replace('RvrIn-', '') + ' (in)'),
    n.generators.dynamic.p[rvrOut_cols].rename(columns=lambda c: c.replace('RvrOut-', '') + ' (out)')
], axis=1)

fig3 = go.Figure()
for col in river_df.columns:
    fig3.add_trace(go.Scatter(x=river_df.index, y=river_df[col], mode='lines', name=col))
fig3.update_layout(title='River In/Outflows', xaxis_title='Time', yaxis_title='Mm³/h', hovermode='x unified')
# fig3.show()

# --- fig4: Dam Levels ---
dam_cols = [c for c in n.stores.dynamic.e.columns if c.startswith('Dam-')]
dam_df   = n.stores.dynamic.e[dam_cols].rename(columns=lambda c: c.replace('Dam-', ''))

fig4 = go.Figure()
for col in dam_df.columns:
    fig4.add_trace(go.Scatter(x=dam_df.index, y=dam_df[col], mode='lines', name=col))
fig4.update_layout(title='Dam Levels', xaxis_title='Time', yaxis_title='Volume', hovermode='x unified')
# fig4.show()

# --- fig5: Spill Flows ---
spl_cols = [c for c in n.links.dynamic.p0.columns if c.startswith('Spl-')]
spl_df   = n.links.dynamic.p0[spl_cols].rename(columns=lambda c: c.replace('Spl-', ''))

fig5 = go.Figure()
for col in spl_df.columns:
    fig5.add_trace(go.Scatter(x=spl_df.index, y=spl_df[col], mode='lines', name=col))
fig5.update_layout(title='Spill Flows', xaxis_title='Time', yaxis_title='Mm³/h', hovermode='x unified')
# fig5.show()

# --- fig6: Thailand Export vs Southern Load ---
fig6 = go.Figure()
fig6.add_trace(go.Scatter(x=n.generators.dynamic.p.index, y=n.generators.dynamic.p['Thailand Export'], mode='lines', name='Thailand Export'))
fig6.add_trace(go.Scatter(x=n.loads.dynamic.p.index,      y=n.loads.dynamic.p['Southern Load'],        mode='lines', name='Southern Load'))
fig6.update_layout(title='Thailand Export vs Southern Load', xaxis_title='Time', yaxis_title='MW', hovermode='x unified')
# fig6.show()
from pypsa_network_viewer import html_network

custom_figs = [fig, fig2, fig3, fig4, fig5 , fig6]

output = html_network(
    n,
    file_path='output',
    file_name='exercise_3_results.html',
    title='Exercise 3 Network Analysis',
    currency='$',
    custom_plots=custom_figs
)

print("\n" + "="*60)
print(f"Complete viewer: {output}")
print("="*60)
print("\nIncludes:")
print("  ✓ Network Summary")
print("  ✓ Global Constraints")
print(f"  ✓ {len(custom_figs)} Custom Plots")
print("  ✓ All component data")
print("\nOpen in browser to explore!")