Skip to content

Add pumped hydro in a generalized & backwards-compatible way #117

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: next_release
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions examples/pumped_hydro_simple/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
SYNOPSIS:

switch solve --verbose --log-run

This example illustrates modeling pumped hydro storage by using the hydro_simple module in concert with the storage module.

This adds pumped hydro to the hydro_simple example, and illustrates pumped hydro being used for arbitrage within each example day.
2 changes: 2 additions & 0 deletions examples/pumped_hydro_simple/inputs/financials.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
base_financial_year,interest_rate,discount_rate
2015,0.07,0.05
2 changes: 2 additions & 0 deletions examples/pumped_hydro_simple/inputs/fuel_cost.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
load_zone,fuel,period,fuel_cost
South,NaturalGas,2020,4
2 changes: 2 additions & 0 deletions examples/pumped_hydro_simple/inputs/fuels.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
fuel,co2_intensity,upstream_co2_intensity
NaturalGas,0.05306,0
10 changes: 10 additions & 0 deletions examples/pumped_hydro_simple/inputs/gen_build_costs.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
GENERATION_PROJECT,build_year,gen_overnight_cost,gen_fixed_om,gen_storage_energy_overnight_cost
S-NG_CC,2000.0,1143900.0,5868.3,.
S-Central_PV-1,2000.0,2334300.0,41850.0,.
S-Geothermal,1998.0,5524200.0,0.0,.
Hydro,2000.0,10000000.0,100000.0,.
Hydro_RoR,2000.0,1000000.0,100000.0,.
Hydro_Pumped,2000.0,1000000.0,100000.0,0.0
S-Geothermal,2020.0,5524200.0,0.0,.
S-NG_CC,2020.0,1143900.0,5868.3,.
S-Central_PV-1,2020.0,2334300.0,41850.0,.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
GENERATION_PROJECT,build_year,gen_predetermined_cap
S-NG_CC,2000,5.0
S-Central_PV-1,2000,1.0
S-Geothermal,1998,1.0
Hydro,2000,1.0
Hydro_RoR,2000,1.0
Hydro_Pumped,2000,5.0
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
GENERATION_PROJECT,gen_dbid,gen_tech,gen_load_zone,gen_connect_cost_per_mw,gen_capacity_limit_mw,gen_variable_om,gen_max_age,gen_min_build_capacity,gen_scheduled_outage_rate,gen_forced_outage_rate,gen_is_variable,gen_is_baseload,gen_is_cogen,gen_energy_source,gen_full_load_heat_rate,gen_is_pumped_hydro,gen_storage_efficiency
S-Geothermal,33.0,Geothermal,South,134222.0,10.0,28.83,30,0,0.0075,0.0241,0,1,0,Geothermal,.,.,.
S-NG_CC,34.0,NG_CC,South,57566.6,.,3.4131,20,0,0.04,0.06,0,0,0,NaturalGas,6.705,.,.
S-Central_PV-1,41.0,Central_PV,South,74881.9,2.0,0.0,20,0,0.0,0.02,1,0,0,Solar,.,.,.
Hydro,.,Hydro,South,0.0,1.0,0.1,100,0,0.019,0.05,0,0,0,Water,.,.,.
Hydro_RoR,.,Hydro_RoR,South,0.0,1.0,0.0,30,0,0.019,0.05,1,0,0,Water,.,.,.
Hydro_Pumped,.,Hydro_Pumped,South,0.0,5.0,0.1,100,0,0.019,0.05,0,0,0,Water,.,1.0,0.75
5 changes: 5 additions & 0 deletions examples/pumped_hydro_simple/inputs/hydro_timeseries.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
hydro_project,timeseries,hydro_min_flow_mw,hydro_avg_flow_mw
Hydro,2020_winter,0.6,0.75
Hydro,2020_summer,0.2,0.6
Hydro_Pumped,2020_winter,0.6,0.75
Hydro_Pumped,2020_summer,0.2,0.6
2 changes: 2 additions & 0 deletions examples/pumped_hydro_simple/inputs/load_zones.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
LOAD_ZONE,cost_multipliers,ccs_distance_km,dbid
South,1,0,3
5 changes: 5 additions & 0 deletions examples/pumped_hydro_simple/inputs/loads.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
LOAD_ZONE,TIMEPOINT,zone_demand_mw
South,1,9.0
South,2,2.5
South,3,10.0
South,4,4.0
14 changes: 14 additions & 0 deletions examples/pumped_hydro_simple/inputs/modules.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Core Modules
switch_model
switch_model.timescales
switch_model.financials
switch_model.balancing.load_zones
switch_model.energy_sources.properties
switch_model.generators.core.build
switch_model.generators.core.dispatch
switch_model.reporting
# Custom Modules
switch_model.generators.core.no_commit
switch_model.energy_sources.fuel_costs.simple
switch_model.generators.extensions.hydro_simple
switch_model.generators.extensions.storage
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
energy_source
Solar
Geothermal
Water
2 changes: 2 additions & 0 deletions examples/pumped_hydro_simple/inputs/periods.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
INVESTMENT_PERIOD,period_start,period_end
2020,2017,2026
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
2.0.5
5 changes: 5 additions & 0 deletions examples/pumped_hydro_simple/inputs/timepoints.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
timepoint_id,timestamp,timeseries
1,2025011512,2020_winter
2,2025011600,2020_winter
3,2025071512,2020_summer
4,2025071600,2020_summer
3 changes: 3 additions & 0 deletions examples/pumped_hydro_simple/inputs/timeseries.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
TIMESERIES,ts_period,ts_duration_of_tp,ts_num_tps,ts_scale_to_period
2020_winter,2020,12,2,1826
2020_summer,2020,12,2,1826
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
GENERATION_PROJECT,timepoint,gen_max_capacity_factor
S-Central_PV-1,1,0.61
S-Central_PV-1,2,0.0
S-Central_PV-1,3,0.81
S-Central_PV-1,4,0.0
Hydro_RoR,1,0.25
Hydro_RoR,2,0.5
Hydro_RoR,3,0.2
Hydro_RoR,4,0.4
1 change: 1 addition & 0 deletions examples/pumped_hydro_simple/outputs/total_cost.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
30423392.46
74 changes: 68 additions & 6 deletions switch_model/generators/extensions/hydro_simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,23 @@
but the advanced framework would take longer to read and understand. To really
take advantage of it, you'll also need more data than we usually have
available.

