Skip to content

Commit 68d535f

Browse files
committed
Merge remote-tracking branch 'rael/wecc' into plots
2 parents 95ffc64 + 9776d23 commit 68d535f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1844
-1073
lines changed

README.md

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,43 @@
33
Welcome! This repository contains the SWITCH electricity planning model adapted for the
44
REAM research lab.
55

6-
For **an overview** of what SWITCH is and how it works, read [`docs/Overview.md`](./docs/Overiew.md).
6+
## Available documentation
77

8-
To discover **how to install, run or debug** the different components of Switch, read [`docs/Usage.md`](./docs/Usage.md)
8+
In `docs/`:
99

10-
To **see examples** of smaller SWITCH models, see the `examples/` folder.
10+
- [`Overview.md`](./docs/Overiew.md): An overview of what SWITCH is and how it works, read
1111

12-
To discover **how to contribute to the model**, read [`docs/Contribute.md`](/docs/Contribute.md)
12+
- [`Usage.md`](./docs/Usage.md): How to install, run or debug the different components of Switch
1313

14-
To **learn about our database** (e.g. how to connect to it and modify it), read [`docs/Database.md`](/docs/Database.md).
14+
- [`Developing Modules.md`](./docs/Developing%20Modules.md): How to create SWITCH modules from scratch
1515

16-
To **generate documentation**, run `pydoc -w switch_model` after having installed
16+
- [`Contribute.md`](/docs/Contribute.md): How to contribute code to the project.
17+
18+
- [`Graphs`](/docs/Graphs.md): How to create new graphs to analyze your results.
19+
20+
- [`Database.md`](/docs/Database.md): All about the REAM database (e.g. how to connect to it and modify it)
21+
22+
- [`Numerical Solvers.md`](/docs/Numerical%20Solvers.md): Information about numerical solvers, specifically Gurobi.
23+
24+
- [`Numerical Issues.md`](/docs/Numerical%20Issues.md): Information about detecting and resolving numerical issues.
25+
26+
- [`Pandas.md`](/docs/Pandas.md): Crash course on the Pandas data manipulation library.
27+
28+
Finally, you can generate documentation for the SWITCH modules by running `pydoc -w switch_model` after having installed
1729
SWITCH. This will build HTML documentation files from python doc strings which
1830
will include descriptions of each module, their intentions, model
1931
components they define, and what input files they expect.
2032

21-
To learn about **numerical solvers** read [`docs/Numerical Solvers.md`](/docs/Numerical%20Solvers.md)
33+
## Key folders
34+
35+
- [`/database`](/database) Folder containing SQL scripts and files that keep track of updates to our PostgreSQL database.
36+
37+
- [`/examples`](/examples) Folder with numerous examples of SWITCH projects often used for testing.
38+
39+
- [`/switch_model`](/switch_model) Folder containing all the source code for the SWITCH modules.
40+
41+
- [`/switch_model/wecc`](/switch_model/wecc) Folder containing modules specific to the REAM team.
42+
43+
- [`/switch_model/wecc/get_inputs`](/switch_model/wecc/get_inputs) Scripts that fetch the input data from the PostgreSQL database.
2244

23-
To learn about **numerical issues** read [`docs/Numerical Issues.md`](/docs/Numerical%20Issues.md)
45+
- [`/tests`](/tests) Folder containing tests for SWITCH. Can be run via `python run_tests.py`.

