diff --git a/examples/production_cost_models/spinning_and_quickstart_reserves/README.md b/examples/production_cost_models/spinning_and_quickstart_reserves/README.md new file mode 100644 index 000000000..383ef46ec --- /dev/null +++ b/examples/production_cost_models/spinning_and_quickstart_reserves/README.md @@ -0,0 +1,4 @@ +SYNOPSIS + switch solve --verbose --log-run + +This example extends spinning_reserves by adding quickstart reserve requirements. diff --git a/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/financials.csv b/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/financials.csv new file mode 100644 index 000000000..c162f5372 --- /dev/null +++ b/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/financials.csv @@ -0,0 +1,2 @@ +base_financial_year,interest_rate,discount_rate +2015,0.07,0.05 diff --git a/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/fuel_cost.csv b/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/fuel_cost.csv new file mode 100644 index 000000000..765891797 --- /dev/null +++ b/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/fuel_cost.csv @@ -0,0 +1,2 @@ +load_zone,fuel,period,fuel_cost +South,NaturalGas,2010,4 diff --git a/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/fuels.csv b/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/fuels.csv new file mode 100644 index 000000000..54dfca062 --- /dev/null +++ b/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/fuels.csv @@ -0,0 +1,2 @@ +fuel,co2_intensity,upstream_co2_intensity +NaturalGas,0.05306,0 diff --git a/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/gen_build_costs.csv b/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/gen_build_costs.csv new file mode 100644 index 000000000..ac5a4ef32 --- /dev/null +++ b/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/gen_build_costs.csv @@ -0,0 +1,6 @@ +GENERATION_PROJECT,build_year,gen_overnight_cost,gen_fixed_om +S-Geothermal,1998,5524200,0.0 +S-NG_CC,2000,1143900,5868.3 +S-NG_GT,1990,605430,4891.8 +S-NG_GT,2002,605430,4891.8 +S-Central_PV-1,2001,2334300,41850.0 diff --git a/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/gen_build_predetermined.csv b/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/gen_build_predetermined.csv new file mode 100644 index 000000000..0ba241572 --- /dev/null +++ b/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/gen_build_predetermined.csv @@ -0,0 +1,6 @@ +GENERATION_PROJECT,build_year,gen_predetermined_cap +S-Geothermal,1998,2.0 +S-NG_CC,2000,7.0 +S-NG_GT,1990,3.0 +S-NG_GT,2002,4.0 +S-Central_PV-1,2001,3.0 diff --git a/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/gen_inc_heat_rates.csv b/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/gen_inc_heat_rates.csv new file mode 100644 index 000000000..1e3b52fee --- /dev/null +++ b/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/gen_inc_heat_rates.csv @@ -0,0 +1,5 @@ +GENERATION_PROJECT,power_start_mw,power_end_mw,incremental_heat_rate_mbtu_per_mwhr,fuel_use_rate_mmbtu_per_h +S-NG_CC,40,.,.,269.4069 +S-NG_CC,40,100.0,6.684885,. +S-NG_GT,0,.,.,0.1039 +S-NG_GT,0,1.0,10.2861,. diff --git a/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/gen_part_load_heat_rates.csv b/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/gen_part_load_heat_rates.csv new file mode 100644 index 000000000..cbb3139e1 --- /dev/null +++ b/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/gen_part_load_heat_rates.csv @@ -0,0 +1,5 @@ +GENERATION_PROJECT,gen_loading_level,gen_heat_rate_at_loading_level +S-NG_CC,0.4,2.694069 +S-NG_CC,1,6.705 +S-NG_GT,0,0.1039 +S-NG_GT,1,10.39 diff --git a/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/generation_projects_info.csv b/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/generation_projects_info.csv new file mode 100644 index 000000000..ff728359a --- /dev/null +++ b/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/generation_projects_info.csv @@ -0,0 +1,5 @@ +GENERATION_PROJECT,gen_dbid,gen_tech,gen_load_zone,gen_connect_cost_per_mw,gen_capacity_limit_mw,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_variable_om,gen_energy_source,gen_full_load_heat_rate,gen_unit_size,gen_min_load_fraction,gen_startup_fuel,gen_startup_om,gen_min_downtime,gen_can_provide_spinning_reserves +S-Geothermal,33,Geothermal,South,134222.0,3.0,30,0,0.0075,0.0241,0,1,0,28.83,Geothermal,.,.,.,.,.,.,0 +S-NG_CC,34,NG_CC,South,57566.6,.,20,0,0.04,0.06,0,0,0,3.4131,NaturalGas,6.705,1.0,0.4,9.16,10.3,12.0,1 +S-NG_GT,36,NG_GT,South,57566.6,.,20,0,0.04,0.06,0,0,0,27.807,NaturalGas,10.39,.,0.0,0.22,0.86,.,1 +S-Central_PV-1,41,Central_PV,South,74881.9,4.0,20,0,0.0,0.02,1,0,0,0.0,Solar,.,.,0.0,.,0.0,.,0 diff --git a/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/load_zones.csv b/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/load_zones.csv new file mode 100644 index 000000000..9fc69cca1 --- /dev/null +++ b/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/load_zones.csv @@ -0,0 +1,2 @@ +LOAD_ZONE,existing_local_td,local_td_annual_cost_per_mw +South,10,128040 diff --git a/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/loads.csv b/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/loads.csv new file mode 100644 index 000000000..ae1c33ba3 --- /dev/null +++ b/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/loads.csv @@ -0,0 +1,5 @@ +LOAD_ZONE,TIMEPOINT,zone_demand_mw +South,1,3 +South,2,8 +South,3,10 +South,4,7 diff --git a/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/modules.txt b/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/modules.txt new file mode 100644 index 000000000..a1fc8624e --- /dev/null +++ b/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/modules.txt @@ -0,0 +1,18 @@ +# 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.transmission.local_td +switch_model.generators.core.commit.operate +switch_model.generators.core.commit.fuel_use +switch_model.energy_sources.fuel_costs.simple +switch_model.balancing.operating_reserves.areas +switch_model.balancing.operating_reserves.spinning_reserves +switch_model.balancing.operating_reserves.quickstart +#switch_model.reporting.dump diff --git a/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/non_fuel_energy_sources.csv b/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/non_fuel_energy_sources.csv new file mode 100644 index 000000000..19134daf2 --- /dev/null +++ b/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/non_fuel_energy_sources.csv @@ -0,0 +1,3 @@ +energy_source +Geothermal +Solar diff --git a/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/periods.csv b/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/periods.csv new file mode 100644 index 000000000..e11233c10 --- /dev/null +++ b/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/periods.csv @@ -0,0 +1,2 @@ +INVESTMENT_PERIOD,period_start,period_end +2010,2008,2012 diff --git a/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/spinning_reserve_params.csv b/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/spinning_reserve_params.csv new file mode 100644 index 000000000..c10fc72e0 --- /dev/null +++ b/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/spinning_reserve_params.csv @@ -0,0 +1,2 @@ +contingency_safety_factor +1 diff --git a/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/switch_inputs_version.txt b/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/switch_inputs_version.txt new file mode 100644 index 000000000..e01025862 --- /dev/null +++ b/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/switch_inputs_version.txt @@ -0,0 +1 @@ +2.0.5 diff --git a/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/timepoints.csv b/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/timepoints.csv new file mode 100644 index 000000000..7f226c2a5 --- /dev/null +++ b/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/timepoints.csv @@ -0,0 +1,5 @@ +timepoint_id,timestamp,timeseries +1,2010011500,2010_all +2,2010011506,2010_all +3,2010011512,2010_all +4,2010011518,2010_all diff --git a/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/timeseries.csv b/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/timeseries.csv new file mode 100644 index 000000000..b6aef5f1e --- /dev/null +++ b/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/timeseries.csv @@ -0,0 +1,2 @@ +TIMESERIES,ts_period,ts_duration_of_tp,ts_num_tps,ts_scale_to_period +2010_all,2010,6,1,1826.25 diff --git a/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/variable_capacity_factors.csv b/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/variable_capacity_factors.csv new file mode 100644 index 000000000..a8ea1cb1a --- /dev/null +++ b/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/variable_capacity_factors.csv @@ -0,0 +1,5 @@ +GENERATION_PROJECT,timepoint,gen_max_capacity_factor +S-Central_PV-1,1,0.0 +S-Central_PV-1,2,0.61 +S-Central_PV-1,3,1.0 +S-Central_PV-1,4,0.4 diff --git a/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/zone_coincident_peak_demand.csv b/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/zone_coincident_peak_demand.csv new file mode 100644 index 000000000..7dd64257c --- /dev/null +++ b/examples/production_cost_models/spinning_and_quickstart_reserves/inputs/zone_coincident_peak_demand.csv @@ -0,0 +1,2 @@ +LOAD_ZONE,PERIOD,zone_expected_coincident_peak_demand +South,2010,10 diff --git a/examples/production_cost_models/spinning_and_quickstart_reserves/options.txt b/examples/production_cost_models/spinning_and_quickstart_reserves/options.txt new file mode 100644 index 000000000..202ef2d6d --- /dev/null +++ b/examples/production_cost_models/spinning_and_quickstart_reserves/options.txt @@ -0,0 +1,2 @@ +--spinning-requirement-rule 3+5 +--unit-contingency diff --git a/examples/production_cost_models/spinning_and_quickstart_reserves/outputs/total_cost.txt b/examples/production_cost_models/spinning_and_quickstart_reserves/outputs/total_cost.txt new file mode 100644 index 000000000..d7d9a1f62 --- /dev/null +++ b/examples/production_cost_models/spinning_and_quickstart_reserves/outputs/total_cost.txt @@ -0,0 +1 @@ +36406402.36853148 diff --git a/switch_model/balancing/operating_reserves/quickstart.py b/switch_model/balancing/operating_reserves/quickstart.py new file mode 100644 index 000000000..2d142e623 --- /dev/null +++ b/switch_model/balancing/operating_reserves/quickstart.py @@ -0,0 +1,216 @@ +# Copyright (c) 2015-2019 The Switch Authors. All rights reserved. +# Licensed under the Apache License, Version 2.0, which is in the LICENSE file. +""" +A simple model of quickstart reserves to accompany the spinning_reserves +modules. Patterns from the spinning reserves modules were followed where +practical to simplify implementation and review. + +Unlike spinning reserves, this module does not currently implement +contingency-based requirements because I lack an immediate use case for that. +The contigency reserves methodology from spinning reserve modules could +probably be adapted readily if needed. + +For more discussion of operating reserve considerations and modeling +approaches, see the spinning_reserves module. + +To do (wish list): + +1) Define additional rules for quickstart reserve requirements. +2) Add an optional parameter to specify how much capacity of a generation unit can be used to provide quickstart reserves (defaults to GenCapacity if units are MW, or 1.0 if units are fraction of available capacity). This could give us modeling flexibility to adapt to different definitions of quickstart reserves (some markets are more stringent than others), as well as to include different technologies. E.g. one might model a CCGT with the capability of providing quickstart reserves using only the Gas Turbines, or model a BESS system with the capability of providing quickstart reserves up to a certain fraction of its capacity (because the other fraction is reserved for energy arbitrage, for instance). + +""" +import os +from pyomo.environ import * + +dependencies = ( + 'switch_model.timescales', + 'switch_model.balancing.load_zones', + 'switch_model.balancing.operating_reserves.areas', + 'switch_model.financials', + 'switch_model.energy_sources.properties', + 'switch_model.generators.core.build', + 'switch_model.generators.core.dispatch', + 'switch_model.generators.core.commit.operate', +) +optional_prerequisites = ( + 'switch_model.balancing.operating_reserves.spinning_reserves', + 'switch_model.balancing.operating_reserves.spinning_reserves_advanced', +) + +# Uncomment this section when more than one rule is implemented. +# def define_arguments(argparser): +# group = argparser.add_argument_group(__name__) +# group.add_argument('--quickstart-requirement-rule', default="3+5", +# dest='quickstart_requirement_rule', +# choices = ["3+5"], +# help=("Choose rules for quickstart reserves requirements as a function " +# "of variable renewable power and load. '3+5' requires 3%% of " +# "load and 5%% of variable renewable output, based on the " +# "heuristic described in the 2010 Western Wind and Solar " +# "Integration Study.") +# ) + + +def define_dynamic_lists(mod): + """ + Quickstart_Reserve_Requirements is a list of model components that + contribute to quickstart reserve requirements in each balancing area and + timepoint. + + Quickstart_Reserve_Provisions is a list of model components that help + satisfy spinning reserve requirements in each balancing area and + timepoint. + + Each component in both lists needs to use units of MW and be indexed by: + (b, t) in BALANCING_AREA_TIMEPOINTS. + """ + mod.Quickstart_Reserve_Requirements = [] + mod.Quickstart_Reserve_Provisions = [] + + +def nrel_3_5_quickstart_reserve_requirements(mod): + """ + NREL35QuickstartRequirement[(b,t) in BALANCING_AREA_TIMEPOINTS] is + an expression for quickstart reserve requirements of 3% of load plus 5% of + renewable output, based on a heuristic described in NREL's 2010 Western + Wind and Solar Integration study. It is added to the + Quickstart_Reserve_Requirements list. If the local_td module is available + with DER accounting, load will be set to WithdrawFromCentralGrid. + Otherwise load will be set to lz_demand_mw. + """ + def NREL35QuickstartRequirement_rule(m, b, t): + try: + load = m.WithdrawFromCentralGrid + except AttributeError: + load = m.lz_demand_mw + return (0.03 * sum(load[z, t] for z in m.LOAD_ZONES + if b == m.zone_balancing_area[z]) + + 0.05 * sum(m.DispatchGen[g, t] for g in m.VARIABLE_GENS + if (g, t) in m.VARIABLE_GEN_TPS and + b == m.zone_balancing_area[m.gen_load_zone[g]])) + mod.NREL35QuickstartRequirement = Expression( + mod.BALANCING_AREA_TIMEPOINTS, + rule=NREL35QuickstartRequirement_rule + ) + mod.Quickstart_Reserve_Requirements.append('NREL35QuickstartRequirement') + + +def define_components(mod): + """ + gen_can_provide_quickstart_reserves[g] is a binary flag indicating whether + a generation project can provide quickstart reserves. Default to False for + baseload & variable generators, otherwise defaults to True. + + QUICKSTART_RESERVE_GEN_TPS is a subset of GEN_TPS for generators that + have gen_can_provide_quickstart_reserves set to True. + + CommitQuickstartReserves[(g,t) in QUICKSTART_RESERVE_GEN_TPS] is a + decision variable of how much quickstart reserve capacity to commit + (in MW). + + CommitQuickstartReserves_Limit[(g,t) in SPINNING_RESERVE_GEN_TPS] + constrain the CommitGenSpinningReserves variables based on CommitSlackUp + (and CommitGenSpinningReservesSlackUp as applicable). + + For example, if discrete unit commitment is enabled, and a 5MW hydro + generator is fully committed but only providing 2MW of power and 1 MW of + spinning reserves, the remaining 2MW of capacity (summarized in + CommitGenSpinningReservesSlackUp) could be committed to quickstart + reserves. + + CommittedQuickstartReserves[(b,t) in BALANCING_AREA_TIMEPOINTS] is an + expression summarizing the CommitQuickstartReserves variables for + generators within each balancing area. + + See also: NREL35QuickstartRequirement defined in the function above. + """ + mod.gen_can_provide_quickstart_reserves = Param( + mod.GENERATION_PROJECTS, + within=Boolean, + default=lambda m, g: not (m.gen_is_baseload[g] or m.gen_is_variable[g]), + doc="Denotes whether a generation project can provide quickstart " + "reserves. Default to false for baseload & variable generators, " + "otherwise true. Can be explicitly specified in an input file." + ) + mod.QUICKSTART_RESERVE_GEN_TPS = Set( + dimen=2, + within=mod.GEN_TPS, + initialize=lambda m: set( + (g,t) + for g in m.GENERATION_PROJECTS + if m.gen_can_provide_quickstart_reserves[g] + for t in m.TPS_FOR_GEN[g] + ) + ) + mod.CommitQuickstartReserves = Var( + mod.QUICKSTART_RESERVE_GEN_TPS, + within=NonNegativeReals + ) + def CommitQuickstartReserves_Limit_rule(m, g, t): + limit = m.CommitSlackUp[g,t] + try: + limit += m.CommitGenSpinningReservesSlackUp[g,t] + except (AttributeError, KeyError): + pass + return (m.CommitQuickstartReserves[g,t] <= limit) + mod.CommitQuickstartReserves_Limit = Constraint( + mod.QUICKSTART_RESERVE_GEN_TPS, + doc="Constrain committed quickstart reserves to uncommited capacity " + "plus any dispatch slack that is not committed to spinning " + "reserves (if applicable).", + rule=CommitQuickstartReserves_Limit_rule + ) + + mod.CommittedQuickstartReserves = Expression( + mod.BALANCING_AREA_TIMEPOINTS, + doc="Sum of committed quickstart reserve capacity per balancing " + "area and timepoint.", + rule=lambda m, b, t: ( + sum(m.CommitQuickstartReserves[g, t] + for z in m.ZONES_IN_BALANCING_AREA[b] + for g in m.GENS_IN_ZONE[z] + if (g,t) in m.QUICKSTART_RESERVE_GEN_TPS + ) + ) + ) + mod.Quickstart_Reserve_Provisions.append('CommittedQuickstartReserves') + + # These rules are in a separate function in anticipation of additional + # rulesets eventually being defined and selectable via command line + # arguments. + nrel_3_5_quickstart_reserve_requirements(mod) + + +def define_dynamic_components(mod): + """ + Satisfy_Quickstart_Requirement[(b,t) in BALANCING_AREA_TIMEPOINTS] + is a constraint that ensures quickstart reserve requirements are + being satisfied based on the sum of the dynamic lists + Quickstart_Reserve_Requirements & Quickstart_Reserve_Provisions. + """ + mod.Satisfy_Quickstart_Requirement = Constraint( + mod.BALANCING_AREA_TIMEPOINTS, + rule=lambda m, b, t: ( + sum(getattr(m, provision)[b,t] + for provision in m.Quickstart_Reserve_Provisions + ) >= + sum(getattr(m, requirement)[b,t] + for requirement in m.Quickstart_Reserve_Requirements + ) + ) + ) + + +def load_inputs(mod, switch_data, inputs_dir): + """ + All files & columns are optional. See notes above for default values. + + generation_projects_info.csv + GENERATION_PROJECTS, ... gen_can_provide_quickstart_reserves + """ + switch_data.load_aug( + filename=os.path.join(inputs_dir, 'generation_projects_info.csv'), + auto_select=True, + optional_params=['gen_can_provide_quickstart_reserves'], + param=(mod.gen_can_provide_quickstart_reserves) + ) diff --git a/switch_model/balancing/operating_reserves/spinning_reserves.py b/switch_model/balancing/operating_reserves/spinning_reserves.py index da1fd0e93..e4d714001 100644 --- a/switch_model/balancing/operating_reserves/spinning_reserves.py +++ b/switch_model/balancing/operating_reserves/spinning_reserves.py @@ -15,7 +15,7 @@ reserves, and contingency reserves without distinguishing their timescales or required response duration. Operating reserves at timescales with slower responses for load following or longer-term recovery from contingencies are not -included here. +included here, but can be accounted for with the quickstart module. Most regions and countries use distinct terminology for reserves products and distinct procedures for determining reserve requirements. This module provides