Skip to content

Commit 8810ea3

Browse files
committed
check_point
1 parent 7d88d07 commit 8810ea3

File tree

5 files changed

+300
-2
lines changed

5 files changed

+300
-2
lines changed

src/pownet/builder/system.py

Lines changed: 105 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,19 @@ class SystemBuilder(ComponentBuilder):
3838
---------------------------
3939
- `spin_shortfall`: Spinning reserve shortfall (system-wide). Unit: MW.
4040
41+
Curtailment variables:
42+
---------------------------
43+
- `pthermal_curtail`: Thermal unit curtailment. Unit: MW.
44+
- `phydro_curtail`: Hydro unit curtailment. Unit: MW.
45+
- `psolar_curtail`: Solar unit curtailment. Unit: MW.
46+
- `pwind_curtail`: Wind unit curtailment. Unit: MW.
47+
- `pimp_curtail`: Import unit curtailment. Unit: MW.
48+
49+
Trade variables:
50+
---------------------------
51+
- `trade_fwd`: Forward trade on interties. Unit: MW.
52+
- `trade_bwd`: Backward trade on interties. Unit: MW.
53+
4154
Fixed objective terms
4255
===========================
4356
- load shortfall penalty
@@ -77,6 +90,10 @@ def __init__(self, model: gp.Model, inputs: SystemInput):
7790
self.pwind_curtail = gp.tupledict()
7891
self.pimp_curtail = gp.tupledict()
7992

93+
# Trade variables
94+
self.trade_fwd = gp.tupledict()
95+
self.trade_bwd = gp.tupledict()
96+
8097
# --- Objective terms ---
8198
# Fixed objective terms
8299
self.load_shortfall_penalty_expr = gp.LinExpr()
@@ -110,6 +127,15 @@ def __init__(self, model: gp.Model, inputs: SystemInput):
110127
self.c_daily_hydro_curtail_ess = gp.tupledict()
111128
self.c_weekly_hydro_curtail_ess = gp.tupledict()
112129

130+
# Trade constraints
131+
# Note: Set the RHS of the opposite flow path to zero for mutual exclusivity
132+
self.c_trade_fwd_ub = gp.tupledict()
133+
self.c_trade_bwd_ub = gp.tupledict()
134+
135+
# Trade LMPs
136+
self.trade_lmps: dict[tuple[str, str], float] = {}
137+
138+
113139
def add_variables(self, step_k: int) -> None:
114140

115141
##############################################
@@ -201,6 +227,23 @@ def add_variables(self, step_k: int) -> None:
201227
name="theta",
202228
)
203229

230+
# --- Trade variables ---
231+
# UB set by constraints.
232+
self.trade_fwd = self.model.addVars(
233+
self.inputs.intertie_points,
234+
self.timesteps,
235+
lb=0,
236+
vtype=gp.GRB.CONTINUOUS,
237+
name="trade_fwd",
238+
)
239+
self.trade_bwd = self.model.addVars(
240+
self.inputs.intertie_points,
241+
self.timesteps,
242+
lb=0,
243+
vtype=gp.GRB.CONTINUOUS,
244+
name="trade_bwd",
245+
)
246+
204247
#############################################
205248
# Variables with time-dependent upper bounds
206249
#############################################
@@ -221,7 +264,7 @@ def add_variables(self, step_k: int) -> None:
221264
]
222265
for t in self.timesteps
223266
for source, sink in self.inputs.edges
224-
},
267+
},
225268
vtype=gp.GRB.CONTINUOUS,
226269
name="flow_fwd",
227270
)
@@ -471,12 +514,14 @@ def _add_nondispatchable_unit_curtailment(
471514
)
472515

473516
def add_constraints(self, step_k: int, init_conds: dict, **kwargs) -> None:
474-
475517
# Thermal-unit specific variables
476518
spin_vars = kwargs.get("spin_vars", None)
477519
vpowerbar_vars = kwargs.get("vpowerbar_vars", None)
478520
thermal_status_vars = kwargs.get("thermal_status_vars", None)
479521

