Skip to content

Commit 805641b

Browse files
authored
Merge pull request #89 from staadecker/improv_hydro
Improve hydro module
2 parents 3e3a208 + 5f05840 commit 805641b

File tree

4 files changed

+117
-48
lines changed

4 files changed

+117
-48
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: 79 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -23,25 +23,34 @@
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
# ToDo: Refactor this code to move the core components into a
4149
# switch_model.hydro.core module, the simplist components into
4250
# switch_model.hydro.simple, and the advanced components into
4351
# switch_model.hydro.water_network. That should set a good example
4452
# for other people who want to do other custom handling of hydro.
53+
import os.path
4554

4655
from pyomo.environ import *
4756

@@ -51,64 +60,92 @@
5160

5261
def define_components(mod):
5362
"""
54-
5563
HYDRO_GENS is the set of dispatchable hydro projects. This is a subet
5664
of GENERATION_PROJECTS, and is determined by the inputs file hydro_timeseries.csv.
5765
Members of this set can be called either g, or hydro_g.
5866
59-
HYDRO_GEN_TS is the set of Hydro projects and timeseries for which
67+
HYDRO_TS is the set of hydro timeseries over which the average flow constraint is defined.
68+
These hydro timeseries are different from the timeseries used in the reset of SWITCH
69+
and are defined by the input file hydro_timepoints.csv. If hydro_timepoints.csv doesn't exist,
70+
the default is for the timeseries to be the same as the SWITCH timeseries from timeseries.csv.
71+
Members of this set can be abbreviated as hts.
72+
73+
HYDRO_GEN_TS is the set of Hydro projects and hydro timeseries for which
6074
minimum and average flow are specified. Members of this set can be
61-
abbreviated as (project, timeseries) or (g, ts).
75+
abbreviated as (project, hydro_timeseries) or (g, hts).
6276
6377
HYDRO_GEN_TPS is the set of Hydro projects and available
6478
dispatch points. This is a filtered version of GEN_TPS that
6579
only includes hydro projects.
6680
67-
hydro_min_flow_mw[(g, ts) in HYDRO_GEN_TS] is a parameter that
81+
tp_to_hts[tp in TIMEPOINTS] is a parameter that returns the hydro timeseries
82+
for a given timepoint. It is defined in hydro_timepoints.csv and if unspecified
83+
it defaults to be equal to tp_ts.
84+
85+
hydro_min_flow_mw[(g, hts) in HYDRO_GEN_TS] is a parameter that
6886
determines minimum flow levels, specified in units of MW dispatch.
6987
70-
hydro_avg_flow_mw[(g, ts) in HYDRO_GEN_TS] is a parameter that
88+
hydro_avg_flow_mw[(g, hts) in HYDRO_GEN_TS] is a parameter that
7189
determines average flow levels, specified in units of MW dispatch.
7290
7391
Enforce_Hydro_Min_Flow[(g, t) in HYDRO_GEN_TPS] is a
7492
constraint that enforces minimum flow levels for each timepoint.
7593
76-
Enforce_Hydro_Avg_Flow[(g, ts) in HYDRO_GEN_TS] is a constraint
77-
that enforces average flow levels across each timeseries.
78-
94+
Enforce_Hydro_Avg_Flow[(g, hts) in HYDRO_GEN_TS] is a constraint
95+
that enforces average flow levels across each hydro timeseries.
7996
"""
97+
mod.tp_to_hts = Param(
98+
mod.TIMEPOINTS,
99+
input_file='hydro_timepoints.csv',
100+
default=lambda m, tp: m.tp_ts[tp],
101+
doc="Mapping of timepoints to a hydro series.",
102+
within=Any
103+
)
104+
105+
mod.HYDRO_TS = Set(
106+
dimen=1,
107+
ordered=False,
108+
initialize=lambda m: set(m.tp_to_hts[tp] for tp in m.TIMEPOINTS),
109+
doc="Set of hydro timeseries as defined in the mapping."
110+
)
111+
112+
mod.TPS_IN_HTS = Set(
113+
mod.HYDRO_TS,
114+
within=mod.TIMEPOINTS,
115+
ordered=False,
116+
initialize=lambda m, hts: set(t for t in m.TIMEPOINTS if m.tp_to_hts[t] == hts),
117+
doc="Set of timepoints in each hydro timeseries"
118+
)
80119

81120
mod.HYDRO_GEN_TS_RAW = Set(
82121
dimen=2,
83122
input_file='hydro_timeseries.csv',
84123
input_optional=True,
85-
validate=lambda m, g, ts: (g in m.GENERATION_PROJECTS) & (ts in m.TIMESERIES))
124+
validate=lambda m, g, hts: (g in m.GENERATION_PROJECTS) & (hts in m.HYDRO_TS), )
86125

