Skip to content

Commit 9fb9c4d

Browse files
pesapstaadecker
authored andcommitted
Merge pull request #105 from staadecker/aggregate_projects
Add get_inputs post-process step that aggregates similar plants within the same zone
2 parents 7942ff7 + 0c15238 commit 9fb9c4d

File tree

13 files changed

+537
-130
lines changed

13 files changed

+537
-130
lines changed

switch_model/__main__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def runner():
3737
"solve-scenarios": get_module_runner("switch_model.solve_scenarios"),
3838
"test": get_module_runner("switch_model.test"),
3939
"upgrade": get_module_runner("switch_model.upgrade"),
40-
"get_inputs": get_module_runner("switch_model.wecc.get_inputs"),
40+
"get_inputs": get_module_runner("switch_model.wecc.get_inputs.cli"),
4141
"drop": get_module_runner("switch_model.tools.drop"),
4242
"new": get_module_runner("switch_model.tools.new"),
4343
"graph": get_module_runner("switch_model.tools.graph.cli_graph"),

switch_model/tools/drop.py

Lines changed: 43 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import os
2+
import warnings
3+
24
import pandas
35
from switch_model.utilities import query_yes_no
46
from argparse import ArgumentParser, RawTextHelpFormatter
@@ -64,7 +66,11 @@
6466
),
6567
"timepoints": (
6668
("timepoints.csv", "timepoint_id"),
67-
[("loads.csv", "TIMEPOINT"), ("variable_capacity_factors.csv", "timepoint")],
69+
[
70+
("loads.csv", "TIMEPOINT"),
71+
("variable_capacity_factors.csv", "timepoint"),
72+
("hydro_timepoints.csv", "timepoint_id"),
73+
],
6874
),
6975
"projects": (
7076
("generation_projects_info.csv", "GENERATION_PROJECT"),
@@ -99,6 +105,15 @@ def main(args=None):
99105
default="inputs",
100106
help='Directory of the input files. Defaults to "inputs".',
101107
)
108+
parser.add_argument(
109+
"--silent", default=False, action="store_true", help="Suppress output"
110+
)
111+
parser.add_argument(
112+
"--no-confirm",
113+
default=False,
114+
action="store_true",
115+
help="Skip confirmation prompts",
116+
)
102117
args = parser.parse_args(args)
103118

104119
if not args.run:
@@ -108,7 +123,7 @@ def main(args=None):
108123
if not os.path.isdir(args.inputs_dir):
109124
raise NotADirectoryError("{} is not a directory".format(args.inputs_dir))
110125

