Skip to content

Commit ed8cc87

Browse files
author
Kyle Skolfield
committed
for this week
1 parent f70da78 commit ed8cc87

File tree

6 files changed

+250
-18
lines changed

6 files changed

+250
-18
lines changed

Diff for: gtep/driver_idaes_viz.py

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
from gtep.gtep_model import ExpansionPlanningModel
2+
from gtep.gtep_data import ExpansionPlanningData
3+
from gtep.gtep_solution import ExpansionPlanningSolution
4+
from pyomo.core import TransformationFactory
5+
from pyomo.contrib.appsi.solvers.highs import Highs
6+
from pyomo.contrib.appsi.solvers.gurobi import Gurobi
7+
import gtep.validation
8+
9+
10+
data_path = "./gtep/data/5bus_jsc_flat"
11+
data_object = ExpansionPlanningData()
12+
data_object.load_prescient(data_path)
13+
14+
15+
mod_object = ExpansionPlanningModel(
16+
stages=2,
17+
data=data_object.representative_data,
18+
num_reps=4,
19+
len_reps=1,
20+
num_commit=6,
21+
num_dispatch=4,
22+
)
23+
24+
25+
mod_object.config["include_investment"] = True
26+
mod_object.config["dispatch_randomization"] = False
27+
mod_object.config["scale_loads"] = False
28+
mod_object.create_model()
29+
30+
31+
TransformationFactory("gdp.bound_pretransformation").apply_to(mod_object.model)
32+
TransformationFactory("gdp.bigm").apply_to(mod_object.model)
33+
opt = Gurobi()
34+
mod_object.results = opt.solve(mod_object.model)
35+
36+
sol_object = ExpansionPlanningSolution()
37+
sol_object.load_from_model(mod_object)
38+
sol_object.dump_json("./Sim Eng Viz/4Hourly/GTEP/idaes_solution.json")
39+
40+
data_out_path = "./Sim Eng Viz/4Hourly/Prescient/data"
41+
42+
gtep.validation.populate_generators(data_path, sol_object, data_out_path)
43+
gtep.validation.populate_transmission(data_path, sol_object, data_out_path)
44+
gtep.validation.filter_pointers(data_path, data_out_path)
45+
gtep.validation.clone_timeseries(data_path, data_out_path)
46+
47+
48+
# sol_object.import_data_object(data_object)
49+
50+
# sol_object.plot_levels(save_dir="./Sim Eng Viz/Hourly/GTEP/plots/")
51+
52+
from prescient.simulator import Prescient
53+
54+
# set some options
55+
prescient_options = {
56+
"data_path": data_out_path,
57+
"input_format": "rts-gmlc",
58+
"simulate_out_of_sample": False,
59+
"run_sced_with_persistent_forecast_errors": False,
60+
"output_directory": "./Sim Eng Viz/4Hourly/Prescient/results/04-01-2020",
61+
"start_date": "03-26-2020",
62+
"num_days": 7,
63+
"sced_horizon": 24,
64+
"ruc_mipgap": 0.01,
65+
"reserve_factor": 0,
66+
"deterministic_ruc_solver": "gurobi_persistent",
67+
"deterministic_ruc_solver_options": {
68+
"feas": "off",
69+
"DivingF": "on",
70+
},
71+
"sced_solver": "gurobi",
72+
"sced_frequency_minutes": 60,
73+
"ruc_horizon": 48,
74+
"compute_market_settlements": True,
75+
"monitor_all_contingencies": False,
76+
"output_solver_logs": False,
77+
"price_threshold": 1000,
78+
"contingency_price_threshold": 100,
79+
"reserve_price_threshold": 5,
80+
"ruc_network_type": "btheta",
81+
"sced_network_type": "btheta",
82+
"enforce_sced_shutdown_ramprate": False,
83+
}
84+
# run the simulator
85+
Prescient().simulate(**prescient_options)

Diff for: gtep/gtep_data.py

+9-9
Original file line numberDiff line numberDiff line change
@@ -71,16 +71,16 @@ def load_prescient(self, data_path, options_dict=None):
7171
# default here allows up to 24 hours for periods
7272
time_keys = self.md.data["system"]["time_keys"]
7373
key_idx = time_keys.index("2020-01-01 00:00")
74-
time_key_set = time_keys[key_idx : key_idx + 24]
74+
time_key_set = time_keys[key_idx : key_idx + 24 : 4]
7575
data_list.append(self.md.clone_at_time_keys(time_key_set))
7676
key_idx = time_keys.index("2020-04-01 00:00")
77-
time_key_set = time_keys[key_idx : key_idx + 24]
77+
time_key_set = time_keys[key_idx : key_idx + 24 : 4]
7878
data_list.append(self.md.clone_at_time_keys(time_key_set))
7979
key_idx = time_keys.index("2020-07-01 00:00")
80-
time_key_set = time_keys[key_idx : key_idx + 24]
80+
time_key_set = time_keys[key_idx : key_idx + 24 : 4]
8181
data_list.append(self.md.clone_at_time_keys(time_key_set))
8282
key_idx = time_keys.index("2020-10-01 00:00")
83-
time_key_set = time_keys[key_idx : key_idx + 24]
83+
time_key_set = time_keys[key_idx : key_idx + 24 : 4]
8484
data_list.append(self.md.clone_at_time_keys(time_key_set))
8585