This module can model pumped hydro systems if the storage module is listed
after simple_hydro in modules.txt, and pumped hydro generation projects are
flagged via gen_is_pumped_hydro. The current implementation of pumped_hydro
implicitly assumes that the lower reservoir always has sufficient water in it
for pumping uphill into storage. Existing resevoir energy capacity can be
constrained via gen_predetermined_storage_energy_mwh as needed. If existing
reservoir energy capacity is never a binding constraint for day-to-day
operations of pumped hydro, leave gen_predetermined_storage_energy_mwh
unspecified, and set gen_storage_energy_overnight_cost to 0 and the
optimization will set the energy value to a conveniently large value.

"""
# ToDo: Refactor this code to move the core components into a
# switch_model.hydro.core module, the simplest components into
# switch_model.hydro.simple, and the advanced components into
# switch_model.hydro.water_network. That should set a good example
# switch_model.hydro.water_network. That could set a good example
# for other people who want to do other custom handling of hydro.

from __future__ import division
Expand All @@ -51,9 +63,19 @@ def define_components(mod):
GENERATION_PROJECTS, and is determined by the inputs file
hydro_timeseries.csv.

gen_is_pumped_hydro[g in GENERATION_PROJECTS] is an optional parameter
that denotes whether a hydro project includes pumped storage. To use this
pumped hydro implementation, you must include the storage module after
hydro_simple in modules.txt. The storage module will look for storage
generators flagged as pumped hydro and will define custom constraints for
their dispatch & storage that takes into account stream flow.

HYDRO_GEN_TS is the set of Hydro projects and timeseries for which
minimum and average flow are specified.

HYDRO_NONPUMPED_GEN_TS is a subset of HYDRO_GEN_TS for hydro projects that
do not include pumped storage, and is used to establish flow constraints.

HYDRO_GEN_TPS is the set of Hydro projects and available
dispatch points. This is a filtered version of GEN_TPS that
only includes hydro projects.
Expand All @@ -76,7 +98,11 @@ def define_components(mod):
Enforce_Hydro_Avg_Flow[(g, ts) in HYDRO_NONPUMPED_GEN_TS] is a constraint
that enforces average flow levels across each timeseries. It requires the
average of dispatched and spilled hydro over the course of a timeseries
must equal to the corresponding hydro_avg_flow_mw parameter.
must equal to the corresponding hydro_avg_flow_mw parameter. The
corresponding constraint for pumped hydro is defined in the storage
module (after storage decision variables are available), via an augmented
version of the Track_State_Of_Charge constraint that adds average incoming
streamflow and subtracts any spilled power.
"""

