Skip to content

Commit 8b739d8

Browse files
authored
Merge pull request #105 from staadecker/aggregate_projects
Add get_inputs post-process step that aggregates similar plants within the same zone
2 parents 2a1a7f0 + fb25f4c commit 8b739d8

File tree

13 files changed

+454
-95
lines changed

13 files changed

+454
-95
lines changed

switch_model/__main__.py

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

switch_model/tools/drop.py

Lines changed: 27 additions & 14 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
@@ -71,7 +73,8 @@
7173
('timepoints.csv', 'timepoint_id'),
7274
[
7375
('loads.csv', 'TIMEPOINT'),
74-
('variable_capacity_factors.csv', 'timepoint')
76+
('variable_capacity_factors.csv', 'timepoint'),
77+
('hydro_timepoints.csv', 'timepoint_id')
7578
]
7679
),
7780
"projects": (
@@ -100,6 +103,8 @@ def main(args=None):
100103

101104
parser.add_argument('--run', default=False, action='store_true', help='Drop the data.')
102105
parser.add_argument('--inputs-dir', default='inputs', help='Directory of the input files. Defaults to "inputs".')
106+
parser.add_argument('--silent', default=False, action='store_true', help='Suppress output')
107+
parser.add_argument('--no-confirm', default=False, action='store_true', help="Skip confirmation prompts")
103108
args = parser.parse_args(args)
104109

105110
if not args.run:
@@ -109,7 +114,7 @@ def main(args=None):
109114
if not os.path.isdir(args.inputs_dir):
110115
raise NotADirectoryError("{} is not a directory".format(args.inputs_dir))
111116

112-
should_continue = query_yes_no("WARNING: This will permanently delete data from directory '{}' "
117+
should_continue = args.no_confirm or query_yes_no("WARNING: This will permanently delete data from directory '{}' "
113118
"WITHOUT backing it up. Are you sure you want to continue?".format(args.inputs_dir))
114119

115120
if not should_continue:
@@ -123,30 +128,34 @@ def main(args=None):
123128
warn_about_periods = False
124129
pass_count = 0
125130
while pass_count == 0 or rows_removed_in_pass != 0:
126-
print("Pass {}...".format(pass_count), flush=True)
131+
if not args.silent:
132+
print("Pass {}...".format(pass_count), flush=True)
127133
rows_removed_in_pass = 0
128134
for name, data_type in data_types.items():
129-
print("Checking '{}'...".format(name), flush=True)
135+
if not args.silent:
136+
print("Checking '{}'...".format(name), flush=True)
130137
rows_removed = drop_data(data_type, args)
131138
rows_removed_in_pass += rows_removed
132139

133140
if name == "periods" and rows_removed != 0:
134141
warn_about_periods = True
135-
print("Removed {} rows during pass.".format(rows_removed_in_pass))
142+
if not args.silent:
143+
print("Removed {} rows during pass.".format(rows_removed_in_pass))
136144

137145
total_rows_removed += rows_removed_in_pass
138146
pass_count += 1
139147

140-
print("\n\nRemove {} rows in total from the input files.".format(total_rows_removed))
141-
print("\n\nNote: If SWITCH fails to load the model when solving it is possible that some input files were missed."
142-
" If this is the case, please add the missing input files to 'data_types' in 'switch_model/tools/drop.py'.")
148+
if not args.silent:
149+
print("\n\nRemove {} rows in total from the input files.".format(total_rows_removed))
150+
print("\n\nNote: If SWITCH fails to load the model when solving it is possible that some input files were missed."
151+
" If this is the case, please add the missing input files to 'data_types' in 'switch_model/tools/drop.py'.")
143152

144153
# It is impossible to know if a row in gen_build_costs.csv is for predetermined generation or for
145154
# a period that was removed. So instead we don't touch it and let the user manually edit
146155
# the input file.
147156
if warn_about_periods:
148-
print("\n\nWARNING: Could not update gen_build_costs.csv. Please manually edit gen_build_costs.csv to remove "
149-
"references to the removed periods.")
157+
warnings.warn("\n\nCould not update gen_build_costs.csv. Please manually edit gen_build_costs.csv to remove "
158+
"references to the removed periods.")
150159

151160

152161
def drop_data(id_type, args):
@@ -170,7 +179,7 @@ def get_valid_ids(primary_file, args):
170179
print("\n Warning: {} was not found.".format(filename))
171180
return None
172181

173-
valid_ids = pandas.read_csv(path)[primary_key]
182+
valid_ids = pandas.read_csv(path, dtype=str)[primary_key]
174183
return valid_ids
175184

176185

@@ -180,17 +189,21 @@ def drop_from_file(filename, foreign_key, valid_ids, args):
180189
if not os.path.exists(path):
181190
return 0
182191

183-
df = pandas.read_csv(path)
192+
df = pandas.read_csv(path, dtype=str)
184193
count = len(df)
194+
if foreign_key not in df.columns:
195+
raise Exception(f"Column {foreign_key} not in file {filename}")
185196
df = df[df[foreign_key].isin(valid_ids)]
186197
rows_removed = count - len(df)
187198

188199
if rows_removed != 0:
189200
df.to_csv(path, index=False)
190201

191-
print("Removed {} rows {}.".format(rows_removed, filename))
202+
if not args.silent:
203+
print("Removed {} rows {}.".format(rows_removed, filename))
192204
if rows_removed == count:
193-
print("WARNING: {} is now empty.".format(filename))
205+
if not args.silent:
206+
print("WARNING: {} is now empty.".format(filename))
194207

195208
return rows_removed
196209

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
@@ -48,4 +48,13 @@ get_inputs:
4848
# costs_scenario: 0
4949
# plants_scenario: 0
5050
# constant_scenario: 0
51-
# minimums_scenario: 0
51+
# minimums_scenario: 0
52+
# When the following line is uncommented (regardless of its value) then only California load zones are kept
53+
# only_california: 0
54+
# When the following lines are uncommented all the Central_PV and Wind projects within the same load zone gets
55+
# aggregated into a single project. This helps reduce the model complexity.
56+
# cf_quantile is the percentile for the capacity factor to use. 1 will use the largest capacity factor
57+
# of all the available candidate plants, 0.5 will use the median plant and 0 will use the worst plant.
58+
# aggregate_projects_by_zone:
59+
# agg_techs: ["Central_PV"]
60+
# cf_method: "file" # Other options are "weighted_mean" and "95_quantile"
Lines changed: 0 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +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("--skip-cf", default=False, action='store_true',
25-
help="Skip creation variable_capacity_factors.csv. Useful when debugging and one doesn't"
26-
"want to wait for the command.")
27-
parser.add_argument("--post-only", default=False, action='store_true',
28-
help="Only run the post solve functions (don't query db)")
29-
parser.add_argument("--overwrite", default=False, action='store_true',
30-
help="Overwrite previous input files without prompting to confirm.")
31-
args = parser.parse_args() # Makes switch get_inputs --help works
32-
33-
# Load values from config.yaml
34-
full_config = load_config()
35-
switch_to_input_dir(full_config, overwrite=args.overwrite)
36-
37-
if not args.post_only:
38-
query_db(full_config, skip_cf=args.skip_cf)
39-
run_post_process(full_config)
40-
print(f"\nScript took {timer.step_time_as_str()} seconds to build input tables.")
41-
42-
43-
def switch_to_input_dir(config, overwrite):
44-
inputs_dir = config["inputs_dir"]
45-
46-
# Create inputs_dir if it doesn't exist
47-
if not os.path.exists(inputs_dir):
48-
os.makedirs(inputs_dir)
49-
print("Inputs directory created.")
50-
else:
51-
if not overwrite and not query_yes_no(
52-
"Inputs directory already exists. Allow contents to be overwritten?"
53-
):
54-
raise SystemExit("User cancelled run.")
55-
56-
os.chdir(inputs_dir)
57-
return inputs_dir
58-
59-
60-
if __name__ == "__main__":
61-
main()
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
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("--skip-cf", default=False, action='store_true',
25+
help="Skip creation variable_capacity_factors.csv. Useful when debugging and one doesn't"
26+
"want to wait for the command.")
27+
parser.add_argument("--post-process", default=None, help="Run only this post process step.")
28+
parser.add_argument("--overwrite", default=False, action='store_true',
29+
help="Overwrite previous input files without prompting to confirm.")
30+
args = parser.parse_args() # Makes switch get_inputs --help works
31+
32+
# Load values from config.yaml
33+
full_config = load_config()
34+
switch_to_input_dir(full_config, overwrite=args.overwrite)
35+
36+
if args.post_process is None:
37+
query_db(full_config, skip_cf=args.skip_cf)
38+
print("Post-processing...")
39+
run_post_process(full_config, step_name=args.post_process)
40+
print(f"\nScript took {timer.step_time_as_str()} seconds to build input tables.")
41+
42+
43+
def switch_to_input_dir(config, overwrite):
44+
inputs_dir = config["inputs_dir"]
45+
46+
# Create inputs_dir if it doesn't exist
47+
if not os.path.exists(inputs_dir):
48+
os.makedirs(inputs_dir)
49+
print("Inputs directory created.")
50+
else:
51+
if not overwrite and not query_yes_no(
52+
"Inputs directory already exists. Allow contents to be overwritten?"
53+
):
54+
raise SystemExit("User cancelled run.")
55+
56+
os.chdir(inputs_dir)
57+
return inputs_dir
58+
59+
60+
if __name__ == "__main__":
61+
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)