87126
mod.HYDRO_GENS = Set(
88127
dimen=1,
89128
ordered=False,
90-
initialize=lambda m: set(g for (g, ts) in m.HYDRO_GEN_TS_RAW),
129+
initialize=lambda m: set(g for (g, hts) in m.HYDRO_GEN_TS_RAW),
91130
doc="Dispatchable hydro projects")
92-
mod.HYDRO_GEN_TS = Set(
93-
dimen=2,
94-
initialize=lambda m: set(
95-
(g, m.tp_ts[tp])
96-
for g in m.HYDRO_GENS
97-
for tp in m.TPS_FOR_GEN[g]))
131+
98132
mod.HYDRO_GEN_TPS = Set(
99133
initialize=mod.GEN_TPS,
100134
filter=lambda m, g, t: g in m.HYDRO_GENS)
101135

136+
mod.HYDRO_GEN_TS = Set(
137+
dimen=2,
138+
initialize=lambda m: set((g, m.tp_to_hts[tp]) for (g, tp) in m.HYDRO_GEN_TPS))
139+
102140
# Validate that a timeseries data is specified for every hydro generator /
103141
# timeseries that we need. Extra data points (ex: outside of planning
104142
# horizon or beyond a plant's lifetime) can safely be ignored to make it
105143
# easier to create input files.
106144
mod.have_minimal_hydro_params = BuildCheck(
107145
mod.HYDRO_GEN_TS,
108-
rule=lambda m, g, ts: (g,ts) in m.HYDRO_GEN_TS_RAW)
146+
rule=lambda m, g, hts: (g, hts) in m.HYDRO_GEN_TS_RAW)
109147

110-
# To do: Add validation check that timeseries data are specified for every
111-
# valid timepoint.
148+
# Todo: Add validation check that timeseries data are specified for every valid timepoint.
112149

113150
mod.hydro_min_flow_mw = Param(
114151
mod.HYDRO_GEN_TS_RAW,
@@ -117,14 +154,14 @@ def define_components(mod):
117154
default=0.0)
118155
mod.Enforce_Hydro_Min_Flow = Constraint(
119156
mod.HYDRO_GEN_TPS,
120-
rule=lambda m, g, t: (
121-
m.DispatchGen[g, t] >= m.hydro_min_flow_mw[g, m.tp_ts[t]]))
157+
rule=lambda m, g, t: Constraint.Skip
158+
if m.hydro_min_flow_mw[g, m.tp_to_hts[t]] == 0
159+
else m.DispatchGen[g, t] >= m.hydro_min_flow_mw[g, m.tp_to_hts[t]])
122160

123161
mod.hydro_avg_flow_mw = Param(
124162
mod.HYDRO_GEN_TS_RAW,
125163
within=NonNegativeReals,
126-
input_file='hydro_timeseries.csv',
127-
default=0.0)
164+
input_file='hydro_timeseries.csv')
128165

129166
# We use a scaling factor to improve the numerical properties
130167
# of the model. The scaling factor was determined using trial
@@ -133,8 +170,12 @@ def define_components(mod):
133170
enforce_hydro_avg_flow_scaling_factor = 1e1
134171
mod.Enforce_Hydro_Avg_Flow = Constraint(
135172
mod.HYDRO_GEN_TS,
136-
rule=lambda m, g, ts: (enforce_hydro_avg_flow_scaling_factor *
137-
sum(m.DispatchGen[g, t] for t in m.TPS_IN_TS[ts]) / m.ts_num_tps[ts]
138-
== m.hydro_avg_flow_mw[g, ts] * enforce_hydro_avg_flow_scaling_factor))
173+
rule=lambda m, g, hts:
174+
enforce_hydro_avg_flow_scaling_factor *
175+
# Compute the weighted average of the dispatch
176+
sum(m.DispatchGen[g, t] * m.tp_weight[t] for t in m.TPS_IN_HTS[hts])
177+
/ sum(m.tp_weight[tp] for tp in m.TPS_IN_HTS[hts])
178+
<= m.hydro_avg_flow_mw[g, hts] * enforce_hydro_avg_flow_scaling_factor
179+
)
139180

140181
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
@@ -90,6 +90,8 @@ def debug(type, value, tb):
9090
# Patch pyomo if needed, to allow reconstruction of expressions.
9191
# This must be done before the model is constructed.
9292
patch_pyomo()
93+
patch_to_allow_loading(Set)
94+
patch_to_allow_loading(Param)
9395

9496
# Define the model
9597
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)