Skip to content

Commit 4cb4fcc

Browse files
committed
feat(weekly_hydro): weekly hydro introduced
1 parent 70f17a3 commit 4cb4fcc

File tree

8 files changed

+274
-42
lines changed

8 files changed

+274
-42
lines changed

scripts/run_pownet.py

Lines changed: 60 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import os
12
from datetime import datetime
23
from pownet.core import (
34
DataProcessor,
@@ -9,29 +10,32 @@
910
)
1011
from pownet.data_utils import create_init_condition
1112

13+
scen_path = ".\\model_library"
1214
##### User inputs #####
1315
to_process_inputs = True
14-
sim_horizon = 24
15-
model_name = "dummy"
16+
sim_horizon = 24*7
17+
model_name = "cumberland"
1618
steps_to_run = 3 # Default is None
1719
do_plot = True
1820
#######################
1921

2022
if to_process_inputs:
2123
data_processor = DataProcessor(
22-
input_folder="../model_library", model_name=model_name, year=2016, frequency=50
24+
input_folder=scen_path, model_name=model_name, year=2016, frequency=50
2325
)
2426
data_processor.execute_data_pipeline()
2527

2628
inputs = SystemInput(
27-
input_folder="../model_library",
29+
input_folder=scen_path,
2830
model_name=model_name,
2931
year=2016,
3032
sim_horizon=sim_horizon,
31-
spin_reserve_factor=0.15,
33+
spin_reserve_factor=0,
3234
load_shortfall_penalty_factor=1000,
3335
load_curtail_penalty_factor=1,
3436
spin_shortfall_penalty_factor=1000,
37+
line_capacity_factor=1,
38+
line_loss_factor=0
3539
)
3640
inputs.load_and_check_data()
3741

@@ -42,14 +46,15 @@
4246
build_times = []
4347
opt_times = []
4448
objvals = []
45-
init_conditions = create_init_condition(inputs.thermal_units)
49+
init_conditions = create_init_condition(inputs.thermal_units) # init conditions should be empty dict if we dont have any thermal generators
4650

4751
if steps_to_run is None:
4852
steps_to_run = 10 # 365 - (sim_horizon // 24 - 1)
4953

50-
for step_k in range(1, steps_to_run):
54+
start_day = 1
55+
for step_k in range(start_day, steps_to_run):
5156
start_time = datetime.now()
52-
if step_k == 1:
57+
if step_k == start_day:
5358
power_system_model = model_builder.build(
5459
step_k=step_k,
5560
init_conds=init_conditions,
@@ -61,6 +66,7 @@
6166
)
6267
build_times.append((datetime.now() - start_time).total_seconds())
6368

69+
power_system_model.model.write(str(step_k) + '.lp')
6470
power_system_model.optimize(mipgap=0.001)
6571

6672
# Raise an error if the model is not feasible
@@ -79,45 +85,65 @@
7985
)
8086
init_conditions = record.get_init_conds()
8187

88+
import os
89+
output_folder = ".//outputs"
90+
if not os.path.exists(output_folder):
91+
os.makedirs(output_folder)
92+
record.write_simulation_results(output_folder)
8293

94+
95+
96+
# output_folder = ".//outputs"
97+
# if not os.path.exists(output_folder):
98+
# os.makedirs(output_folder)
99+
# results.write_results(output_folder)
100+
101+
import pandas as pd
83102
# Process the results
84-
output_processor = OutputProcessor(inputs)
103+
output_processor = OutputProcessor()
104+
output_processor.load(inputs=inputs)
105+
# output_processor = OutputProcessor(inputs)
85106
node_var_df = record.get_node_variables()
86-
output_processor.load_from_dataframe(node_var_df)
107+
output_processor.get_hourly_generation(node_var_df)
108+
# output_processor.load_from_dataframe(node_var_df)
87109

110+
output_processor.get_thermal_unit_hourly_dispatch(node_var_df)
111+
a = output_processor.get_unit_hourly_generation(node_var_df)
88112
# Visualize the results
89113
if do_plot:
90114
visualizer = Visualizer(inputs.model_id)
91-
if steps_to_run <= 3:
115+
if steps_to_run <= 7:
92116
visualizer.plot_fuelmix_bar(
93-
dispatch=output_processor.get_hourly_dispatch(),
94-
demand=output_processor.get_hourly_demand(),
117+
dispatch=output_processor.get_hourly_generation(node_var_df),
118+
demand=output_processor.get_hourly_demand(inputs.demand),
95119
)
120+
visualizer.plot_lmp(record.lmp_df)
96121
else:
97122
visualizer = Visualizer(inputs.model_id)
98123
visualizer.plot_fuelmix_area(
99124
dispatch=output_processor.get_daily_dispatch(),
100125
demand=output_processor.get_daily_demand(),
101126
)
102127

