Skip to content

Commit 83151d3

Browse files
Update documentation of pumped hydro storage. Explicit describe units of various energy-based components in the storage model (to reduce confusion between power & energy), and rewrap line endings to conform to PEP-8 recommendations for reading two files side-by-side in a single screen (or a single file in large font on a small screen)
1 parent 49bdcd8 commit 83151d3

File tree

2 files changed

+78
-30
lines changed

2 files changed

+78
-30
lines changed

switch_model/generators/extensions/hydro_simple.py

+13-1
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,23 @@
2222
but the advanced framework would take longer to read and understand. To really
2323
take advantage of it, you'll also need more data than we usually have
2424
available.
25+
26+
This module can model pumped hydro systems if the storage module is listed
27+
after simple_hydro in modules.txt, and pumped hydro generation projects are
28+
flagged via gen_is_pumped_hydro. The current implementation of pumped_hydro
29+
implicitly assumes that the lower reservoir always has sufficient water in it
30+
for pumping uphill into storage. Existing resevoir energy capacity can be
31+
constrained via gen_predetermined_storage_energy_mwh as needed. If existing
32+
reservoir energy capacity is never a binding constraint for day-to-day
33+
operations of pumped hydro, leave gen_predetermined_storage_energy_mwh
34+
unspecified, and set gen_storage_energy_overnight_cost to 0 and the
35+
optimization will set the energy value to a conveniently large value.
36+
2537
"""
2638
# ToDo: Refactor this code to move the core components into a
2739
# switch_model.hydro.core module, the simplest components into
2840
# switch_model.hydro.simple, and the advanced components into
29-
# switch_model.hydro.water_network. That should set a good example
41+
# switch_model.hydro.water_network. That could set a good example
3042
# for other people who want to do other custom handling of hydro.
3143

3244
from __future__ import division

switch_model/generators/extensions/storage.py

+65-29
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@
55
This module defines storage technologies. It builds on top of generic
66
generators, adding components for deciding how much energy to build into
77
storage, when to charge, energy accounting, etc.
8+
9+
To do:
10+
11+
* Add optional ability to exogenously constrain StateOfCharge based on
12+
input files. See notes under the Track_State_Of_Charge documentation below.
13+
* Update the PumpedHydro features to work with the hydro_system module,
14+
and use full mass balance accounting.
815
"""
916