mod.HYDRO_GEN_TS_RAW = Set(
Expand All @@ -87,13 +113,23 @@ def define_components(mod):
)
mod.HYDRO_GENS = Set(
initialize=lambda m: set(g for (g, ts) in m.HYDRO_GEN_TS_RAW),
doc="Dispatchable hydro projects")
doc="Dispatchable hydro projects (both pumped & non-pumped)")
mod.gen_is_pumped_hydro = Param(
mod.GENERATION_PROJECTS,
within=Boolean,
default=False,
validate=lambda m, value, g: (value == False) or (g in m.HYDRO_GENS))

mod.HYDRO_GEN_TS = Set(
dimen=2,
initialize=lambda m: set(
(g, m.tp_ts[tp])
for g in m.HYDRO_GENS
for tp in m.TPS_FOR_GEN[g]))
mod.HYDRO_NONPUMPED_GEN_TS = Set(
dimen=2,
initialize=mod.HYDRO_GEN_TS,
filter=lambda m, g, ts: m.gen_is_pumped_hydro[g] == False)
mod.HYDRO_GEN_TPS = Set(
initialize=mod.GEN_TPS,
filter=lambda m, g, t: g in m.HYDRO_GENS)
Expand Down Expand Up @@ -125,8 +161,8 @@ def _warn_on_extra_HYDRO_GEN_TS(m):
"could indicate a benign issue where the process that built "
"the dataset used simplified logic and/or didn't know the "
"scheduled operating dates. If you expect those datapoints to "
"be useful, then those plants need to either come online earlier "
", have longer lifetimes, or have options to build new capacity "
"be useful, then those plants need to either come online earlier, "
"have longer lifetimes, or have options to build new capacity "
"when the old capacity reaches the provided end-of-life date."
"\n".format(num_impacted_generators))
if extra_indexes:
Expand All @@ -153,14 +189,26 @@ def _warn_on_extra_HYDRO_GEN_TS(m):
mod.HYDRO_GEN_TPS,
within=NonNegativeReals)
mod.Enforce_Hydro_Avg_Flow = Constraint(
mod.HYDRO_GEN_TS,
mod.HYDRO_NONPUMPED_GEN_TS,
rule=lambda m, g, ts: (
sum(m.DispatchGen[g, t] + m.SpillHydro[g,t]
for t in m.TPS_IN_TS[ts]
) == m.hydro_avg_flow_mw[g, ts] * m.ts_num_tps[ts]))

mod.min_data_check('hydro_min_flow_mw', 'hydro_avg_flow_mw')

def storage_module_avail_for_pumped_hydro_check(m):
no_pumped_hydro = all(
value(m.gen_is_pumped_hydro[g]) == False
for g in m.GENERATION_PROJECTS)
has_storage = (
'switch_model.generators.extensions.storage' in m.module_list)
return (no_pumped_hydro or has_storage)
mod.storage_module_avail_for_pumped_hydro = BuildCheck(
rule=storage_module_avail_for_pumped_hydro_check,
doc="Ensure that the user has included the storage module if they are"
"attempting to model pumped hydro.")


def load_inputs(mod, switch_data, inputs_dir):
"""
Expand All @@ -177,6 +225,15 @@ def load_inputs(mod, switch_data, inputs_dir):
hydro_generation_project, timeseries, hydro_min_flow_mw,
hydro_avg_flow_mw

To model pumped hydro projects that use both river flows and pumped
storage, include the storage module in modules.txt after the hydro_simple
module. You will also need to populate the gen_is_pumped_hydro &
gen_storage_efficiency columns of generation_projects_info.tab for the
pumped hydro projects.

generation_projects_info.csv
GENERATION_PROJECT, ..., gen_is_pumped_hydro

"""
switch_data.load_aug(
optional=True,
Expand All @@ -185,3 +242,8 @@ def load_inputs(mod, switch_data, inputs_dir):
index=mod.HYDRO_GEN_TS_RAW,
param=(mod.hydro_min_flow_mw, mod.hydro_avg_flow_mw)
)
switch_data.load_aug(
filename=os.path.join(inputs_dir, 'generation_projects_info.csv'),
auto_select=True,
optional_params=['gen_is_pumped_hydro'],
param=(mod.gen_is_pumped_hydro,))
Loading