103-
# # Save other modeling statistics
104-
# import os
105-
# from pownet.folder_utils import get_output_dir
106-
# import pandas as pd
107-
108-
# folder_dir = get_output_dir()
109-
# prefix_name = "warmstart"
110-
# df = pd.DataFrame(
111-
# {
112-
# "build_time": build_times,
113-
# "opt_time": opt_times,
114-
# "objval": objvals,
115-
# }
116-
# )
117-
# df.to_csv(
118-
# os.path.join(
119-
# folder_dir,
120-
# f"branch{prefix_name}_{inputs.model_name}_{sim_horizon}_modelstats.csv",
121-
# ),
122-
# index=False,
123-
# )
128+
129+
# Save other modeling statistics
130+
import os
131+
from pownet.folder_utils import get_output_dir
132+
import pandas as pd
133+
134+
folder_dir = get_output_dir()
135+
prefix_name = "warmstart"
136+
df = pd.DataFrame(
137+
{
138+
"build_time": build_times,
139+
"opt_time": opt_times,
140+
"objval": objvals,
141+
}
142+
)
143+
df.to_csv(
144+
os.path.join(
145+
folder_dir,
146+
f"branch{prefix_name}_{inputs.model_name}_{sim_horizon}_modelstats.csv",
147+
),
148+
index=False,
149+
)

scripts/run_quickstart.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@ def main():
99

1010
# --------- User inputs
1111

12-
input_folder = "..//model_library"
13-
output_folder = "..//outputs"
12+
input_folder = ".//model_library"
13+
output_folder = ".//outputs"
1414

15-
model_name = "dummy"
15+
model_name = "cumberland"
1616
model_year = 2016
1717

1818
# Simulation parameters
1919
sim_horizon = 24
20-
steps_to_run = 2 # 2 Simulation days or 48 hours
20+
steps_to_run = 30 # 2 Simulation days or 48 hours
2121
solver = "gurobi" # or highs
2222

2323
# --------- End of user inputs
@@ -44,6 +44,8 @@ def main():
4444
simulator.plot_fuelmix("bar", output_folder)
4545
simulator.plot_thermal_units(output_folder)
4646

47+
simulator.plot_thermal_units(output_folder)
48+
4749

4850
if __name__ == "__main__":
4951
main()

src/pownet/core/builder.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ def __init__(self, inputs: SystemInput) -> None:
122122

123123
self.c_hydro_curtail_ess = gp.tupledict()
124124
self.c_daily_hydro_curtail_ess = gp.tupledict()
125+
self.c_weekly_hydro_curtail_ess = gp.tupledict()
125126
self.c_solar_curtail_ess = gp.tupledict()
126127
self.c_wind_curtail_ess = gp.tupledict()
127128
self.c_import_curtail_ess = gp.tupledict()
@@ -130,11 +131,13 @@ def __init__(self, inputs: SystemInput) -> None:
130131
# and provides indicators if the unit is online
131132
self.c_link_hydro_pu = gp.tupledict()
132133
self.c_link_daily_hydro_pu = gp.tupledict()
134+
self.c_link_weekly_hydro_pu = gp.tupledict()
133135
self.c_link_solar_pu = gp.tupledict()
134136
self.c_link_wind_pu = gp.tupledict()
135137
self.c_link_import_pu = gp.tupledict()
136138

137139
self.c_hydro_limit_daily = gp.tupledict()
140+
self.c_hydro_limit_weekly = gp.tupledict()
138141

139142
# Energy storage constraints
140143
self.c_link_ess_charge = gp.tupledict()
@@ -380,6 +383,12 @@ def _build_rnw_import_storage_objfunc_terms(self, step_k: int) -> gp.LinExpr:
380383
self.inputs.daily_hydro_must_take_units,
381384
self.inputs.nondispatch_contracts,
382385
),
386+
# Weekly hydro
387+
(
388+
self.phydro_curtail,
389+
self.inputs.weekly_hydro_must_take_units,
390+
self.inputs.nondispatch_contracts,
391+
),
383392
(self.psolar, self.inputs.solar_units, self.inputs.nondispatch_contracts),
384393
(
385394
self.psolar_curtail,
@@ -531,6 +540,7 @@ def _add_daily_hydropower_constraints(self, step_k: int) -> None:
531540
sim_horizon=self.inputs.sim_horizon,
532541
hydro_units=self.inputs.daily_hydro_unit_node.keys(),
533542
hydro_capacity=self.inputs.daily_hydro_capacity,
543+
hydro_capacity_min=self.inputs.hydro_capacity_min,
534544
)
535545
# (2) The curtailment is enforced for the day and not the hour.
536546
self.c_daily_hydro_curtail_ess = modeling.add_c_unit_curtail_ess_daily(
@@ -546,10 +556,36 @@ def _add_daily_hydropower_constraints(self, step_k: int) -> None:
546556
ess_attached=self.inputs.ess_daily_hydro_units,
547557
)
548558