REAM Model Changelog.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ Changes are listed from oldest (first line) to newest (last line of table).
1616
| #56 | June 2021 | Convert 2020 predetermined build years to 2019 in `get_inputs.py` to avoid conflicts with 2020 period. |
1717
| #57 | June 2021 | Specify predetermined storage energy capacity in inputs (previously left unspecified). |
1818
| #68 | June 2021 | Change financial params to 2018 dollars & 5% interest rate. Start using terrain multipliers (which now include the economic multiplier). |
19-
| #72 | July 2021 | Drop build and O&M costs of existing transmission lines. |
19+
| #72 | July 2021 | Drop build and O&M costs of existing transmission lines. |
20+
| #89 | August 2021 | Change hydro module average flow constraint to a monthly constraint rather than per timeseries and change it to a <= rather than ==. |
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
"""
2+
####################
3+
Create a low hydro scenario
4+
5+
Date applied: 2021-07-29
6+
Description:
7+
This script adds a scenario to the database for low hydro power.
8+
The worst year for hydro is 2015. As such we use those values for every year unless a plant is missing
9+
in 2015 in which case we use the lowest value in the other years for that plant.
10+
#################
11+
"""
12+
import time
13+
14+
from switch_model.utilities import query_yes_no, format_seconds
15+
from switch_model.wecc.utilities import connect
16+
import pandas as pd
17+
18+
raw_data_scenario = 21
19+
all_plants_scenario = 23
20+
worst_year = 2015
21+
22+
new_start_year = 2020
23+
new_end_year = 2050
24+
new_scenario_id = 24
25+
new_scenario_name = "Lowest year (2015) repeated. Using EIA and AMPL Canada and Mex data."
26+
new_scenario_description = "Lowest year (2015) repeated from 2020 to 2050, based on data from id 21 (EIA + AMPL Canada & Mex)."
27+
28+
29+
def main():
30+
db_conn = connect()
31+
db_cursor = db_conn.cursor()
32+
33+
# 1. Get all the hydro plants
34+
db_cursor.execute(
35+
f"""
36+
SELECT DISTINCT generation_plant_id FROM hydro_historical_monthly_capacity_factors
37+
WHERE hydro_simple_scenario_id={all_plants_scenario};
38+
""")
39+
hydro_plants = pd.DataFrame(db_cursor.fetchall(), columns=["generation_plant_id"])["generation_plant_id"]
40+
41+
# 2. Get all the hydro flow data for the worst year
42+
db_cursor.execute(
43+
f"""
44+
SELECT generation_plant_id, month, hydro_min_flow_mw, hydro_avg_flow_mw FROM hydro_historical_monthly_capacity_factors
45+
WHERE hydro_simple_scenario_id={raw_data_scenario} and year={worst_year};
46+
""")
47+
worst_year_data = pd.DataFrame(db_cursor.fetchall(),
48+
columns=["generation_plant_id", "month", "hydro_min_flow_mw", "hydro_avg_flow_mw"])
49+
50+
# 3. Identify plants where data is missing
51+
missing_hydro_plants = hydro_plants[~hydro_plants.isin(worst_year_data["generation_plant_id"])].values
52+
53+
# 4. For each missing plant get the data for all the years
54+
db_cursor.execute(
55+
f"""
56+
SELECT generation_plant_id, year, month, hydro_min_flow_mw, hydro_avg_flow_mw FROM hydro_historical_monthly_capacity_factors
57+
WHERE hydro_simple_scenario_id={raw_data_scenario} and generation_plant_id in ({",".join(missing_hydro_plants.astype(str))});
58+
""")
59+
missing_plants_data = pd.DataFrame(db_cursor.fetchall(),
60+
columns=["generation_plant_id", "year", "month", "hydro_min_flow_mw",
61+
"hydro_avg_flow_mw"])
62+
63+
# 5. Pick the year with the least flow
64+
# Aggregate by year
65+
missing_data_by_year = missing_plants_data.groupby(["generation_plant_id", "year"], as_index=False)[
66+
"hydro_avg_flow_mw"].mean()
67+
# Select years where the flow is at its lowest
68+
year_to_use = \
69+
missing_data_by_year.loc[missing_data_by_year.groupby("generation_plant_id")["hydro_avg_flow_mw"].idxmin()][
70+
["generation_plant_id", "year"]]
71+
# Essentially filter missing_plants_data to only include keys from the right table, aka plants and years that are lowest
72+
missing_plants_data = missing_plants_data.merge(
73+
year_to_use,
74+
on=["generation_plant_id", "year"],
75+
how="right"
76+
).drop("year", axis=1)
77+
78+
# 6. Add the missing data to our worst year data and verify we have data for all the plants
79+
worst_year_data = pd.concat([worst_year_data, missing_plants_data])
80+
assert all(hydro_plants.isin(worst_year_data["generation_plant_id"]))
81+
82+
# 7. Cross join the series with all the years from 2020 to 2050
83+
years = pd.Series(range(new_start_year, new_end_year + 1), name="year")
84+
worst_year_data = worst_year_data.merge(
85+
years,
86+
how="cross"
87+
)
88+
worst_year_data["hydro_simple_scenario_id"] = new_scenario_id
89+
90+
# 8. Complete some data checks
91+
assert len(worst_year_data) == 12 * (new_end_year - new_start_year + 1) * len(hydro_plants)
92+
93+
# 9. Add data to database
94+
print(f"hydro_simple_scenario: {new_scenario_id}")
95+
print(f"name: {new_scenario_name}")
96+
print(f"description: {new_scenario_description}")
97+
print(f"Num hydro plants: {worst_year_data.generation_plant_id.nunique()}")
98+
print(f"From year: {new_start_year}")
99+
print(f"To year: {new_end_year}")
100+
print(f"Example data:\n{worst_year_data.head()}")
101+
102+
if not query_yes_no("\nAre you sure you want to add this data to the database?", default="no"):
103+
raise SystemExit
104+
105+
db_cursor.execute(
106+
"INSERT INTO hydro_simple_scenario(hydro_simple_scenario_id, name, description) "
107+
f"VALUES ('{new_scenario_id}','{new_scenario_name}','{new_scenario_description}')"
108+
)
109+
110+
n = len(worst_year_data)
111+
start_time = time.time()
112+
for i, r in enumerate(worst_year_data.itertuples(index=False)):
113+
if i !=0 and i % 1000 == 0:
114+
print(
115+
f"{i}/{n} inserts completed. Estimated time remaining {format_seconds((n - i) * (time.time() - start_time) / i)}")
116+
db_cursor.execute(
117+
f"INSERT INTO hydro_historical_monthly_capacity_factors(hydro_simple_scenario_id, generation_plant_id, year, month, hydro_min_flow_mw, hydro_avg_flow_mw) "
118+
f"VALUES ({r.hydro_simple_scenario_id},{r.generation_plant_id},{r.year},{r.month},{r.hydro_min_flow_mw},{r.hydro_avg_flow_mw})"
119+
)
120+
121+
db_conn.commit()
122+
db_cursor.close()
123+
db_conn.close()
124+
print("Done.")
125+
126+
127+
if __name__ == "__main__":
128+
main()
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
"""
2+
####################
3+
Create a no hydro scenario
4+
5+
Date applied: 2021-07-30
6+
Description:
7+
This script adds a scenario to the database that effectively removes all hydro generation from the model.
8+
#################
9+
"""
10+
import time
11+
12+
from switch_model.utilities import query_yes_no, format_seconds
13+
from switch_model.wecc.utilities import connect
14+
import pandas as pd
15+
16+
all_plants_scenario = 23
17+
18+
new_scenario_id = 25
19+
new_scenario_name = "No Hydro"
20+
new_scenario_description = "All average flows are zero effectively removing all hydro generation from the model." \
21+
" Represents as an extreme edge case of no hydro generation."
22+
23+
24+
def main():
25+
db_conn = connect()
26+
db_cursor = db_conn.cursor()
27+
28+
# 1. Get all the hydro plants
29+
db_cursor.execute(
30+
f"""
31+
SELECT DISTINCT generation_plant_id, year, month, hydro_min_flow_mw, hydro_avg_flow_mw FROM hydro_historical_monthly_capacity_factors
32+
WHERE hydro_simple_scenario_id={all_plants_scenario};
33+
""")
34+
df = pd.DataFrame(db_cursor.fetchall(),
35+
columns=["generation_plant_id", "year", "month", "hydro_min_flow_mw", "hydro_avg_flow_mw"])
36+
37+
# 2. Set all the flows to zero and set the scenario id
38+
df["hydro_min_flow_mw"] = 0
39+
df["hydro_avg_flow_mw"] = 0
40+
df["hydro_simple_scenario_id"] = new_scenario_id
41+
42+
# 3. Add data to database
43+
print(f"hydro_simple_scenario: {new_scenario_id}")
44+
print(f"name: {new_scenario_name}")
45+
print(f"description: {new_scenario_description}")
46+
print(f"Num hydro plants: {df.generation_plant_id.nunique()}")
47+
print(f"Example data:\n{df.head()}")
48+
49+
if not query_yes_no("\nAre you sure you want to add this data to the database?", default="no"):
50+
raise SystemExit
51+
52+
db_cursor.execute(
53+
"INSERT INTO hydro_simple_scenario(hydro_simple_scenario_id, name, description) "
54+
f"VALUES ('{new_scenario_id}','{new_scenario_name}','{new_scenario_description}')"
55+
)
56+
57+
n = len(df)
58+
start_time = time.time()
59+
for i, r in enumerate(df.itertuples(index=False)):
60+
if i != 0 and i % 1000 == 0:
61+
print(
62+
f"{i}/{n} inserts completed. Estimated time remaining {format_seconds((n - i) * (time.time() - start_time) / i)}")
63+
db_cursor.execute(
64+
f"INSERT INTO hydro_historical_monthly_capacity_factors(hydro_simple_scenario_id, generation_plant_id, year, month, hydro_min_flow_mw, hydro_avg_flow_mw) "
65+
f"VALUES ({r.hydro_simple_scenario_id},{r.generation_plant_id},{r.year},{r.month},{r.hydro_min_flow_mw},{r.hydro_avg_flow_mw})"
66+
)
67+
68+
db_conn.commit()
69+
db_cursor.close()
70+
db_conn.close()
71+
print("Done.")
72+
73+
74+
if __name__ == "__main__":
75+
main()
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
"""
2+
####################
3+
Create a hydro scenario with only half the flow of the baseline
4+
5+
Date applied: 2021-08-02
6+
Description:
7+
This script adds a scenario to the database that makes all hydro flows half of the expected hydro flow.
8+
#################
9+
"""
10+
import time
11+
12+
from switch_model.utilities import query_yes_no, format_seconds
13+
from switch_model.wecc.utilities import connect
14+
import pandas as pd
15+
16+
all_plants_scenario = 23
17+
18+
new_scenario_id = 26
19+
new_scenario_name = "50% of scenario 23"
20+
new_scenario_description = "All average flows are halved to represent a scenario where hydro generation is low."
21+
22+
23+
def main():
24+
db_conn = connect()
25+
db_cursor = db_conn.cursor()
26+
27+
# 1. Get all the hydro plants
28+
db_cursor.execute(
29+
f"""
30+
SELECT DISTINCT generation_plant_id, year, month, hydro_min_flow_mw, hydro_avg_flow_mw FROM hydro_historical_monthly_capacity_factors
31+
WHERE hydro_simple_scenario_id={all_plants_scenario};
32+
""")
33+
df = pd.DataFrame(db_cursor.fetchall(),
34+
columns=["generation_plant_id", "year", "month", "hydro_min_flow_mw", "hydro_avg_flow_mw"])
35+
36+
# 2. Set all the flows to zero and set the scenario id
37+
df["hydro_avg_flow_mw"] /= 2
38+
df["hydro_min_flow_mw"] = df[["hydro_min_flow_mw", "hydro_avg_flow_mw"]].min(axis=1)
39+
df["hydro_simple_scenario_id"] = new_scenario_id
40+
41+
# 3. Add data to database
42+
print(f"hydro_simple_scenario: {new_scenario_id}")
43+
print(f"name: {new_scenario_name}")
44+
print(f"description: {new_scenario_description}")
45+
print(f"Num hydro plants: {df.generation_plant_id.nunique()}")
46+
print(f"Example data:\n{df.head()}")
47+
48+
if not query_yes_no("\nAre you sure you want to add this data to the database?", default="no"):
49+
raise SystemExit
50+
51+
db_cursor.execute(
52+
"INSERT INTO hydro_simple_scenario(hydro_simple_scenario_id, name, description) "
53+
f"VALUES ('{new_scenario_id}','{new_scenario_name}','{new_scenario_description}')"
54+
)
55+
56+
n = len(df)
57+
start_time = time.time()
58+
for i, r in enumerate(df.itertuples(index=False)):
59+
if i != 0 and i % 1000 == 0:
60+
print(
61+
f"{i}/{n} inserts completed. Estimated time remaining {format_seconds((n - i) * (time.time() - start_time) / i)}")
62+
db_cursor.execute(
63+
f"INSERT INTO hydro_historical_monthly_capacity_factors(hydro_simple_scenario_id, generation_plant_id, year, month, hydro_min_flow_mw, hydro_avg_flow_mw) "
64+
f"VALUES ({r.hydro_simple_scenario_id},{r.generation_plant_id},{r.year},{r.month},{r.hydro_min_flow_mw},{r.hydro_avg_flow_mw})"
65+
)
66+
67+
db_conn.commit()
68+
db_cursor.close()
69+
db_conn.close()
70+
print("Done.")
71+
72+
73+
if __name__ == "__main__":
74+
main()