8686
self.representative_data = data_list
@@ -90,8 +90,8 @@ def load_default_data_settings(self):
9090
"""Fills in necessary but unspecified data information."""
9191
for gen in self.md.data["elements"]["generator"]:
9292
self.md.data["elements"]["generator"][gen]["lifetime"] = 3
93-
self.md.data["elements"]["generator"][gen]["spinning_reserve_frac"] = 0.1
94-
self.md.data["elements"]["generator"][gen]["quickstart_reserve_frac"] = 0.1
93+
self.md.data["elements"]["generator"][gen]["spinning_reserve_frac"] = 0
94+
self.md.data["elements"]["generator"][gen]["quickstart_reserve_frac"] = 0
9595
self.md.data["elements"]["generator"][gen]["capital_multiplier"] = 1
9696
self.md.data["elements"]["generator"][gen]["extension_multiplier"] = 1
9797
self.md.data["elements"]["generator"][gen]["max_operating_reserve"] = 1
@@ -101,9 +101,9 @@ def load_default_data_settings(self):
101101
self.md.data["elements"]["generator"][gen]["ramp_down_rate"] = 0.1
102102
self.md.data["elements"]["generator"][gen]["emissions_factor"] = 1
103103
self.md.data["elements"]["generator"][gen]["start_fuel"] = 1
104-
self.md.data["elements"]["generator"][gen]["investment_cost"] = 235164
104+
self.md.data["elements"]["generator"][gen]["investment_cost"] = 235
105105
for branch in self.md.data["elements"]["branch"]:
106106
self.md.data["elements"]["branch"][branch]["loss_rate"] = 0
107107
self.md.data["elements"]["branch"][branch]["distance"] = 1
108-
self.md.data["system"]["min_operating_reserve"] = 0.1
109-
self.md.data["system"]["min_spinning_reserve"] = 0.1
108+
self.md.data["system"]["min_operating_reserve"] = 0
109+
self.md.data["system"]["min_spinning_reserve"] = 0

Diff for: gtep/gtep_model.py

+11-5
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,9 @@ def create_model(self):
102102
# If self.data is a list, it is a list of data for
103103
# representative periods
104104
m.data_list = self.data
105-
m.md = scale_ModelData_to_pu(self.data[0])
105+
# TODO god what have I done
106+
# m.md = scale_ModelData_to_pu(self.data[0])
107+
m.md = self.data[0]
106108
else:
107109
# If self.data is an Egret model data object, representative periods will just copy it unchanged
108110
m.data_list = None
@@ -501,6 +503,8 @@ def renewable_generation_limits(b, renewableGen):
501503

502504
# Define bounds on renewable generator curtailment
503505
def curtailment_limits(b, renewableGen):
506+
if "HYDRO" in renewableGen:
507+
return (0, 0)
504508
return (0, m.renewableCapacity[renewableGen])
505509

506510
b.renewableCurtailment = Var(
@@ -712,8 +716,9 @@ def add_dispatch_constraints(b, disp_per):
712716
r_p = c_p.parent_block()
713717
i_p = r_p.parent_block()
714718

715-
for key in m.loads.keys():
716-
m.loads[key] *= max(0, m.rng.normal(0.5, 0.2))
719+
if m.config["dispatch_randomization"]:
720+
for key in m.loads.keys():
721+
m.loads[key] *= max(0, m.rng.normal(0.5, 0.2))
717722

718723
# Energy balance constraint
719724
@b.Constraint(m.buses)
@@ -1641,6 +1646,7 @@ def model_data_references(m):
16411646
}
16421647

