1- """hydro.py: Hydro unit builder."""
1+ """hydro.py: Hydro unit builder. This includes hourly, daily, and weekly constraints for hydro units. """
22
33from .basebuilder import ComponentBuilder
44
55import gurobipy as gp
66
77from ..input import SystemInput
8+ from ..optim_model .variable_func import (
9+ add_var_with_variable_ub ,
10+ update_var_with_variable_ub ,
11+ )
812from ..optim_model .objfunc import get_marginal_cost_coeff
913from ..optim_model .constraints import nondispatch_constr
1014
1115
1216class 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