Skip to content

Commit 12ee5eb

Browse files
pesapstaadecker
authored andcommitted
Merge pull request #89 from staadecker/improv_hydro
Improve hydro module
2 parents d2e7456 + 2102eaf commit 12ee5eb

File tree

3 files changed

+114
-54
lines changed

3 files changed

+114
-54
lines changed

REAM Model Changelog.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@ Changes are listed from oldest (first line) to newest (last line of table).
1515
| #36 | May 2021 | Correct inputs to only list transmission lines in one direction. |
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). |
18-
| #68 | June 2021 | Change financial params to 2018 dollars & 5% interest rate. Start using terrain multipliers (which now include the economic multiplier). |
18+
| #68 | June 2021 | Change financial params to 2018 dollars & 5% interest rate. Start using terrain multipliers (which now include the economic multiplier). |
19+
| #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/wecc/get_inputs/get_inputs.py

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

0 commit comments

Comments
 (0)