1017
from pyomo.environ import *
@@ -66,47 +73,61 @@ def define_components(mod):
6673
Note that this describes the energy component and the overnight_cost
6774
describes the power component.
6875
69-
BuildStorageEnergy[(g, bld_yr) in STORAGE_GEN_BLD_YRS]
70-
is a decision of how much energy capacity to build onto a storage
71-
project. This is analogous to BuildGen, but for energy rather than power.
76+
BuildStorageEnergy[(g, bld_yr) in STORAGE_GEN_BLD_YRS] is a decision of
77+
how much energy capacity to build onto a storage project in units of MWh.
78+
This is analogous to BuildGen, but for energy rather than power.
7279
73-
StorageEnergyInstallCosts[PERIODS] is an expression of the
74-
annual costs incurred by the BuildStorageEnergy decision.
80+
StorageEnergyInstallCosts[PERIODS] is an expression of the annual costs
81+
incurred by the BuildStorageEnergy decision, in units of $/MWh
7582
7683
StorageEnergyCapacity[g, period] is an expression describing the
77-
cumulative available energy capacity of BuildStorageEnergy. This is
78-
analogous to GenCapacity.
84+
cumulative available energy capacity of BuildStorageEnergy in units of
85+
MWh. This is analogous to GenCapacity.
7986
8087
STORAGE_GEN_TPS is the subset of GEN_TPS,
8188
restricted to storage projects.
8289
83-
ChargeStorage[(g, t) in STORAGE_GEN_TPS] is a dispatch
84-
decision of how much to charge a storage project in each timepoint.
90+
ChargeStorage[(g, t) in STORAGE_GEN_TPS] is a dispatch decision of how
91+
much to charge a storage project in each timepoint in units of MW.
8592
8693
StorageNetCharge[LOAD_ZONE, TIMEPOINT] is an expression describing the
87-
aggregate impact of ChargeStorage in each load zone and timepoint.
94+
aggregate impact of ChargeStorage in each load zone and timepoint in units
95+
of MW.
8896
8997
Charge_Storage_Upper_Limit[(g, t) in STORAGE_GEN_TPS]
9098
constrains ChargeStorage to available power capacity (accounting for
9199
gen_store_to_release_ratio)
92100
93-
StateOfCharge[(g, t) in STORAGE_GEN_TPS] is a variable
94-
for tracking state of charge. This value stores the state of charge at
95-
the end of each timepoint for each storage project.
101+
StateOfCharge[(g, t) in STORAGE_GEN_TPS] is a variable for tracking state
102+
of charge, in units of MWh. This value stores the state of charge at the
103+
end of each timepoint for each storage project.
96104
97-
With the current "circular" implementation of each timeseries,
98-
StateOfCharge[first] = StateOfCharge[last] + net_energy. This effectively
99-
allows each timeseries to begin and end with any given charge level (0 to
100-
100% of energy capacity), and the optimization process will choose a value
101-
that is convenient. If you wish to constrain the value, you will need to
102-
write an extension.
103-
104105
Track_State_Of_Charge[(g, t) in STORAGE_GEN_TPS] constrains StateOfCharge
105106
based on the StateOfCharge in the previous timepoint, ChargeStorage and
106107
DispatchGen. If pumped hydro projects are available (defined in the
107108
optional prerequisite simple_hydro module), this constraint will also
108109
include average stream inflow and spilled water for pumped hydro projects.
109110
111+
With the current circular implementation of each timeseries,
112+
StateOfCharge[first] = StateOfCharge[last] + net_energy. This effectively
113+
allows each timeseries to begin and end with any given charge level (0 to
114+
100% of energy capacity), and the optimization process will choose a value
115+
that is convenient.
116+
117+
For some applications, you may wish to constrain the StateOfCharge values
118+
to pre-determined values, which would require writing a new module that
119+
reads data and applies a constraint to StateOfCharge at the beginning of
120+
each day - either as a fixed amount of energy (MWh) or as a fraction of
121+
energy storage capacity (%). Or alternately, updating this file to include
122+
that behavior as an optional feature.
123+
124+
Other applications may require linking consecutive timeseries so that the
125+
StateOfCharge carries over from one timeseries to the next. That would
126+
require writing a new version of the timescales module and redefining
127+
tp_previous to use sequential indexing rather than consecutive. Note, that
128+
strategy is not required for 8760 hourly/annual production cost models;
129+
those problems can define a single timeseries of an entire year.
130+
110131
State_Of_Charge_Upper_Limit[(g, t) in STORAGE_GEN_TPS]
111132
constrains StateOfCharge based on installed energy capacity.
112133
@@ -271,19 +292,34 @@ def load_inputs(mod, switch_data, inputs_dir):
271292
272293
"""
273294

274-
# TODO: maybe move these columns to a storage_gen_info file to avoid the weird index
275-
# reading and avoid having to create these extra columns for all projects;
276-
# Alternatively, say that these values are specified for _all_ projects (maybe with None
277-
# as default) and then define STORAGE_GENS as the subset of projects for which
278-
# gen_storage_efficiency has been specified, then require valid settings for all
279-
# STORAGE_GENS.
295+
# TODO: consider moving these columns to a storage_gen_info file to avoid
296+
# avoid having to create these extra columns for all projects, and to
297+
# allow unambiguous marking of storage projects without looking for
298+
# non-empty gen_storage_efficiency columns. This is similar to how
299+
# simple_hydro specifies input files. Pro: less empty values for
300+
# non-storage projects. Con: another input file to keep track of.
301+
# Alternatively, say that these values are specified for _all_ projects
302+
# (maybe with None as default) and then define STORAGE_GENS as the subset
303+
# of projects for which gen_storage_efficiency has been specified, then
304+
# require valid settings for all STORAGE_GENS.
305+
# Note: The current implementation is similar to the 2nd option above, but
306+
# requires checking `g in mod.gen_storage_efficiency` rather than checking
307+
# `mod.gen_storage_efficiency[g] is not None`.
280308
switch_data.load_aug(
281309
filename=os.path.join(inputs_dir, 'generation_projects_info.tab'),
282310
auto_select=True,
283-
optional_params=['gen_store_to_release_ratio', 'gen_storage_energy_to_power_ratio', 'gen_storage_max_cycles_per_year'],
284-
param=(mod.gen_storage_efficiency, mod.gen_store_to_release_ratio, mod.gen_storage_energy_to_power_ratio, mod.gen_storage_max_cycles_per_year))
311+
optional_params=[
312+
'gen_store_to_release_ratio',
313+
'gen_storage_energy_to_power_ratio',
314+
'gen_storage_max_cycles_per_year'],
315+
param=(
316+
mod.gen_storage_efficiency,
317+
mod.gen_store_to_release_ratio,
318+
mod.gen_storage_energy_to_power_ratio,
319+
mod.gen_storage_max_cycles_per_year))
285320
# Base the set of storage projects on storage efficiency being specified.
286-
# TODO: define this in a more normal way
321+
# TODO: define this in a more normal way, possibly with a binary flag
322+
# gen_is_storage.
287323
switch_data.data()['STORAGE_GENS'] = {
288324
None: list(switch_data.data(name='gen_storage_efficiency').keys())}
289325
switch_data.load_aug(

0 commit comments

Comments
 (0)