Skip to content

Commit 6eb80c3

Browse files
committed
Revise hydropower formulation
Removed binary variables from the default formulation to speed up the computation.
1 parent 0eb24fb commit 6eb80c3

4 files changed

Lines changed: 273 additions & 116 deletions

File tree

src/pownet/builder/hydro.py

Lines changed: 78 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,28 @@
1-
"""hydro.py: Hydro unit builder."""
1+
"""hydro.py: Hydro unit builder. This includes hourly, daily, and weekly constraints for hydro units."""
22

33
from .basebuilder import ComponentBuilder
44

55
import gurobipy as gp
66

77
from ..input import SystemInput
8+
from ..optim_model.variable_func import (
9+
add_var_with_variable_ub,
10+
update_var_with_variable_ub,
11+
)
812
from ..optim_model.objfunc import get_marginal_cost_coeff
913
from ..optim_model.constraints import nondispatch_constr
1014

1115

1216
class HydroUnitBuilder(ComponentBuilder):
13-
"""Builder class for hydro units.
17+
"""Builder class for hydro units. If a hydro unit's hourly availability is provided,
18+
then the hydro unit's dispatch variables are limited by them. If a hydro unit's
19+
hourly availability is not provided, as in the case with daily/weekly hydro units,
20+
then the hydro unit's dispatch variables are limited by the contracted capacity.
1421
1522
Variables
1623
===========================
1724
- `phydro`: Hydropower output. Unit: MW.
18-
- `uhydro`: Hydropower unit status. Unit: binary (0 or 1).
25+
- `uhydro`: Hydropower unit status. Unit: binary (0 or 1). (optional)
1926
2027
Fixed objective terms
2128
===========================
@@ -27,8 +34,8 @@ class HydroUnitBuilder(ComponentBuilder):
2734
2835
Constraints
2936
===========================
37+
- Hourly/Daily/Weekly hydropower limits
3038
- Linking upper bounds of dispatch variables to unit status variables
31-
- Daily hydropower limits
3239
3340
"""
3441

@@ -37,6 +44,10 @@ def __init__(self, model: gp.Model, inputs: SystemInput):
3744

3845
# Variables
3946
self.phydro = gp.tupledict()
47+
self.hourly_phydro = gp.tupledict()
48+
self.daily_phydro = gp.tupledict()
49+
self.weekly_phydro = gp.tupledict()
50+
4051
self.uhydro = gp.tupledict()
4152

4253
# Fixed objective terms
@@ -46,39 +57,69 @@ def __init__(self, model: gp.Model, inputs: SystemInput):
4657
self.total_energy_cost_expr = gp.LinExpr()
4758

4859
# Constraints
49-
self.c_link_hydro_pu = gp.tupledict()
50-
self.c_link_weekly_hydro_pu = gp.tupledict()
51-
60+
self.c_hourly_hydro_ub = gp.tupledict()
5261
self.c_hydro_limit_daily = gp.tupledict()
5362
self.c_hydro_limit_weekly = gp.tupledict()
5463

64+
self.c_link_hydro_pu = gp.tupledict()
65+
5566
def add_variables(self, step_k: int) -> None:
5667
"""Add variables to the model for hydro units.
5768
5869
Args:
5970
step_k (int): Current time step.
6071
"""
61-
self.phydro = self.model.addVars(
62-
self.inputs.hydro_units,
72+
# --- Hourly hydropower
73+
self.hourly_phydro = add_var_with_variable_ub(
74+
model=self.model,
75+
varname="phydro",
76+
timesteps=self.timesteps,
77+
step_k=step_k,
78+
units=self.inputs.hydro_unit_node.keys(),
79+
capacity_df=self.inputs.hydro_capacity,
80+
)
81+
82+
# --- Daily/weekly hydropower are limited by contracted capacity
83+
self.daily_phydro = self.model.addVars(
84+
self.inputs.daily_hydro_unit_node.keys(),
6385
self.timesteps,
6486
lb=0,
6587
ub={
6688
(unit, t): self.inputs.hydro_contracted_capacity[unit]
89+
for unit in self.inputs.daily_hydro_unit_node.keys()
6790
for t in self.timesteps
68-
for unit in self.inputs.hydro_units
6991
},
7092
vtype=gp.GRB.CONTINUOUS,
7193
name="phydro",
7294
)
7395

74-
self.uhydro = self.model.addVars(
75-
self.inputs.hydro_units,
96+
self.weekly_phydro = self.model.addVars(
97+
self.inputs.weekly_hydro_unit_node.keys(),
7698
self.timesteps,
7799
lb=0,
78-
vtype=gp.GRB.BINARY,
79-
name="uhydro",
100+
ub={
101+
(unit, t): self.inputs.hydro_contracted_capacity[unit]
102+
for unit in self.inputs.weekly_hydro_unit_node.keys()
103+
for t in self.timesteps
104+
},
105+
vtype=gp.GRB.CONTINUOUS,
106+
name="phydro",
80107
)
81108

109+
# Collect the dispatch variables (shallow copy)
110+
self.phydro.update(self.hourly_phydro)
111+
self.phydro.update(self.daily_phydro)
112+
self.phydro.update(self.weekly_phydro)
113+
114+
if self.inputs.use_nondispatch_status_var:
115+
self.uhydro = self.model.addVars(
116+
self.inputs.hydro_units,
117+
self.timesteps,
118+
lb=0,
119+
vtype=gp.GRB.BINARY,
120+
name="uhydro",
121+
)
122+
82123
def get_fixed_objective_terms(self) -> gp.LinExpr:
83124
"""Hydropower units have no fixed objective terms."""
84125
return self.total_fixed_objective_expr
@@ -107,25 +148,14 @@ def get_variable_objective_terms(self, step_k: int) -> gp.LinExpr:
107148

108149
def add_constraints(self, step_k: int, init_conds: dict, **kwargs) -> None:
109150
# Hourly upper bound
110-
self.c_link_hydro_pu = nondispatch_constr.add_c_link_unit_pu(
151+
# Limited by contracted capacity
152+
self.c_hourly_hydro_ub = nondispatch_constr.add_c_hourly_unit_ub(
111153
model=self.model,
112-
pdispatch=self.phydro,
113-
u=self.uhydro,
154+
pdispatch=self.hourly_phydro,
114155
unit_type="hydro",
115156
timesteps=self.timesteps,
116-
step_k=step_k,
117157
units=self.inputs.hydro_unit_node.keys(),
118-
capacity_df=self.inputs.hydro_capacity,
119-
)
120-
121-
# Weekly upper bound
122-
self.c_link_weekly_hydro_pu = nondispatch_constr.add_c_link_unit_pu(
123-
model=self.model,
124-
pdispatch=self.phydro,
125-
u=self.uhydro,
126-
timesteps=self.timesteps,
127-
units=self.inputs.weekly_hydro_unit_node.keys(),
128-
contracted_capacity=self.inputs.hydro_contracted_capacity,
158+
contracted_capacity_dict=self.inputs.hydro_contracted_capacity,
129159
)
130160

131161
# Daily upper bound
@@ -149,9 +179,25 @@ def add_constraints(self, step_k: int, init_conds: dict, **kwargs) -> None:
149179
hydro_capacity_min=self.inputs.hydro_min_capacity,
150180
)
151181

182+
if self.inputs.use_nondispatch_status_var:
183+
self.c_link_hydro_pu = nondispatch_constr.add_c_link_unit_pu(
184+
model=self.model,
185+
pdispatch=self.phydro,
186+
u=self.uhydro,
187+
unit_type="hydro",
188+
timesteps=self.timesteps,
189+
units=self.inputs.hydro_units,
190+
contracted_capacity_dict=self.inputs.hydro_contracted_capacity,
191+
)
192+
152193
def update_variables(self, step_k: int) -> None:
153-
"Hydropower variables do not have time-dependent lower/upper bounds."
154-
return
194+
"Some hydropower units have hourly upper bounds."
195+
# Update the time-dependent upper bound of the variable
196+
update_var_with_variable_ub(
197+
variables=self.hourly_phydro,
198+
step_k=step_k,
199+
capacity_df=self.inputs.hydro_capacity,
200+
)
155201

156202
def update_constraints(self, step_k: int, init_conds: dict, **kwargs) -> None:
157203
"""Update constraints for hydro units.
@@ -162,17 +208,6 @@ def update_constraints(self, step_k: int, init_conds: dict, **kwargs) -> None:
162208
Returns:
163209
None
164210
"""
165-
self.model.remove(self.c_link_hydro_pu)
166-
self.c_link_hydro_pu = nondispatch_constr.add_c_link_unit_pu(
167-
model=self.model,
168-
pdispatch=self.phydro,
169-
u=self.uhydro,
170-
unit_type="hydro",
171-
timesteps=self.timesteps,
172-
step_k=step_k,
173-
units=self.inputs.hydro_unit_node.keys(),
174-
capacity_df=self.inputs.hydro_capacity,
175-
)
176211

177212
self.model.remove(self.c_hydro_limit_daily)
178213
self.c_hydro_limit_daily = nondispatch_constr.add_c_hydro_limit_daily(
@@ -196,7 +231,7 @@ def update_constraints(self, step_k: int, init_conds: dict, **kwargs) -> None:
196231
)
197232

198233
def update_daily_hydropower_capacity(
199-
self, step_k: int, new_capacity: dict[(str, int), float]
234+
self, step_k: int, new_capacity: dict[tuple[str, int], float]
200235
) -> None:
201236
self.model.remove(self.c_hydro_limit_daily)
202237
self.c_hydro_limit_daily = nondispatch_constr.add_c_hydro_limit_daily_dict(

0 commit comments

Comments
 (0)