|
5 | 5 | This module defines storage technologies. It builds on top of generic
|
6 | 6 | generators, adding components for deciding how much energy to build into
|
7 | 7 | 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. |
8 | 15 | """
|
9 | 16 |
|
10 | 17 | from pyomo.environ import *
|
@@ -66,47 +73,61 @@ def define_components(mod):
|
66 | 73 | Note that this describes the energy component and the overnight_cost
|
67 | 74 | describes the power component.
|
68 | 75 |
|
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. |
72 | 79 |
|
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 |
75 | 82 |
|
76 | 83 | 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. |
79 | 86 |
|
80 | 87 | STORAGE_GEN_TPS is the subset of GEN_TPS,
|
81 | 88 | restricted to storage projects.
|
82 | 89 |
|
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. |
85 | 92 |
|
86 | 93 | 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. |
88 | 96 |
|
89 | 97 | Charge_Storage_Upper_Limit[(g, t) in STORAGE_GEN_TPS]
|
90 | 98 | constrains ChargeStorage to available power capacity (accounting for
|
91 | 99 | gen_store_to_release_ratio)
|
92 | 100 |
|
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. |
96 | 104 |
|
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 |
| -
|
104 | 105 | Track_State_Of_Charge[(g, t) in STORAGE_GEN_TPS] constrains StateOfCharge
|
105 | 106 | based on the StateOfCharge in the previous timepoint, ChargeStorage and
|
106 | 107 | DispatchGen. If pumped hydro projects are available (defined in the
|
107 | 108 | optional prerequisite simple_hydro module), this constraint will also
|
108 | 109 | include average stream inflow and spilled water for pumped hydro projects.
|
109 | 110 |
|
| 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 | +
|
110 | 131 | State_Of_Charge_Upper_Limit[(g, t) in STORAGE_GEN_TPS]
|
111 | 132 | constrains StateOfCharge based on installed energy capacity.
|
112 | 133 |
|
@@ -271,19 +292,34 @@ def load_inputs(mod, switch_data, inputs_dir):
|
271 | 292 |
|
272 | 293 | """
|
273 | 294 |
|
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`. |
280 | 308 | switch_data.load_aug(
|
281 | 309 | filename=os.path.join(inputs_dir, 'generation_projects_info.tab'),
|
282 | 310 | 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)) |
285 | 320 | # 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. |
287 | 323 | switch_data.data()['STORAGE_GENS'] = {
|
288 | 324 | None: list(switch_data.data(name='gen_storage_efficiency').keys())}
|
289 | 325 | switch_data.load_aug(
|
|
0 commit comments