111-
should_continue = query_yes_no(
126+
should_continue = args.no_confirm or query_yes_no(
112127
"WARNING: This will permanently delete data from directory '{}' "
113128
"WITHOUT backing it up. Are you sure you want to continue?".format(
114129
args.inputs_dir
@@ -126,34 +141,40 @@ def main(args=None):
126141
warn_about_periods = False
127142
pass_count = 0
128143
while pass_count == 0 or rows_removed_in_pass != 0:
129-
print("Pass {}...".format(pass_count), flush=True)
144+
if not args.silent:
145+
print("Pass {}...".format(pass_count), flush=True)
130146
rows_removed_in_pass = 0
131147
for name, data_type in data_types.items():
132-
print("Checking '{}'...".format(name), flush=True)
148+
if not args.silent:
149+
print("Checking '{}'...".format(name), flush=True)
133150
rows_removed = drop_data(data_type, args)
134151
rows_removed_in_pass += rows_removed
135152

136153
if name == "periods" and rows_removed != 0:
137154
warn_about_periods = True
138-
print("Removed {} rows during pass.".format(rows_removed_in_pass))
155+
if not args.silent:
156+
print("Removed {} rows during pass.".format(rows_removed_in_pass))
139157

140158
total_rows_removed += rows_removed_in_pass
141159
pass_count += 1
142160

143-
print(
144-
"\n\nRemove {} rows in total from the input files.".format(total_rows_removed)
145-
)
146-
print(
147-
"\n\nNote: If SWITCH fails to load the model when solving it is possible that some input files were missed."
148-
" If this is the case, please add the missing input files to 'data_types' in 'switch_model/tools/drop.py'."
149-
)
161+
if not args.silent:
162+
print(
163+
"\n\nRemove {} rows in total from the input files.".format(
164+
total_rows_removed
165+
)
166+
)
167+
print(
168+
"\n\nNote: If SWITCH fails to load the model when solving it is possible that some input files were missed."
169+
" If this is the case, please add the missing input files to 'data_types' in 'switch_model/tools/drop.py'."
170+
)
150171

151172
# It is impossible to know if a row in gen_build_costs.csv is for predetermined generation or for
152173
# a period that was removed. So instead we don't touch it and let the user manually edit
153174
# the input file.
154175
if warn_about_periods:
155-
print(
156-
"\n\nWARNING: Could not update gen_build_costs.csv. Please manually edit gen_build_costs.csv to remove "
176+
warnings.warn(
177+
"\n\nCould not update gen_build_costs.csv. Please manually edit gen_build_costs.csv to remove "
157178
"references to the removed periods."
158179
)
159180

@@ -179,7 +200,7 @@ def get_valid_ids(primary_file, args):
179200
print("\n Warning: {} was not found.".format(filename))
180201
return None
181202

182-
valid_ids = pandas.read_csv(path)[primary_key]
203+
valid_ids = pandas.read_csv(path, dtype=str)[primary_key]
183204
return valid_ids
184205

185206

@@ -189,17 +210,21 @@ def drop_from_file(filename, foreign_key, valid_ids, args):
189210
if not os.path.exists(path):
190211
return 0
191212

192-
df = pandas.read_csv(path)
213+
df = pandas.read_csv(path, dtype=str)
193214
count = len(df)
215+
if foreign_key not in df.columns:
216+
raise Exception(f"Column {foreign_key} not in file {filename}")
194217
df = df[df[foreign_key].isin(valid_ids)]
195218
rows_removed = count - len(df)
196219

197220
if rows_removed != 0:
198221
df.to_csv(path, index=False)
199222

200-
print("Removed {} rows {}.".format(rows_removed, filename))
223+
if not args.silent:
224+
print("Removed {} rows {}.".format(rows_removed, filename))
201225
if rows_removed == count:
202-
print("WARNING: {} is now empty.".format(filename))
226+
if not args.silent:
227+
print("WARNING: {} is now empty.".format(filename))
203228

204229
return rows_removed
205230

switch_model/tools/graphing.md

Whitespace-only changes.

switch_model/tools/templates/config.yaml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,13 @@ get_inputs:
4747
# costs_scenario: 0
4848
# plants_scenario: 0
4949
# constant_scenario: 0
50-
# minimums_scenario: 0
50+
# minimums_scenario: 0
51+
# When the following line is uncommented (regardless of its value) then only California load zones are kept
52+
# only_california: 0
53+
# When the following lines are uncommented all the Central_PV and Wind projects within the same load zone gets
54+
# aggregated into a single project. This helps reduce the model complexity.
55+
# cf_quantile is the percentile for the capacity factor to use. 1 will use the largest capacity factor
56+
# of all the available candidate plants, 0.5 will use the median plant and 0 will use the worst plant.
57+
# aggregate_projects_by_zone:
58+
# agg_techs: ["Central_PV"]
59+
# cf_method: "file" # Other options are "weighted_mean" and "95_quantile"
Lines changed: 0 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +0,0 @@
1-
""" Script to retrieve the input data from the switch-wecc database and apply post-processing steps.
2-
"""
3-
import argparse
4-
import os
5-
6-
from switch_model.utilities import query_yes_no, StepTimer
7-
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
9-
from switch_model.wecc.utilities import load_config
10-
from switch_model.wecc.get_inputs.post_process_steps import *
11-
12-
13-
def main():
14-
timer = StepTimer()
15-
16-
# Create command line tool, just provides help information
17-
parser = argparse.ArgumentParser(
18-
description="Write SWITCH input files from database tables.",
19-
epilog="""
20-
This tool will populate the inputs folder with the data from the PostgreSQL database.
21-
config.yaml specifies the scenario parameters.
22-
The environment variable DB_URL specifies the url to connect to the database. """,
23-
)
24-
parser.add_argument(
25-
"--skip-cf",
26-
default=False,
27-
action="store_true",
28-
help="Skip creation variable_capacity_factors.csv. Useful when debugging and one doesn't"
29-
"want to wait for the command.",
30-
)
31-
parser.add_argument(
32-
"--post-only",
33-
default=False,
34-
action="store_true",
35-
help="Only run the post solve functions (don't query db)",
36-
)
37-
parser.add_argument(
38-
"--overwrite",
39-
default=False,
40-
action="store_true",
41-
help="Overwrite previous input files without prompting to confirm.",
42-
)
43-
args = parser.parse_args() # Makes switch get_inputs --help works
44-
45-
# Load values from config.yaml
46-
full_config = load_config()
47-
switch_to_input_dir(full_config, overwrite=args.overwrite)
48-
49-
if not args.post_only:
50-
query_db(full_config, skip_cf=args.skip_cf)
51-
run_post_process()
52-
print(f"\nScript took {timer.step_time_as_str()} seconds to build input tables.")
53-
54-
55-
def switch_to_input_dir(config, overwrite):
56-
inputs_dir = config["inputs_dir"]
57-
58-
# Create inputs_dir if it doesn't exist
59-
if not os.path.exists(inputs_dir):
60-
os.makedirs(inputs_dir)
61-
print("Inputs directory created.")
62-
else:
63-
if not overwrite and not query_yes_no(
64-
"Inputs directory already exists. Allow contents to be overwritten?"
65-
):
66-
raise SystemExit("User cancelled run.")
67-
68-
os.chdir(inputs_dir)
69-
return inputs_dir
70-
71-
72-
if __name__ == "__main__":
73-
main()
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
""" Script to retrieve the input data from the switch-wecc database and apply post-processing steps.
2+
"""
3+
import argparse
4+
import os
5+
6+
from switch_model.utilities import query_yes_no, StepTimer
7+
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
9+
from switch_model.wecc.utilities import load_config
10+
from switch_model.wecc.get_inputs.post_process_steps import *
11+
12+
13+
def main():
14+
timer = StepTimer()
15+
16+
# Create command line tool, just provides help information
17+
parser = argparse.ArgumentParser(
18+
description="Write SWITCH input files from database tables.",
19+
epilog="""
20+
This tool will populate the inputs folder with the data from the PostgreSQL database.
21+
config.yaml specifies the scenario parameters.
22+
The environment variable DB_URL specifies the url to connect to the database. """,
23+
)
24+
parser.add_argument(
25+
"--skip-cf",
26+
default=False,
27+
action="store_true",
28+
help="Skip creation variable_capacity_factors.csv. Useful when debugging and one doesn't"
29+
"want to wait for the command.",
30+
)
31+
parser.add_argument(
32+
"--post-process", default=None, help="Run only this post process step."
33+
)
34+
parser.add_argument(
35+
"--overwrite",
36+
default=False,
37+
action="store_true",
38+
help="Overwrite previous input files without prompting to confirm.",
39+
)
40+
args = parser.parse_args() # Makes switch get_inputs --help works
41+
42+
# Load values from config.yaml
43+
full_config = load_config()
44+
switch_to_input_dir(full_config, overwrite=args.overwrite)
45+
46+
if args.post_process is None:
47+
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.")
51+
52+
53+
def switch_to_input_dir(config, overwrite):
54+
inputs_dir = config["inputs_dir"]
55+
56+
# Create inputs_dir if it doesn't exist
57+
if not os.path.exists(inputs_dir):
58+
os.makedirs(inputs_dir)
59+
print("Inputs directory created.")
60+
else:
61+
if not overwrite and not query_yes_no(
62+
"Inputs directory already exists. Allow contents to be overwritten?"
63+
):
64+
raise SystemExit("User cancelled run.")
65+
66+
os.chdir(inputs_dir)
67+
return inputs_dir
68+
69+
70+
if __name__ == "__main__":
71+
main()

switch_model/wecc/get_inputs/post_process_steps/add_storage.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ def fetch_df(tab_name, key, config):
2525
url = f"https://docs.google.com/spreadsheet/ccc?key={SHEET_ID}&output=csv&gid={gid}"
2626

2727
df: pd.DataFrame = pd.read_csv(url, index_col=False) \
28-
.replace("FALSE", 0) \
29-
.replace("TRUE", 1)
28+
.replace("FALSE", False) \
29+
.replace("TRUE", True)
3030

3131
if "description" in df.columns:
3232
df = df.drop("description", axis=1)
@@ -87,8 +87,6 @@ def drop_previous_candidate_storage():
8787
should_drop = (gen["gen_tech"] == STORAGE_TECH) & ~gen["GENERATION_PROJECT"].isin(predetermined_gen)
8888
# Find projects that we should drop (candidate storage)
8989
gen_to_drop = gen[should_drop]["GENERATION_PROJECT"]
90-
# Verify we're dropping the right amount
91-
assert len(gen_to_drop) == 50 # 50 is the number of load zones. we expect one candidate per load zone
9290

9391
# Drop and write output
9492
gen = gen[~should_drop]

0 commit comments

Comments
 (0)