Skip to content

Commit 54d7f92

Browse files
committed
Add estimate hydropower example
1 parent 614f333 commit 54d7f92

File tree

15 files changed

+1492
-198
lines changed

15 files changed

+1492
-198
lines changed

examples/estimate_hydropower.ipynb

Lines changed: 325 additions & 0 deletions
Large diffs are not rendered by default.

images/complex_river.png

32.3 KB
Loading
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
source,sink,lag_time,flow_fraction
2+
atay,kamchay,0,1
3+
kamchay,kirirom1,0,0.25
4+
kamchay,kirirom2,0,0.75

model_library/complex_river/inflow.csv

Lines changed: 366 additions & 0 deletions
Large diffs are not rendered by default.

model_library/complex_river/minimum_flow.csv

Lines changed: 366 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
name,runoff_coeff,basin_area,max_storage,min_level,max_level,max_head,min_day,max_day,max_release,max_generation,turbine_factor
2+
kirirom1,0.48,99000000,30000000,500,534,373.5,150,310,1728000,12,0.9
3+
kirirom2,0.48,105000000,30000000,500,540,271,150,310,3456000,18,0.9
4+
kamchay,0.62,710000000,432000000,500,610,122,180,298,14126400,194,0.9
5+
atay,0.75,1157000000,443800000,510,545,216,158,311,10800000,240,0.9

src/pownet/data_model/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
"""This is the core module."""
2+
3+
from .reservoir import ReservoirParams
4+
5+
__all__ = [
6+
"ReservoirParams",
7+
]

src/pownet/data_model/reservoir.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import dataclasses
2+
import pandas as pd
3+
4+
5+
@dataclasses.dataclass()
6+
class ReservoirParams:
7+
"""
8+
Data class to hold static parameters and initial timeseries data for a reservoir.
9+
10+
Attributes:
11+
name (str): The unique name of the reservoir.
12+
min_day (int): The day of the year when the target level is typically at its minimum.
13+
max_day (int): The day of the year when the target level is typically at its maximum.
14+
min_level (float): The minimum operational water level (meters).
15+
max_level (float): The maximum operational water level (meters).
16+
max_head (float): The maximum hydraulic head difference available for generation (meters).
17+
max_storage (float): The maximum storage capacity of the reservoir (m³).
18+
max_release (float): The maximum allowable daily release rate (m³/day).
19+
max_generation (float): The maximum power generation capacity (MW).
20+
turbine_factor (float): The efficiency factor of the turbine(s).
21+
inflow_ts (pd.Series): Timeseries of daily natural inflow into the reservoir (m³/day),
22+
indexed from 1 to sim_horizon.
23+
minflow_ts (pd.Series): Minimum environmental flow (m³/day), indexed from 1 to sim_horizon.
24+
upstream_units (list[str]): List of upstream reservoir names that feed into this reservoir.
25+
downstream_flow_fracs (dict[str, float]): Dictionary mapping downstream reservoir names to their
26+
respective flow fractions (0-1).
27+
"""
28+
29+
name: str
30+
min_day: int
31+
max_day: int
32+
min_level: float
33+
max_level: float
34+
max_head: float
35+
max_storage: float
36+
max_release: float
37+
max_generation: float
38+
turbine_factor: float
39+
inflow_ts: pd.Series
40+
minflow_ts: pd.Series
41+
upstream_units: list[str]
42+
downstream_flow_fracs: dict[str, float]
43+
44+
def __post_init__(self):
45+
"""Perform basic validation after initialization."""
46+
# Flow fractions of downstream units should sum to 1
47+
if self.downstream_flow_fracs:
48+
if not 0.999 <= sum(self.downstream_flow_fracs.values()) <= 1.001:
49+
raise ValueError(
50+
f"Downstream units for {self.name} do not sum to 1: "
51+
f"{self.downstream_flow_fracs}"
52+
)
53+
54+
# Check that inflow and minflow timeseries are indexed correctly
55+
if not self.inflow_ts.index.equals(self.minflow_ts.index):
56+
raise ValueError(
57+
f"Inflows and minflows for {self.name} are not indexed the same: "
58+
f"{self.inflow_ts.index} vs {self.minflow_ts.index}"
59+
)
60+
61+
# Indexing starts at 1
62+
if self.inflow_ts.index[0] != 1:
63+
raise ValueError(
64+
f"Inflows for {self.name} do not start at 1: {self.inflow_ts.index[0]}"
65+
)
66+
if self.minflow_ts.index[0] != 1:
67+
raise ValueError(
68+
f"Minflows for {self.name} do not start at 1: {self.minflow_ts.index[0]}"
69+
)
70+
71+
# Inflow must be greater than minflow for all days
72+
if not all(self.inflow_ts >= self.minflow_ts):
73+
raise ValueError(
74+
f"Inflows for {self.name} are less than minflows on some days: "
75+
f"{(self.inflow_ts < self.minflow_ts).sum()} days"
76+
)

src/pownet/reservoir/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
""" This is the reservoir module.
2-
"""
1+
"""This is the reservoir module."""
32

43
from .reservoir import Reservoir
54
from .basin import Basin
@@ -8,3 +7,4 @@
87
solve_release_from_target_storage,
98
solve_release_from_dispatch,
109
)
10+
from .manager import ReservoirManager

src/pownet/reservoir/basin.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,21 @@
1-
""" basin.py: Basin class
2-
"""
1+
"""basin.py: Basin class"""
32

43
import os
54

65
import pandas as pd
76

8-
from pownet.folder_utils import get_reservoir_file
97
from .reservoir import Reservoir
108

119

1210
class Basin:
1311
def __init__(
1412
self,
15-
model_name: str,
1613
basin_name: str,
1714
sim_horizon: int = 365,
1815
) -> None:
1916
"""
2017
A class to manage reservoirs in a basin.
2118
"""
22-
23-
self.model_name: str = model_name
2419
self.basin_name: str = basin_name
2520
self.sim_horizon: int = sim_horizon
2621

0 commit comments

Comments
 (0)