522+
# Power trading
523+
trade_limits = kwargs.get("trade_limits", {})
524+
480525
# Power variables
481526
pthermal = kwargs.get("pthermal", None)
482527
phydro = kwargs.get("phydro", None)
@@ -531,6 +576,8 @@ def add_constraints(self, step_k: int, init_conds: dict, **kwargs) -> None:
531576
neg_pmismatch=self.neg_pmismatch,
532577
flow_fwd=self.flow_fwd,
533578
flow_bwd=self.flow_bwd,
579+
trade_fwd=self.trade_fwd,
580+
trade_bwd=self.trade_bwd,
534581
timesteps=self.timesteps,
535582
step_k=step_k,
536583
thermal_units=self.inputs.thermal_units,
@@ -540,6 +587,7 @@ def add_constraints(self, step_k: int, init_conds: dict, **kwargs) -> None:
540587
import_units=self.inputs.import_units,
541588
nodes=self.inputs.nodes,
542589
node_edge=self.inputs.node_edge,
590+
intertie_points=self.inputs.intertie_points,
543591
node_generator=self.inputs.node_generator,
544592
ess_charge_units=self.inputs.ess_substation_units,
545593
ess_discharge_units=self.inputs.ess_attach_unit,
@@ -619,6 +667,20 @@ def add_constraints(self, step_k: int, init_conds: dict, **kwargs) -> None:
619667
ess_attached=self.inputs.ess_daily_hydro_units,
620668
)
621669

670+
# --- Trade flow constraints ---
671+
self.c_trade_fwd_ub = system_constr.add_c_trade_fwd_ub(
672+
model=self.model,
673+
trade_fwd=self.trade_fwd,
674+
intertie_points=self.inputs.intertie_points,
675+
timesteps=self.timesteps,
676+
)
677+
self.c_trade_bwd_ub = system_constr.add_c_trade_bwd_ub(
678+
model=self.model,
679+
trade_bwd=self.trade_bwd,
680+
intertie_points=self.inputs.intertie_points,
681+
timesteps=self.timesteps,
682+
)
683+
622684
def update_variables(self, step_k: int) -> None:
623685
"""Update the time-dependent upper bounds of the flow variables.
624686
@@ -666,6 +728,9 @@ def update_constraints(self, step_k: int, init_conds: dict, **kwargs) -> None:
666728
pdischarge = kwargs.get("pdischarge", None)
667729
charge_state = kwargs.get("charge_state", None)
668730

731+
# Power trade limits
732+
trade_limits = kwargs.get("trade_limits", {})
733+
669734
# --- Spinning reserve constraints ---
670735
self.model.remove(self.c_reserve_req)
671736
if self.inputs.use_spin_var:
@@ -711,6 +776,8 @@ def update_constraints(self, step_k: int, init_conds: dict, **kwargs) -> None:
711776
neg_pmismatch=self.neg_pmismatch,
712777
flow_fwd=self.flow_fwd,
713778
flow_bwd=self.flow_bwd,
779+
trade_fwd=self.trade_fwd,
780+
trade_bwd=self.trade_bwd,
714781
timesteps=self.timesteps,
715782
step_k=step_k,
716783
thermal_units=self.inputs.thermal_units,
@@ -720,6 +787,7 @@ def update_constraints(self, step_k: int, init_conds: dict, **kwargs) -> None:
720787
import_units=self.inputs.import_units,
721788
nodes=self.inputs.nodes,
722789
node_edge=self.inputs.node_edge,
790+
intertie_points=self.inputs.intertie_points,
723791
node_generator=self.inputs.node_generator,
724792
ess_charge_units=self.inputs.ess_substation_units,
725793
ess_discharge_units=self.inputs.ess_attach_unit,
@@ -804,6 +872,39 @@ def update_constraints(self, step_k: int, init_conds: dict, **kwargs) -> None:
804872
ess_attached=self.inputs.ess_daily_hydro_units,
805873
)
806874

875+
# --- Trade flow constraints ---
876+
# Reset the trade upper bounds to zero for the next step_k.
877+
self.model.remove(self.c_trade_fwd_ub)
878+
self.c_trade_fwd_ub = system_constr.add_c_trade_fwd_ub(
879+
model=self.model,
880+
trade_fwd=self.trade_fwd,
881+
intertie_points=self.inputs.intertie_points,
882+
timesteps=self.timesteps,
883+
)
884+
self.model.remove(self.c_trade_bwd_ub)
885+
self.c_trade_bwd_ub = system_constr.add_c_trade_bwd_ub(
886+
model=self.model,
887+
trade_bwd=self.trade_bwd,
888+
intertie_points=self.inputs.intertie_points,
889+
timesteps=self.timesteps,
890+
)
891+
892+
def update_c_trade_ub_rhs(self, trade_limits: dict[tuple[str, str], float]) -> None:
893+
# Forward trade upper bounds
894+
for constr_name, constr in self.c_trade_fwd_ub.items():
895+
intertie_point = constr_name[0]
896+
if intertie_point in trade_limits:
897+
constr.RHS = trade_limits[intertie_point]
898+
else:
899+
constr.RHS = 0.0
900+
901+
for constr_name, constr in self.c_trade_bwd_ub.items():
902+
intertie_point = constr_name[0]
903+
if intertie_point in trade_limits:
904+
constr.RHS = trade_limits[intertie_point]
905+
else:
906+
constr.RHS = 0.0
907+
807908
def get_variables(self) -> dict[str, gp.tupledict]:
808909
"""Get the variables of the system builder.
809910
@@ -816,5 +917,7 @@ def get_variables(self) -> dict[str, gp.tupledict]:
816917
"spin_shortfall": self.spin_shortfall,
817918
"flow_fwd": self.flow_fwd,
818919
"flow_bwd": self.flow_bwd,
920+
"trade_fwd": self.trade_fwd,
921+
"trade_bwd": self.trade_bwd,
819922
"theta": self.theta,
820923
}

