Skip to content

Commit 0e8d986

Browse files
pesapstaadecker
authored andcommitted
Merge pull request #110 from REAM-lab/feature/post_process
Refactoring and simplification of postprocess
2 parents 3fb8498 + 6933120 commit 0e8d986

File tree

10 files changed

+181
-78
lines changed

10 files changed

+181
-78
lines changed

switch_model/wecc/get_inputs/cli.py

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
""" Script to retrieve the input data from the switch-wecc database and apply post-processing steps.
22
"""
33
import argparse
4+
import importlib
45
import os
56

67
from switch_model.utilities import query_yes_no, StepTimer
78
from switch_model.wecc.get_inputs.get_inputs import query_db
8-
from switch_model.wecc.get_inputs.register_post_process import run_post_process
99
from switch_model.wecc.utilities import load_config
10-
from switch_model.wecc.get_inputs.post_process_steps import *
10+
11+
# from switch_model.wecc.get_inputs.post_process_steps import *
12+
# from switch_model.wecc.get_inputs.register_post_process import run_post_process, _registered_steps
1113

1214

1315
def main():
@@ -29,7 +31,13 @@ def main():
2931
"want to wait for the command.",
3032
)
3133
parser.add_argument(
32-
"--post-process", default=None, help="Run only this post process step."
34+
"--post-process-only",
35+
default=False,
36+
action="store_true",
37+
help="Run only post process steps.",
38+
)
39+
parser.add_argument(
40+
"--post-process-step", default=None, help="Run only this post process step."
3341
)
3442
parser.add_argument(
3543
"--overwrite",
@@ -43,11 +51,36 @@ def main():
4351
full_config = load_config()
4452
switch_to_input_dir(full_config, overwrite=args.overwrite)
4553

46-
if args.post_process is None:
54+
if not args.post_process_only and args.post_process_step is None:
4755
query_db(full_config, skip_cf=args.skip_cf)
48-
print("Post-processing...")
49-
run_post_process(full_config, step_name=args.post_process)
50-
print(f"\nScript took {timer.step_time_as_str()} seconds to build input tables.")
56+
57+
print("\nRunning post processing...")
58+
59+
# Get location of post process scripts
60+
post_process_path = ".".join(__name__.split(".")[:-1]) + ".post_process_steps"
61+
62+
def run_post_process(module):
63+
"""Run a function from a given module"""
64+
65+
# This uses python module syntax with a dot. Example: import foo.bar.test
66+
mod = importlib.import_module(f".{module}", post_process_path)
67+
68+
post_process = getattr(mod, "post_process")
69+
70+
# Get specific configuration for the post process if specified
71+
post_config = full_config.get(module, None)
72+
73+
# Run post process
74+
post_process(full_config, post_config)
75+
76+
# Run all post process specified, otherwise run single one
77+
if args.post_process_step is None:
78+
for module in full_config["post_process"]:
79+
run_post_process(module)
80+
else:
81+
run_post_process(getattr(args, "post_process_step"))
82+
83+
print(f"\nScript took {timer.step_time_as_str()} seconds.")
5184

5285

5386
def switch_to_input_dir(config, overwrite):

switch_model/wecc/get_inputs/get_inputs.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -932,6 +932,34 @@ def write_wind_to_solar_ratio(wind_to_solar_ratio):
932932
df.to_csv("wind_to_solar_ratio.csv", index=False)
933933

934934

935+
def write_wind_to_solar_ratio(wind_to_solar_ratio):
936+
# TODO ideally we'd have a table where we can specify the wind_to_solar_ratios per period.
937+
# At the moment only the wind_to_solar_ratio is specified and which doesn't allow different values per period
938+
if wind_to_solar_ratio is None:
939+
return
940+
941+
print("wind_to_solar_ratio.csv...")
942+
df = pd.read_csv("periods.csv")[["INVESTMENT_PERIOD"]]
943+
df["wind_to_solar_ratio"] = wind_to_solar_ratio
944+
945+
# wind_to_solar_ratio.csv requires a column called wind_to_solar_ratio_const_gt that is True (1) or False (0)
946+
# This column specifies whether the constraint is a greater than constraint or a less than constraint.
947+
# In our case we want it to be a greater than constraint if we're trying to force wind-to-solar ratio above its default
948+
# and we want it to be a less than constraint if we're trying to force the ratio below its default.
949+
# Here the default is the ratio if we didn't have the constraint.
950+
cutoff_ratio = 0.28
951+
warnings.warn(
952+
"To determine the sign of the wind-to-solar ratio constraint we have "
953+
f"assumed that without the constraint, the wind-to-solar ratio is {cutoff_ratio}. "
954+
f"This value was accurate for Martin's LDES runs however it may not be accurate for you. "
955+
f"You should update this value in get_inputs or manually specify whether you want a greater than "
956+
f"or a less than constraint."
957+
)
958+
df["wind_to_solar_ratio_const_gt"] = 1 if wind_to_solar_ratio > cutoff_ratio else 0
959+
960+
df.to_csv("wind_to_solar_ratio.csv", index=False)
961+
962+
935963
def ca_policies(db_cursor, ca_policies_scenario_id, study_timeframe_id):
936964
if ca_policies_scenario_id is None:
937965
return

switch_model/wecc/get_inputs/post_process_steps/add_storage.py

Lines changed: 43 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
the csvs in the inputs folder.
66
"""
77
import pandas as pd
8-
98
from switch_model.wecc.get_inputs.register_post_process import register_post_process
109

1110

@@ -17,16 +16,16 @@ def fetch_df(tab_name, key, config):
1716
"constants": 0,
1817
"plants": 889129113,
1918
"costs": 1401952285,
20-
"minimums": 1049456965
19+
"minimums": 1049456965,
2120
}
2221
SHEET_ID = "1SJrj039T1T95NLTs964VQnsfZgo2QWCo29x2ireVYcU"
2322

2423
gid = TAB_NAME_GID[tab_name]
2524
url = f"https://docs.google.com/spreadsheet/ccc?key={SHEET_ID}&output=csv&gid={gid}"
2625

27-
df: pd.DataFrame = pd.read_csv(url, index_col=False) \
28-
.replace("FALSE", False) \
29-
.replace("TRUE", True)
26+
df: pd.DataFrame = (
27+
pd.read_csv(url, index_col=False).replace("FALSE", False).replace("TRUE", True)
28+
)
3029

3130
if "description" in df.columns:
3231
df = df.drop("description", axis=1)
@@ -43,17 +42,16 @@ def filer_by_scenario(df, scenario_column, config):
4342
if scenario_column in config:
4443
scenario = config[scenario_column]
4544
else:
46-
scenario = input(f"Which scenario do you want for '{scenario_column}' (default 0) : ")
45+
scenario = input(
46+
f"Which scenario do you want for '{scenario_column}' (default 0) : "
47+
)
4748
scenario = int(scenario) if scenario != "" else 0
4849
df = df[df[scenario_column] == scenario]
4950
return df.drop(scenario_column, axis=1)
5051

5152

5253
def cross_join(df1, df2):
53-
return df1.assign(key=1).merge(
54-
df2.assign(key=1),
55-
on="key"
56-
).drop("key", axis=1)
54+
return df1.assign(key=1).merge(df2.assign(key=1), on="key").drop("key", axis=1)
5755

5856

5957
def add_to_csv(filename, to_add, primary_key=None, append=True):
@@ -83,8 +81,12 @@ def drop_previous_candidate_storage():
8381

8482
gen = pd.read_csv("generation_projects_info.csv", index_col=False)
8583
# Find generation projects that are both storage and not predetermined (i.e. candidate)
86-
predetermined_gen = pd.read_csv("gen_build_predetermined.csv", index_col=False)["GENERATION_PROJECT"]
87-
should_drop = (gen["gen_tech"] == STORAGE_TECH) & ~gen["GENERATION_PROJECT"].isin(predetermined_gen)
84+
predetermined_gen = pd.read_csv("gen_build_predetermined.csv", index_col=False)[
85+
"GENERATION_PROJECT"
86+
]
87+
should_drop = (gen["gen_tech"] == STORAGE_TECH) & ~gen["GENERATION_PROJECT"].isin(
88+
predetermined_gen
89+
)
8890
# Find projects that we should drop (candidate storage)
8991
gen_to_drop = gen[should_drop]["GENERATION_PROJECT"]
9092

@@ -99,42 +101,46 @@ def drop_previous_candidate_storage():
99101

100102

101103
@register_post_process(
102-
name="add_storage",
103104
msg="Adding storage from Google Sheets",
104-
only_with_config=True,
105-
<<<<<<< HEAD
106-
priority=1,
107-
)
108-
def add_storage(config):
109-
from switch_model.tools.add_storage import main
110-
111-
main(
112-
run_post_solve=False, # We will run post solve automatically right afterwards
113-
scenario_config=config,
114-
change_dir=False,
115-
)
116-
=======
117-
priority=1 # Increased priority (default is 2) so that it always runs before replace_plants_in_zone_all.py
118105
)
119-
def main(config):
106+
def post_process(config):
120107
# Drop previous candidate storage from inputs
121108
drop_previous_candidate_storage()
122109

123110
# Get the generation storage plants from Google Sheet
124-
gen_projects = fetch_df("constants", "constant_scenario", config).set_index("param_name").transpose()
125-
gen_projects = cross_join(gen_projects, fetch_df("plants", "plants_scenario", config))
111+
gen_projects = (
112+
fetch_df("constants", "constant_scenario", config)
113+
.set_index("param_name")
114+
.transpose()
115+
)
116+
gen_projects = cross_join(
117+
gen_projects, fetch_df("plants", "plants_scenario", config)
118+
)
126119

127120
# Append the storage plants to the inputs
128-
add_to_csv("generation_projects_info.csv", gen_projects, primary_key="GENERATION_PROJECT")
121+
add_to_csv(
122+
"generation_projects_info.csv", gen_projects, primary_key="GENERATION_PROJECT"
123+
)
129124

130125
# Create min_per_tech.csv
131126
min_projects = fetch_df("minimums", "minimums_scenario", config)
132-
add_to_csv("min_per_tech.csv", min_projects, primary_key=["gen_tech", "period"], append=False)
127+
add_to_csv(
128+
"min_per_tech.csv",
129+
min_projects,
130+
primary_key=["gen_tech", "period"],
131+
append=False,
132+
)
133133

134134
# Get the plant costs from GSheets and append to costs
135135
storage_costs = fetch_df("costs", "costs_scenario", config)
136-
storage_costs = storage_costs[storage_costs["GENERATION_PROJECT"].isin(gen_projects["GENERATION_PROJECT"])]
137-
add_to_csv("gen_build_costs.csv", storage_costs, primary_key=["GENERATION_PROJECT", "build_year"])
136+
storage_costs = storage_costs[
137+
storage_costs["GENERATION_PROJECT"].isin(gen_projects["GENERATION_PROJECT"])
138+
]
139+
add_to_csv(
140+
"gen_build_costs.csv",
141+
storage_costs,
142+
primary_key=["GENERATION_PROJECT", "build_year"],
143+
)
138144

139145
# Create add_storage_info.csv
140146
pd.DataFrame([config]).transpose().to_csv("add_storage_info.csv", header=False)
@@ -144,11 +150,10 @@ def main(config):
144150
gen_type.columns = ["gen_tech", "energy_source"]
145151
gen_type["map_name"] = "default"
146152
gen_type["gen_type"] = "Storage"
147-
pd.concat([
148-
pd.read_csv("graph_tech_types.csv", index_col=False), gen_type
149-
]).to_csv("graph_tech_types.csv", index=False)
153+
pd.concat([pd.read_csv("graph_tech_types.csv", index_col=False), gen_type]).to_csv(
154+
"graph_tech_types.csv", index=False
155+
)
150156

151157

152158
if __name__ == "__main__":
153159
main({})
154-
>>>>>>> 4c70d285 (Refactor add_storage package into post process step)

switch_model/wecc/get_inputs/post_process_steps/aggregate_candidate_projects.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,11 @@
2121

2222

2323
@register_post_process(
24-
name="aggregate_projects_by_zone",
2524
msg="Aggregating candidate projects by load zone for specified technologies",
26-
only_with_config=True,
27-
priority=4,
2825
)
29-
def post_process(config):
30-
agg_techs = config["agg_techs"]
31-
cf_method = config["cf_method"]
26+
def post_process(config, func_config):
27+
agg_techs = func_config["agg_techs"]
28+
cf_method = func_config["cf_method"]
3229
assert type(agg_techs) == list
3330
# Don't allow hydro to be aggregated since we haven't implemented how to handle
3431
# hydro_timeseries.csv

switch_model/wecc/get_inputs/post_process_steps/create_graph_files.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
from switch_model.wecc.get_inputs.register_post_process import register_post_process
44

55

6-
@register_post_process(name="create_graph_files", msg="Creating graph files")
7-
def create_graph_files(_):
6+
@register_post_process(msg="Creating graph files")
7+
def post_process(config, *args, **kwargs):
88
timepoints = pd.read_csv("timepoints.csv", index_col=False)
99
timeseries = pd.read_csv("timeseries.csv", index_col=False)
1010
timepoints = timepoints.merge(
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Standard packages
2+
import os
3+
import shutil
4+
5+
# Third-party packages
6+
import pandas as pd
7+
8+
from switch_model.wecc.get_inputs.register_post_process import register_post_process
9+
10+
11+
@register_post_process(
12+
msg="Change energy cost for storage candidate",
13+
)
14+
def post_process(config, func_config):
15+
16+
percentage = int(func_config["percentage"]) / 100
17+
dtype = {"GENERATION_PROJECT": str}
18+
df = pd.read_csv("generation_projects_info.csv", dtype=dtype)
19+
costs = pd.read_csv("gen_build_costs.csv", dtype=dtype)
20+
predetermined = pd.read_csv("gen_build_predetermined.csv", dtype=dtype)
21+
22+
gen_projects = df.merge(
23+
costs,
24+
on="GENERATION_PROJECT",
25+
)
26+
27+
gen_projects = gen_projects.merge(
28+
predetermined,
29+
on=["GENERATION_PROJECT", "build_year"],
30+
how="left", # Makes a left join
31+
)
32+
33+
# Get candiate technology only
34+
candidate = gen_projects.query("build_year == 2050").query(
35+
"gen_tech =='Battery_Storage'"
36+
)
37+
38+
# Get canidate generation project id
39+
candidate_ids = candidate["GENERATION_PROJECT"].values
40+
41+
gen_cost_mwh = costs.loc[
42+
costs["GENERATION_PROJECT"].isin(candidate_ids),
43+
"gen_storage_energy_overnight_cost",
44+
].astype(float)
45+
46+
# Set to zero column that allows technology to provide reserves
47+
costs.loc[
48+
costs["GENERATION_PROJECT"].isin(candidate_ids),
49+
"gen_storage_energy_overnight_cost",
50+
] = (
51+
gen_cost_mwh * percentage
52+
)
53+
54+
# Save file again
55+
costs.to_csv("gen_build_costs.csv", index=False)

switch_model/wecc/get_inputs/post_process_steps/fix_prebuild_conflict.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,8 @@
33
from switch_model.wecc.get_inputs.register_post_process import register_post_process
44

55

6-
@register_post_process(
7-
name="fix_prebuild_conflict_bug", msg="Shifting 2020 pre-build years to 2019"
8-
)
9-
def fix_prebuild_conflict_bug(_):
6+
@register_post_process(msg="Shifting 2020 pre-build years to 2019")
7+
def post_process(config, *args, **kwargs):
108
"""
119
This post-processing step is necessary to pass the no_predetermined_bld_yr_vs_period_conflict BuildCheck.
1210
Basically we are moving all the 2020 predetermined build years to 2019 to avoid a conflict with the 2020 period.

switch_model/wecc/get_inputs/post_process_steps/only_california.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
@register_post_process(
88
name="only_california",
99
msg="Dropping all the zones outside of California",
10-
only_with_config=True,
1110
priority=3,
1211
)
1312
def main(_):

switch_model/wecc/get_inputs/post_process_steps/replace_plants_in_zone_all.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,8 @@
33
from switch_model.wecc.get_inputs.register_post_process import register_post_process
44

55

6-
@register_post_process(
7-
name="replace_all_zones_plants",
8-
msg="Replacing _ALL_ZONES plants with a plant in each zone",
9-
)
10-
def replace_plants_in_zone_all(_):
6+
@register_post_process(msg="Replacing _ALL_ZONES plants with a plant in each zone")
7+
def post_process(config, *args, **kwargs):
118
"""
129
This post-process step replaces all the generation projects that have a load called
1310
_ALL_ZONES with a generation project for each load zone.

0 commit comments

Comments
 (0)