22"""
33
44from .input import SystemInput
5- import logging
65
76from gurobipy import GRB
87import gurobipy as gp
98import pandas as pd
109
1110import pownet .modeling as modeling
12- from pownet .modeling import PowerSystemModel
1311from pownet .data_utils import get_unit_hour_from_varnam , get_edge_hour_from_varname
1412
15- logger = logging .getLogger (__name__ )
16-
1713
1814class ModelBuilder :
1915 """Build an instance of the Unit Commitment Problem.
@@ -35,7 +31,7 @@ class ModelBuilder:
3531 def __init__ (self , inputs : SystemInput ) -> None :
3632 self .inputs = inputs
3733 self .timesteps = range (1 , self .inputs .sim_horizon + 1 )
38- self .model : gp .Model = None
34+ self .model : gp .Model = gp . Model ( self . inputs . model_id )
3935
4036 # Variables
4137 # Thermal units
@@ -51,6 +47,7 @@ def __init__(self, inputs: SystemInput) -> None:
5147 # Energy storage
5248 self .pcharge = gp .tupledict ()
5349 self .pdischarge = gp .tupledict ()
50+ self .pdischarge_shortfall = gp .tupledict ()
5451
5552 self .charge_state = gp .tupledict () # State of charge
5653 self .ucharge = gp .tupledict () # Charging indicator
@@ -92,6 +89,7 @@ def __init__(self, inputs: SystemInput) -> None:
9289
9390 self .load_shortfall_penalty_expr = gp .LinExpr ()
9491 self .spin_shortfall_penalty_expr = gp .LinExpr ()
92+ self .pdischarge_shortfall_penalty_expr = gp .LinExpr ()
9593
9694 # Thermal unit constraints
9795 self .c_link_uvw_init = gp .tupledict ()
@@ -161,6 +159,7 @@ def add_variables(self, step_k: int) -> None:
161159 -----------------
162160 - `pcharge`: Power charging an energy storage system. Unit: MW.
163161 - `pdischarge`: Power discharging an energy storage system. Unit: MW.
162+ - `pdischarge_shortfall`: Shortfall in discharging power. Unit: MW.
164163 - `charge_state`: State of charge of an energy storage system. Unit: MWh.
165164 - `ucharge`: Indicator that an ESS is charging. Unitless.
166165 - `udischarge`: Indicator that an ESS is discharging. Unitless.
@@ -205,28 +204,33 @@ def add_variables(self, step_k: int) -> None:
205204 (
206205 "phydro_curtail" ,
207206 self .inputs .hydro_units ,
208- self .inputs .hydro_contracted_capacity ,
207+ self .inputs .hydro_max_capacity ,
209208 ),
210209 ("psolar" , self .inputs .solar_units , self .inputs .solar_contracted_capacity ),
211210 (
212211 "psolar_curtail" ,
213212 self .inputs .solar_units ,
214- self .inputs .solar_contracted_capacity ,
213+ self .inputs .solar_max_capacity ,
215214 ),
216215 ("pwind" , self .inputs .wind_units , self .inputs .wind_contracted_capacity ),
217216 (
218217 "pwind_curtail" ,
219218 self .inputs .wind_units ,
220- self .inputs .wind_contracted_capacity ,
219+ self .inputs .wind_max_capacity ,
221220 ),
222221 ("pimp" , self .inputs .import_units , self .inputs .import_contracted_capacity ),
223222 (
224223 "pimp_curtail" ,
225224 self .inputs .import_units ,
226- self .inputs .import_contracted_capacity ,
225+ self .inputs .import_max_capacity ,
227226 ),
228227 ("pcharge" , self .inputs .storage_units , self .inputs .ess_max_charge ),
229228 ("pdischarge" , self .inputs .storage_units , self .inputs .ess_max_discharge ),
229+ (
230+ "pdischarge_shortfall" ,
231+ self .inputs .storage_units ,
232+ self .inputs .ess_max_discharge ,
233+ ),
230234 ]
231235
232236 for varname , units , capacity_dict in var_with_u_tuples :
@@ -474,6 +478,12 @@ def set_objfunc(self, step_k: int) -> None:
474478 self .inputs .spin_shortfall_penalty_factor * gp .quicksum (self .spin_shortfall )
475479 )
476480
481+ # Penalize discharge shortfall
482+ self .pdischarge_shortfall_penalty_expr = (
483+ self .inputs .ess_discharge_shortfall_penalty_factor
484+ * gp .quicksum (self .pdischarge_shortfall )
485+ )
486+
477487 ################################
478488 # Renewable energy, import sources, and energy storage
479489 ################################
@@ -491,6 +501,7 @@ def set_objfunc(self, step_k: int) -> None:
491501 + self .thermal_curtail_expr
492502 + self .load_shortfall_penalty_expr
493503 + self .spin_shortfall_penalty_expr
504+ + self .pdischarge_shortfall_penalty_expr
494505 + rnw_import_expr
495506 ),
496507 sense = GRB .MINIMIZE ,
@@ -503,7 +514,7 @@ def _add_hourly_hydropower_constraints(self, step_k: int) -> None:
503514 model = self .model ,
504515 pdispatch = self .phydro ,
505516 u = self .uhydro ,
506- type = "hydro" ,
517+ unit_type = "hydro" ,
507518 timesteps = self .timesteps ,
508519 step_k = step_k ,
509520 units = self .inputs .hydro_unit_node .keys (),
@@ -514,7 +525,7 @@ def _add_hourly_hydropower_constraints(self, step_k: int) -> None:
514525 pdispatch = self .phydro ,
515526 pcurtail = self .phydro_curtail ,
516527 pcharge = self .pcharge ,
517- type = "hydro" ,
528+ unit_type = "hydro" ,
518529 timesteps = self .timesteps ,
519530 step_k = step_k ,
520531 units = self .inputs .hydro_unit_node .keys (),
@@ -538,7 +549,7 @@ def _add_daily_hydropower_constraints(self, step_k: int) -> None:
538549 pdispatch = self .phydro ,
539550 pcurtail = self .phydro_curtail ,
540551 pcharge = self .pcharge ,
541- type = "hydro" ,
552+ unit_type = "hydro" ,
542553 sim_horizon = self .inputs .sim_horizon ,
543554 step_k = step_k ,
544555 units = self .inputs .daily_hydro_unit_node .keys (),
@@ -549,13 +560,15 @@ def _add_daily_hydropower_constraints(self, step_k: int) -> None:
549560 def _add_hydropower_constraints (self , step_k : int ) -> None :
550561
551562 self ._add_hourly_hydropower_constraints (step_k = step_k )
552- self ._add_daily_hydropower_constraints (step_k = step_k )
553563
554- # Does not update every step_k for this constraint
564+ self ._add_daily_hydropower_constraints (step_k = step_k )
565+ # With daily formulation, each hour is still limited by turbine capacity
566+ # Not updated every step_k
555567 self .c_link_daily_hydro_pu = modeling .add_c_link_unit_pu_constant (
556568 model = self .model ,
557569 pdispatch = self .phydro ,
558570 u = self .uhydro ,
571+ unit_type = "hydro" ,
559572 timesteps = self .timesteps ,
560573 units = self .inputs .daily_hydro_unit_node .keys (),
561574 contracted_capacity = self .inputs .hydro_contracted_capacity ,
@@ -593,15 +606,15 @@ def _add_unit_link_pu(self, step_k: int) -> None:
593606 "capacity_df" : self .inputs .import_capacity ,
594607 },
595608 }
596- for type , params in unit_params .items ():
609+ for unit_type , params in unit_params .items ():
597610 setattr (
598611 self ,
599- f"c_link_{ type } _pu" ,
612+ f"c_link_{ unit_type } _pu" ,
600613 modeling .add_c_link_unit_pu (
601614 model = self .model ,
602615 pdispatch = params ["p" ],
603616 u = params ["u" ],
604- type = type ,
617+ unit_type = unit_type ,
605618 timesteps = self .timesteps ,
606619 step_k = step_k ,
607620 units = params ["units" ],
@@ -647,7 +660,7 @@ def _add_curtail_ess_constraints(self, step_k: int) -> None:
647660 pdispatch = params ["pdispatch" ],
648661 pcurtail = params ["pcurtail" ],
649662 pcharge = params ["pcharge" ],
650- type = unit_type ,
663+ unit_type = unit_type ,
651664 timesteps = self .timesteps ,
652665 step_k = step_k ,
653666 units = params ["units" ],
@@ -1003,13 +1016,12 @@ def build(
10031016 self ,
10041017 step_k : int ,
10051018 init_conds : dict [str , dict ],
1006- ) -> PowerSystemModel :
1019+ ) -> modeling . PowerSystemModel :
10071020 """Build the model for the unit commitment problem."""
1008- self .model = gp .Model (self .inputs .model_id )
10091021 self .add_variables (step_k = step_k )
10101022 self .set_objfunc (step_k = step_k )
10111023 self .add_constraints (step_k = step_k , init_conds = init_conds )
1012- return PowerSystemModel (self .model )
1024+ return modeling . PowerSystemModel (self .model )
10131025
10141026 def _update_variables (self , step_k : int ) -> None :
10151027 """
@@ -1100,6 +1112,7 @@ def _update_objfunc(self, step_k: int) -> None:
11001112 + self .thermal_curtail_expr
11011113 + self .load_shortfall_penalty_expr
11021114 + self .spin_shortfall_penalty_expr
1115+ + self .pdischarge_shortfall_penalty_expr
11031116 + rnw_import_storage_expr
11041117 )
11051118
@@ -1343,64 +1356,4 @@ def update(
13431356 self ._update_objfunc (step_k = step_k )
13441357 self ._update_constraints (step_k = step_k , init_conds = init_conds )
13451358 self .model .update ()
1346-
1347- return PowerSystemModel (self .model )
1348-
1349- def print_added_constraints (self ):
1350- added_constrs = []
1351- not_added_constrs = []
1352-
1353- constraints_list = [
1354- "c_link_uvw_init" ,
1355- "c_link_uvw" ,
1356- "c_link_pthermal" ,
1357- "c_link_pu_lower" ,
1358- "c_link_pu_upper" ,
1359- "c_thermal_curtail" ,
1360- "c_min_down_init" ,
1361- "c_min_up_init" ,
1362- "c_min_down" ,
1363- "c_min_up" ,
1364- "c_peak_down_bound" ,
1365- "c_peak_up_bound" ,
1366- "c_ramp_down_init" ,
1367- "c_ramp_up_init" ,
1368- "c_ramp_down" ,
1369- "c_ramp_up" ,
1370- "c_ref_node" ,
1371- "c_angle_diff" ,
1372- "c_kirchhoff" ,
1373- "c_flow_balance" ,
1374- "c_link_spin" ,
1375- "c_link_ppbar" ,
1376- "c_reserve_req" ,
1377- "c_hydro_curtail_ess" ,
1378- "c_daily_hydro_curtail_ess" ,
1379- "c_solar_curtail_ess" ,
1380- "c_wind_curtail_ess" ,
1381- "c_import_curtail_ess" ,
1382- "c_link_hydro_pu" ,
1383- "c_link_daily_hydro_pu" ,
1384- "c_link_solar_pu" ,
1385- "c_link_wind_pu" ,
1386- "c_link_import_pu" ,
1387- "c_hydro_limit_daily" ,
1388- "c_link_ess_charge" ,
1389- "c_link_discharge" ,
1390- "c_link_ess_state" ,
1391- "c_unit_ess_balance_init" ,
1392- "c_unit_ess_balance" ,
1393- ]
1394-
1395- for attr_name in constraints_list :
1396- if getattr (self , attr_name ):
1397- added_constrs .append (attr_name )
1398- else :
1399- not_added_constrs .append (attr_name )
1400-
1401- log_message = "\n Added constraints:\n "
1402- log_message += "\n " .join (added_constrs )
1403- log_message += "\n \n Not added constraints:\n "
1404- log_message += "\n " .join (not_added_constrs )
1405-
1406- logger .warning (log_message )
1359+ return modeling .PowerSystemModel (self .model )
0 commit comments