docs/Contribute.md

Lines changed: 4 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Contributing Code
22

3-
This document describes the best practices for contributing code.
3+
You've made changes to the codebase and now you want to share them
4+
with the rest of the team! Here are the best practices for the process.
45

56
## The process
67

@@ -39,43 +40,6 @@ supposed to alter the results of the examples, you'll need
3940
to follow the instructions that appear on screen to suppress the errors
4041
produced by `python run_tests.py`.
4142

42-
## Contributing graphs
43+
## Important notes
4344

44-
Read [`docs/Graphs.md`](./Graphs.md) to see learn to add graphs.
45-
46-
## Modifying the database
47-
48-
Read [`docs/Database.md`](./Database.md) to learn about the database.
49-
50-
## Outputting results
51-
52-
Once the model is solved, the `post_solve()` function in each module is called.
53-
Within the `post_solve()` function you may
54-
55-
- Call `write_table()` to create
56-
a .csv file with data from the solution (see existing modules for examples).
57-
58-
- Call `add_info()` (from `utilities/result_info.py`) to add a line
59-
of information to the `outputs/info.txt` file. `add_info()` can also be added to `graph()`.
60-
61-
### Example
62-
63-
```python
64-
from switch_model.utilities.results_info import add_info
65-
from switch_model.reporting import write_table
66-
import os
67-
...
68-
def post_solve(instance, outdir):
69-
...
70-
# This will add the a line to info.txt in the outputs folder
71-
add_info("Some important value", instance.important_value)
72-
...
73-
# This will create my_table.csv
74-
write_table(
75-
instance,
76-
instance.TIMEPOINTS, # Set that the values function will iterate over
77-
output_file=os.path.join(outdir, "my_table.csv"),
78-
headings=("timepoint", "some value"),
79-
values=lambda m, t: (t, m.some_value[t])
80-
)
81-
```
45+
- If your change is modifying the database, make sure you've read [`Database.md`](./Database.md).

0 commit comments

Comments
 (0)