559+
def _add_weekly_hydropower_constraints(self, step_k: int) -> None:
560+
# (1) Define the weekly upper bound.
561+
self.c_hydro_limit_weekly = modeling.add_c_hydro_limit_weekly(
562+
model=self.model,
563+
phydro=self.phydro,
564+
step_k=step_k,
565+
sim_horizon=self.inputs.sim_horizon,
566+
hydro_units=self.inputs.weekly_hydro_unit_node.keys(),
567+
hydro_capacity=self.inputs.weekly_hydro_capacity,
568+
hydro_capacity_min=self.inputs.hydro_capacity_min,
569+
)
570+
# (2) The curtailment is enforced for the day and not the hour.
571+
# self.c_weekly_hydro_curtail_ess = modeling.add_c_unit_curtail_ess_weekly(
572+
# model=self.model,
573+
# pdispatch=self.phydro,
574+
# pcurtail=self.phydro_curtail,
575+
# pcharge=self.pcharge,
576+
# type="hydro",
577+
# sim_horizon=self.inputs.sim_horizon,
578+
# step_k=step_k,
579+
# units=self.inputs.weekly_hydro_unit_node.keys(),
580+
# capacity_df=self.inputs.weekly_hydro_capacity,
581+
# ess_attached=self.inputs.ess_weekly_hydro_units,
582+
# )
583+
549584
def _add_hydropower_constraints(self, step_k: int) -> None:
550585

551586
self._add_hourly_hydropower_constraints(step_k=step_k)
552587
self._add_daily_hydropower_constraints(step_k=step_k)
588+
self._add_weekly_hydropower_constraints(step_k=step_k)
553589

554590
# Does not update every step_k for this constraint
555591
self.c_link_daily_hydro_pu = modeling.add_c_link_unit_pu_constant(
@@ -561,6 +597,16 @@ def _add_hydropower_constraints(self, step_k: int) -> None:
561597
contracted_capacity=self.inputs.hydro_contracted_capacity,
562598
)
563599

600+
# Does not update every step_k for this constraint
601+
self.c_link_weekly_hydro_pu = modeling.add_c_link_unit_pu_constant(
602+
model=self.model,
603+
pdispatch=self.phydro,
604+
u=self.uhydro,
605+
timesteps=self.timesteps,
606+
units=self.inputs.weekly_hydro_unit_node.keys(),
607+
contracted_capacity=self.inputs.hydro_contracted_capacity,
608+
)
609+
564610
def _update_hydropower_constraints(self, step_k: int) -> None:
565611
# Hourly constraints
566612
self.model.remove(self.c_link_hydro_pu)
@@ -571,6 +617,10 @@ def _update_hydropower_constraints(self, step_k: int) -> None:
571617
self.model.remove(self.c_daily_hydro_curtail_ess)
572618
self._add_daily_hydropower_constraints(step_k=step_k)
573619

620+
self.model.remove(self.c_hydro_limit_weekly)
621+
# self.model.remove(self.c_weekly_hydro_curtail_ess)
622+
self._add_weekly_hydropower_constraints(step_k=step_k)
623+
574624
def _add_unit_link_pu(self, step_k: int) -> None:
575625
# Define parameters
576626
unit_params = {
@@ -1127,10 +1177,13 @@ def _update_constraints(self, step_k, init_conds: dict) -> None:
11271177
- c_link_import_pu: Import capacity is a timeseries
11281178
11291179
- c_hydro_limit_daily: Hydropower capacity is a timeseries
1180+
- c_hydro_limit_weekly: Hydropower capacity is a timeseries
11301181
11311182
*Curtailment*
11321183
- c_hydro_curtail_ess: Hydropower capacity is a timeseries
11331184
- c_daily_hydro_curtail_ess: Daily hydropower capacity is a timeseries
1185+
- c_weekly_hydro_curtail_ess: Weekly hydropower capacity is a timeseries
1186+
11341187
- c_solar_curtail_ess: Solar capacity is a timeseries
11351188
- c_wind_curtail_ess: Wind capacity is a timeseries
11361189
- c_import_curtail_ess: Import capacity is a timeseries
@@ -1376,15 +1429,18 @@ def print_added_constraints(self):
13761429
"c_reserve_req",
13771430
"c_hydro_curtail_ess",
13781431
"c_daily_hydro_curtail_ess",
1432+
"c_weekly_hydro_curtail_ess",
13791433
"c_solar_curtail_ess",
13801434
"c_wind_curtail_ess",
13811435
"c_import_curtail_ess",
13821436
"c_link_hydro_pu",
13831437
"c_link_daily_hydro_pu",
1438+
"c_link_weekly_hydro_pu",
13841439
"c_link_solar_pu",
13851440
"c_link_wind_pu",
13861441
"c_link_import_pu",
13871442
"c_hydro_limit_daily",
1443+
"c_hydro_limit_weekly",
13881444
"c_link_ess_charge",
13891445
"c_link_discharge",
13901446
"c_link_ess_state",

0 commit comments

Comments
 (0)