src/pownet/coupler/power_power.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"""power_power.py: PowerSystemCoupler facilitates the electricity trading among power systems."""
2+
3+
from ..core.model_builder import ModelBuilder
4+
5+
6+
import logging
7+
logger = logging.getLogger(__name__)
8+
9+
10+
def calculate_system_cost() -> float:
11+
return 0.0
12+
13+
14+
class PowerPowerCoupler:
15+
def __init__(self, model_builders: list[ModelBuilder]) -> None:
16+
self.model_builders: dict[str, ModelBuilder] = {
17+
model_builder.inputs.model_name: model_builder
18+
for model_builder in model_builders
19+
}
20+
21+
# Locational marginal prices (LMPs) in the format {node_id: lmp_value}
22+
self.lmps: dict[str, float] = {}
23+
24+
# Trade forward variables
25+
best_allowed_trade_fwd = {}
26+
best_allowed_trade_bwd = {}
27+
28+
# Trade backward variables
29+
30+
def initialize(self):
31+
pass
32+
33+
def get_trade_variables(self):
34+
pass
35+
36+
def _update_model_trade_constraints(self):
37+
pass
38+
39+
def _update_model_objective(self):
40+
pass
41+
42+
def _optimize_models(self):
43+
pass
44+
45+
def _record_results(self):
46+
pass
47+
48+
def _update_models(self):
49+
pass
50+
51+
def _reset_models(self):
52+
# Do we even need this as we want to benefit from warm starts?
53+
pass
54+
55+
def run(
56+
self,
57+
learning_rate: float = 0.1,
58+
max_iterations: int = 100,
59+
deviation_threshold: float = 1e-4,
60+
):
61+
pass

src/pownet/data_model/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
"""This is the core module."""
22

33
from .reservoir import ReservoirParams
4+
from .power_trade import PowerTradeParams
45

56
__all__ = [
67
"ReservoirParams",
8+
"PowerTradeParams",
79
]

src/pownet/input.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
from gurobipy import GRB
1111
import pandas as pd
1212

13+
from .data_model.power_trade import PowerTradeParams
14+
1315
logger = logging.getLogger(__name__)
1416

1517

@@ -229,6 +231,10 @@ def __init__(
229231
self.nodes: set[str] = set(["b1"]) # Will get overwritten by the actual nodes
230232
self.node_edge: dict[str, list[str]] = {}
231233

234+
# --- Trade parameters
235+
self.intertie_points: list[tuple[str, str]] = [] # No directionality, just pairs of nodes
236+
self.intertie_capacities: dict[tuple[str, str], float] = {}
237+
232238
def _load_timeseries_from_csv(
233239
self, filename: str, header_levels: int
234240
) -> pd.DataFrame:
@@ -1125,3 +1131,20 @@ def get_unit_contracts(self) -> dict[str, str]:
11251131
all_contracts.update(self.nondispatch_contracts)
11261132
all_contracts.update(self.ess_contracts)
11271133
return all_contracts
1134+
1135+
1136+
def load_trade_params(self, trade_params: PowerTradeParams) -> None:
1137+
"""Load trade parameters into the system builder.
1138+
1139+
Args:
1140+
trade_params (dict): A dictionary containing trade parameters.
1141+
1142+
Returns:
1143+
None
1144+
"""
1145+
1146+
# Find intertie points that contain nodes of this system
1147+
for (source, sink), intertie_capacity in trade_params.intertie_capacities.items():
1148+
if (source in self.nodes) or (sink in self.nodes):
1149+
self.intertie_points.append((source, sink))
1150+
self.intertie_capacities[(source, sink)] = intertie_capacity

0 commit comments

Comments
 (0)