Skip to content

Commit e2f053f

Browse files
pesapstaadecker
authored andcommitted
Merge pull request #89 from staadecker/improv_hydro
Improve hydro module
2 parents eb09e9b + 14aa764 commit e2f053f

File tree

4 files changed

+116
-54
lines changed

4 files changed

+116
-54
lines changed

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 ==. |

switch_model/generators/extensions/hydro_simple.py

Lines changed: 78 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,26 @@
2323
2424
INPUT FILE INFORMATION
2525
26-
The single file hydro_timeseries.csv needs to contain
27-
entries for each dispatchable hydro project. The set of hydro projects
28-
is derived from this file, and this file should cover all time periods
29-
in which the hydro plant can operate.
30-
31-
Run-of-River hydro projects should not be included in this file; RoR
32-
hydro is treated like any other variable renewable resource, and
33-
expects data in variable_capacity_factors.csv.
34-
35-
hydro_timeseries.csv
36-
hydro_generation_project, timeseries, hydro_min_flow_mw,
37-
hydro_avg_flow_mw
26+
The file hydro_timeseries.csv needs to contain
27+
entries for each dispatchable hydro project. The set of hydro projects
28+
is derived from this file, and this file should cover all time periods
29+
in which the hydro plant can operate.
30+
31+
Run-of-River hydro projects should not be included in this file; RoR
32+
hydro is treated like any other variable renewable resource, and
33+
expects data in variable_capacity_factors.csv.
34+
35+
hydro_timeseries.csv
36+
hydro_generation_project, hydro_timeseries, hydro_min_flow_mw,
37+
hydro_avg_flow_mw
38+
39+
The file hydro_timepoints.csv is an optional mapping of timepoints
40+
to a hydro timeseries. Hydro timeseries are different from the SWITCH
41+
timeseries (timeseries.csv) as this allows hydro constraints to be
42+
specified over a different time period.
43+
44+
hydro_timepoints.csv (optional)
45+
timepoint_id,tp_to_hts
3846
"""
3947
from __future__ import division
4048

@@ -43,6 +51,7 @@
4351
# switch_model.hydro.simple, and the advanced components into
4452
# switch_model.hydro.water_network. That should set a good example
4553
# for other people who want to do other custom handling of hydro.
54+
import os.path
4655

4756
from pyomo.environ import *
4857

@@ -58,66 +67,95 @@
5867

5968
def define_components(mod):
6069
"""
61-
6270
HYDRO_GENS is the set of dispatchable hydro projects. This is a subet
6371
of GENERATION_PROJECTS, and is determined by the inputs file hydro_timeseries.csv.
6472
Members of this set can be called either g, or hydro_g.
6573
66-
HYDRO_GEN_TS is the set of Hydro projects and timeseries for which
74+
HYDRO_TS is the set of hydro timeseries over which the average flow constraint is defined.
75+
These hydro timeseries are different from the timeseries used in the reset of SWITCH
76+
and are defined by the input file hydro_timepoints.csv. If hydro_timepoints.csv doesn't exist,
77+
the default is for the timeseries to be the same as the SWITCH timeseries from timeseries.csv.
78+
Members of this set can be abbreviated as hts.
79+
80+
HYDRO_GEN_TS is the set of Hydro projects and hydro timeseries for which
6781
minimum and average flow are specified. Members of this set can be
68-
abbreviated as (project, timeseries) or (g, ts).
82+
abbreviated as (project, hydro_timeseries) or (g, hts).
6983
7084
HYDRO_GEN_TPS is the set of Hydro projects and available
7185
dispatch points. This is a filtered version of GEN_TPS that
7286
only includes hydro projects.
7387
74-
hydro_min_flow_mw[(g, ts) in HYDRO_GEN_TS] is a parameter that
88+
tp_to_hts[tp in TIMEPOINTS] is a parameter that returns the hydro timeseries
89+
for a given timepoint. It is defined in hydro_timepoints.csv and if unspecified
90+
it defaults to be equal to tp_ts.
91+
92+
hydro_min_flow_mw[(g, hts) in HYDRO_GEN_TS] is a parameter that
7593
determines minimum flow levels, specified in units of MW dispatch.
7694
77-
hydro_avg_flow_mw[(g, ts) in HYDRO_GEN_TS] is a parameter that
95+
hydro_avg_flow_mw[(g, hts) in HYDRO_GEN_TS] is a parameter that
7896
determines average flow levels, specified in units of MW dispatch.
7997
8098
Enforce_Hydro_Min_Flow[(g, t) in HYDRO_GEN_TPS] is a
8199
constraint that enforces minimum flow levels for each timepoint.
82100
83-
Enforce_Hydro_Avg_Flow[(g, ts) in HYDRO_GEN_TS] is a constraint
84-
that enforces average flow levels across each timeseries.
85-
101+
Enforce_Hydro_Avg_Flow[(g, hts) in HYDRO_GEN_TS] is a constraint
102+
that enforces average flow levels across each hydro timeseries.
86103
"""
104+
mod.tp_to_hts = Param(
105+
mod.TIMEPOINTS,
106+
input_file="hydro_timepoints.csv",
107+
default=lambda m, tp: m.tp_ts[tp],
108+
doc="Mapping of timepoints to a hydro series.",
109+
within=Any,
110+
)
111+
112+
mod.HYDRO_TS = Set(
113+
dimen=1,
114+
ordered=False,
115+
initialize=lambda m: set(m.tp_to_hts[tp] for tp in m.TIMEPOINTS),
116+
doc="Set of hydro timeseries as defined in the mapping.",
117+
)
118+
119+
mod.TPS_IN_HTS = Set(
120+
mod.HYDRO_TS,
121+
within=mod.TIMEPOINTS,
122+
ordered=False,
123+
initialize=lambda m, hts: set(t for t in m.TIMEPOINTS if m.tp_to_hts[t] == hts),
124+
doc="Set of timepoints in each hydro timeseries",
125+
)
87126

88127
mod.HYDRO_GEN_TS_RAW = Set(
89128
dimen=2,
90129
input_file="hydro_timeseries.csv",
91130
input_optional=True,
92-
validate=lambda m, g, ts: (g in m.GENERATION_PROJECTS) & (ts in m.TIMESERIES),
131+
validate=lambda m, g, hts: (g in m.GENERATION_PROJECTS) & (hts in m.HYDRO_TS),
93132
)
94133

95134
mod.HYDRO_GENS = Set(
96135
dimen=1,
97136
ordered=False,
98-
initialize=lambda m: set(g for (g, ts) in m.HYDRO_GEN_TS_RAW),
137+
initialize=lambda m: set(g for (g, hts) in m.HYDRO_GEN_TS_RAW),
99138
doc="Dispatchable hydro projects",
100139
)
101-
mod.HYDRO_GEN_TS = Set(
102-
dimen=2,
103-
initialize=lambda m: set(
104-
(g, m.tp_ts[tp]) for g in m.HYDRO_GENS for tp in m.TPS_FOR_GEN[g]
105-
),
106-
)
140+
107141
mod.HYDRO_GEN_TPS = Set(
108142
initialize=mod.GEN_TPS, filter=lambda m, g, t: g in m.HYDRO_GENS
109143
)
110144

145+
mod.HYDRO_GEN_TS = Set(
146+
dimen=2,
147+
initialize=lambda m: set((g, m.tp_to_hts[tp]) for (g, tp) in m.HYDRO_GEN_TPS),
148+
)
149+
111150
# Validate that a timeseries data is specified for every hydro generator /
112151
# timeseries that we need. Extra data points (ex: outside of planning
113152
# horizon or beyond a plant's lifetime) can safely be ignored to make it
114153
# easier to create input files.
115154
mod.have_minimal_hydro_params = BuildCheck(
116-
mod.HYDRO_GEN_TS, rule=lambda m, g, ts: (g, ts) in m.HYDRO_GEN_TS_RAW
155+
mod.HYDRO_GEN_TS, rule=lambda m, g, hts: (g, hts) in m.HYDRO_GEN_TS_RAW
117156
)
118157

119-
# To do: Add validation check that timeseries data are specified for every
120-
# valid timepoint.
158+
# Todo: Add validation check that timeseries data are specified for every valid timepoint.
121159

122160
mod.hydro_min_flow_mw = Param(
123161
mod.HYDRO_GEN_TS_RAW,
@@ -127,16 +165,13 @@ def define_components(mod):
127165
)
128166
mod.Enforce_Hydro_Min_Flow = Constraint(
129167
mod.HYDRO_GEN_TPS,
130-
rule=lambda m, g, t: (
131-
m.DispatchGen[g, t] >= m.hydro_min_flow_mw[g, m.tp_ts[t]]
132-
),
168+
rule=lambda m, g, t: Constraint.Skip
169+
if m.hydro_min_flow_mw[g, m.tp_to_hts[t]] == 0
170+
else m.DispatchGen[g, t] >= m.hydro_min_flow_mw[g, m.tp_to_hts[t]],
133171
)
134172

135173
mod.hydro_avg_flow_mw = Param(
136-
mod.HYDRO_GEN_TS_RAW,
137-
within=NonNegativeReals,
138-
input_file="hydro_timeseries.csv",
139-
default=0.0,
174+
mod.HYDRO_GEN_TS_RAW, within=NonNegativeReals, input_file="hydro_timeseries.csv"
140175
)
141176

142177
# We use a scaling factor to improve the numerical properties
@@ -146,12 +181,11 @@ def define_components(mod):
146181
enforce_hydro_avg_flow_scaling_factor = 1e1
147182
mod.Enforce_Hydro_Avg_Flow = Constraint(
148183
mod.HYDRO_GEN_TS,
149-
rule=lambda m, g, ts: (
150-
enforce_hydro_avg_flow_scaling_factor
151-
* sum(m.DispatchGen[g, t] for t in m.TPS_IN_TS[ts])
152-
/ m.ts_num_tps[ts]
153-
== m.hydro_avg_flow_mw[g, ts] * enforce_hydro_avg_flow_scaling_factor
154-
),
184+
rule=lambda m, g, hts: enforce_hydro_avg_flow_scaling_factor *
185+
# Compute the weighted average of the dispatch
186+
sum(m.DispatchGen[g, t] * m.tp_weight[t] for t in m.TPS_IN_HTS[hts])
187+
/ sum(m.tp_weight[tp] for tp in m.TPS_IN_HTS[hts])
188+
<= m.hydro_avg_flow_mw[g, hts] * enforce_hydro_avg_flow_scaling_factor,
155189
)
156190

157191
mod.min_data_check("hydro_min_flow_mw", "hydro_avg_flow_mw")

switch_model/solve.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ def debug(type, value, tb):
108108
# Patch pyomo if needed, to allow reconstruction of expressions.
109109
# This must be done before the model is constructed.
110110
patch_pyomo()
111+
patch_to_allow_loading(Set)
112+
patch_to_allow_loading(Param)
111113

112114
# Define the model
113115
model = create_model(modules, args=args)

switch_model/wecc/get_inputs/get_inputs.py

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -643,27 +643,52 @@ def query_db(full_config, skip_cf):
643643
# zone + watershed. Eventually, we may rethink this derating, but it is a reasonable
644644
# approximation for a large hydro fleet where plant outages are individual random events.
645645
# Negative flows are replaced by 0.
646+
write_csv_from_query(
647+
db_cursor,
648+
"hydro_timepoints",
649+
["timepoint_id", "tp_to_hts"],
650+
f"""
651+
SELECT
652+
tp.raw_timepoint_id AS timepoint_id,
653+
p.label || '_M' || date_part('month', timestamp_utc) AS tp_to_hts
654+
FROM switch.sampled_timepoint AS tp
655+
JOIN switch.period AS p USING(period_id, study_timeframe_id)
656+
WHERE time_sample_id = {time_sample_id}
657+
AND study_timeframe_id = {study_timeframe_id}
658+
ORDER BY 1;
659+
"""
660+
)
661+
646662
write_csv_from_query(
647663
db_cursor,
648664
"hydro_timeseries",
649665
["hydro_project", "timeseries", "hydro_min_flow_mw", "hydro_avg_flow_mw"],
650666
f"""
651-
select generation_plant_id as hydro_project,
652-
{timeseries_id_select},
667+
SELECT
668+
generation_plant_id AS hydro_project,
669+
hts.hydro_timeseries,
653670
CASE
654671
WHEN hydro_min_flow_mw <= 0 THEN 0
655672
ELSE least(hydro_min_flow_mw, capacity_limit_mw * (1-forced_outage_rate)) END,
656673
CASE
657674
WHEN hydro_avg_flow_mw <= 0 THEN 0
658675
ELSE least(hydro_avg_flow_mw, capacity_limit_mw * (1-forced_outage_rate)) END
659-
as hydro_avg_flow_mw
660-
from hydro_historical_monthly_capacity_factors
661-
join sampled_timeseries on(month = date_part('month', first_timepoint_utc) and year = date_part('year', first_timepoint_utc))
662-
join generation_plant using (generation_plant_id)
676+
AS hydro_avg_flow_mw
677+
FROM (
678+
SELECT DISTINCT
679+
date_part('month', tp.timestamp_utc) as month,
680+
date_part('year', tp.timestamp_utc) as year,
681+
p.label || '_M' || date_part('month', timestamp_utc) AS hydro_timeseries
682+
FROM switch.sampled_timepoint AS tp
683+
JOIN switch.period AS p USING(period_id, study_timeframe_id)
684+
WHERE time_sample_id = {time_sample_id}
685+
AND study_timeframe_id = {study_timeframe_id}
686+
) AS hts
687+
JOIN switch.hydro_historical_monthly_capacity_factors USING(month, year)
688+
JOIN switch.generation_plant USING(generation_plant_id)
663689
JOIN temp_generation_plant_ids USING(generation_plant_id)
664-
where hydro_simple_scenario_id={hydro_simple_scenario_id}
665-
and time_sample_id = {time_sample_id}
666-
order by 1;
690+
WHERE hydro_simple_scenario_id={hydro_simple_scenario_id}
691+
ORDER BY 1;
667692
""",
668693
)
669694

0 commit comments

Comments
 (0)