16431648
# Demand at each bus
1649+
## FIXME this just builds a full 8760 of loads which is not really what we want
16441650
m.loads = {
16451651
m.md.data["elements"]["load"][load_n]["bus"]: m.md.data["elements"]["load"][
16461652
load_n
@@ -1693,7 +1699,7 @@ def model_data_references(m):
16931699
## TODO: don't lazily approx NPV, add it into unit handling and calculate from actual time frames
16941700
for stage in m.stages:
16951701
m.investmentFactor[stage] *= 1 / ((1.04) ** (5 * stage))
1696-
m.fixedOperatingCost = Param(m.generators, default=1, units=u.USD / u.hr)
1702+
m.fixedOperatingCost = Param(m.generators, default=0, units=u.USD / u.hr)
16971703
m.deficitPenalty = Param(m.stages, default=1, units=u.USD / (u.MW * u.hr))
16981704

16991705
# Amount of fuel required to be consumed for startup process for each generator
@@ -1722,7 +1728,7 @@ def model_data_references(m):
17221728
# NOTE: what should this be valued at? This being both curtailment and load shed.
17231729
# TODO: update valuations
17241730
m.curtailmentCost = Param(
1725-
initialize=2 * max(value(item) for item in m.fuelCost.values()),
1731+
initialize=10000 * max(value(item) for item in m.fuelCost.values()),
17261732
units=u.USD / (u.MW * u.hr),
17271733
)
17281734
m.loadShedCost = Param(

Diff for: gtep/gtep_solution.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -193,8 +193,9 @@ def nested_set(this_dict, key, val):
193193
self.expressions_tree = results_dict["expressions_tree"]
194194

195195
# mint the final dictionary to save
196-
out_dict = {"data": self.data.data, "results": results_dict}
197-
196+
# out_dict = {"data": self.data.data, "results": results_dict}
197+
out_dict = {"data": self.data[1].data, "results": results_dict}
198+
print(self.data)
198199
self.primals_tree = results_dict["primals_tree"]
199200

200201
return out_dict

Diff for: gtep/idaes_parse.py

+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
from gtep.gtep_solution import ExpansionPlanningSolution
2+
import pandas as pd
3+
import json
4+
import re
5+
import matplotlib as mpl
6+
from matplotlib import colormaps
7+
import seaborn as sns
8+
9+
10+
with open("./Sim Eng Viz/4Hourly/GTEP/idaes_solution.json") as fil:
11+
solutions = json.load(fil)
12+
13+
target_investment_stage = "2"
14+
target_representative_period = "2"
15+
16+
filtered_solns = solutions["results"]["primals_tree"]["investmentStage[2]"][
17+
"representativePeriod[2]"
18+
]
19+
20+
filtered_solns.pop("_logical_to_disjunctive")
21+
22+
23+
def name_filter(gen_name):
24+
return re.search(r"\[.*\]", gen_name).group(0)[1:-1]
25+
26+
27+
thermal_gen = {}
28+
curtailment = {}
29+
load_shed = {}
30+
for key in filtered_solns.keys():
31+
for k, v in filtered_solns[key]["dispatchPeriod[1]"].items():
32+
if "Gen" in k:
33+
k = name_filter(k)
34+
if thermal_gen.get(k):
35+
thermal_gen[k].append(v["value"])
36+
else:
37+
thermal_gen[k] = [v["value"]]
38+
if "Curt" in k:
39+
if curtailment.get(k):
40+
curtailment[k].append(v["value"])
41+
else:
42+
curtailment[k] = [v["value"]]
43+
if "Shed" in k:
44+
if load_shed.get(k):
45+
load_shed[k].append(v["value"])
46+
else:
47+
load_shed[k] = [v["value"]]
48+
# only for the four hourly...
49+
if filtered_solns[key].get("dispatchPeriod[2]"):
50+
for k, v in filtered_solns[key]["dispatchPeriod[2]"].items():
51+
if "Gen" in k:
52+
k = name_filter(k)
53+
if thermal_gen.get(k):
54+
thermal_gen[k].append(v["value"])
55+
else:
56+
thermal_gen[k] = [v["value"]]
57+
for k, v in filtered_solns[key]["dispatchPeriod[3]"].items():
58+
if "Gen" in k:
59+
k = name_filter(k)
60+
if thermal_gen.get(k):
61+
thermal_gen[k].append(v["value"])
62+
else:
63+
thermal_gen[k] = [v["value"]]
64+
for k, v in filtered_solns[key]["dispatchPeriod[4]"].items():
65+
if "Gen" in k:
66+
k = name_filter(k)
67+
if thermal_gen.get(k):
68+
thermal_gen[k].append(v["value"])
69+
else:
70+
thermal_gen[k] = [v["value"]]
71+
72+
73+
print(thermal_gen)
74+
print(curtailment)
75+
print(load_shed)
76+
77+
nonzero_gennames = ["10_STEAM", "3_CT", "4_STEAM"]
78+
for gen in nonzero_gennames:
79+
thermal_gen.pop(gen)
80+
81+
gen_df = pd.DataFrame.from_dict(dict(sorted(thermal_gen.items())))
82+
print(gen_df)
83+
prescient_gen_df = pd.read_csv(
84+
"./Sim Eng Viz/4Hourly/Prescient/results/generation_out.csv",
85+
names=["gen", "val"],
86+
)
87+
88+
print(prescient_gen_df)
89+
for label, content in prescient_gen_df.items():
90+
print(f"label: {label}")
91+
print(f"content: {content}", sep="\n")
92+
93+
prescient_gen = {}
94+
for row in prescient_gen_df.iterrows():
95+
gen = row[-1]["gen"]
96+
val = row[-1]["val"]
97+
if prescient_gen.get(gen):
98+
prescient_gen[gen].append(val)
99+
else:
100+
prescient_gen[gen] = [val]
101+
102+
for gen in nonzero_gennames:
103+
prescient_gen.pop(gen)
104+
prescient_sorted_gen_df = pd.DataFrame.from_dict(dict(sorted(prescient_gen.items())))
105+
106+
my_cmap = sns.color_palette("muted")
107+
108+
109+
ax1 = gen_df.plot.bar(stacked=True, color=my_cmap)
110+
ax1.set_title("GTEP, 4-Hourly Commitment (04/01)")
111+
ax1.set_xlabel("Hour")
112+
ax1.set_ylabel("Generation (MW)")
113+
ax1.figure.savefig("4blah.png")
114+
115+
ax2 = prescient_sorted_gen_df.plot.bar(stacked=True, color=my_cmap)
116+
ax2.set_title("Prescient (04/01)")
117+
ax2.set_xlabel("Hour")
118+
ax2.set_ylabel("Generation (MW)")
119+
ax2.figure.savefig("4blah2.png")
120+
121+
print(gen_df)

Diff for: gtep/validation.py

+21-2
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,17 @@ def gen_name_filter(gen_name):
2929
)
3030

3131
solution_dict = sol_object._to_dict()["results"]["primals_tree"]
32-
end_investment_stage = list(solution_dict.keys())[0]
32+
end_investment_stage = list(solution_dict.keys())[-2]
3333
end_investment_solution_dict = {
3434
k: v["value"]
3535
for k, v in solution_dict[end_investment_stage].items()
3636
if gen_name_filter(k) and v["value"] > 0.5
3737
}
38-
end_investment_gens = [
38+
end_investment_thermal_gens = [
3939
re.search(r"\[.*\]", k).group(0)[1:-1]
4040
for k in end_investment_solution_dict.keys()
4141
]
42+
end_investment_gens = end_investment_thermal_gens
4243

4344
# for renewable:
4445
# total capacity should be installed + operational + extended values
@@ -71,6 +72,18 @@ def renewable_name_filter(gen_name):
7172
os.makedirs(data_output_path)
7273
output_df.to_csv(os.path.join(data_output_path, "gen.csv"), index=False)
7374

75+
# We also need to update initial status values for thermal gens that have been invested
76+
# By default we will set these to (-24, 0)
77+
# initial_df = pd.read_csv(os.path.join(data_input_path, "initial_status.csv"))
78+
# for gen in end_investment_thermal_gens:
79+
# print(gen)
80+
# if gen not in initial_df.columns:
81+
# # initial_df.insert(-1, gen, [-24, 0])
82+
# initial_df[gen] = [-24, 0]
83+
# print(initial_df)
84+
85+
# initial_df.to_csv(os.path.join(data_output_path, "initial_status.csv"), index=False)
86+
7487

7588
def populate_transmission(data_input_path, sol_object, data_output_path):
7689
# load existing and candidate generators from initial prescient data
@@ -115,6 +128,8 @@ def filter_pointers(data_input_path, data_output_path):
115128
# keep generators that exist at the final investment stage and remove the rest
116129
# keep all non-generator timeseries pointers
117130
matching_gen_list = [gen for gen in output_generators_df["GEN UID"]]
131+
132+
## FIXME: this won't work when completely NEW renewable sites are an option
118133
output_df = input_pointers_df[
119134
input_pointers_df["Object"].isin(matching_gen_list)
120135
| input_pointers_df["Category"]
@@ -135,9 +150,13 @@ def clone_timeseries(data_input_path, data_output_path):
135150
file_list.remove("timeseries_pointers.csv")
136151
file_list.remove("gen.csv")
137152
file_list.remove("branch.csv")
153+
file_list.remove("initial_status.csv")
138154

139155
# @jkskolf, I don't think I like this ...
140156
for fname in file_list:
141157
from_file = os.path.join(data_input_path, fname)
142158
to_file = os.path.join(data_output_path, fname)
143159
shutil.copy(from_file, to_file)
160+
161+
162+
# TODO: synchronize load scaling

0 commit comments

Comments
 (0)