Skip to content
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
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ For questions or general discussion, please open a [discussion](https://github.c

To contribute code, fork the repository, make your changes, and submit a pull request. Please review our [Developer Guide](https://reeds-model.github.io/ReEDS/developer_best_practices.html) before getting started.

**Note:** if you're considering making a change that might lead to meaningful differences in model capability, workflow, or outputs, we recommend initiating a [discussion](https://github.com/ReEDS-Model/ReEDS/discussions).
**Note:** if you're considering making a change that might lead to meaningful differences in model capability, workflow, or outputs, we recommend initiating a [discussion](https://github.com/ReEDS-Model/ReEDS/discussions).
1 change: 1 addition & 0 deletions cases_test.csv
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ GSw_RetirePenalty,,,,,0,,,,,,,,,,,,,,,,,,,,,,,,,
GSw_FakeData,,,,,,1,1,1,,,,,,,,,,,,,,,,,,,,,,
GSw_PRM_CapCredit,,,,,,,,1,1,,,,,,,,1,,,,,,,,,,,,,
GSw_PRM_scenario,,,,,,,,,static,,,,,,,,static,,,,,,,,,,,,,
GSw_PRM_UpdateMethod,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,
Comment thread
clairehalloran marked this conversation as resolved.
GSw_HourlyType,,,,,,,,,,wek,year,,,,,,,,,,,,,,,,,,,
GSw_InterDayLinkage,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,
GSw_HourlyWeatherYears,,,,,,,2012_2013,,,,,,2020,2007_2008_2009_2010_2011_2012_2013,,,,,,,,2012_2013,,,,2018,,,,
Expand Down
7 changes: 4 additions & 3 deletions docs/source/model_documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -2460,11 +2460,12 @@ Adapted from {cite}`maiIncorporatingStressfulGrid2024`.

The calculation of capacity credit for VRE is described in the [VRE capacity credit](#vre-capacity-credit) section;
the method for storage is described in the [storage capacity credit](#storage-capacity-credit) section.
Thermal generators are given a capacity credit of 100% in ReEDS;
technology-specific [outage rates](#outage-rates) for thermal generators are not considered in this method
and are instead assumed to be factored into the [planning reserve margin](#planning-reserve-margins).


#### Thermal generation capacity credit

For thermal generators (i.e. combined cycle, combustion turbine, nuclear (conventional and SMR), and steam (coal)), ReEDS estimates a seasonal capacity credit for each region/technology combination based on temperature-dependent forced outage rates (described in detail in [outage rates](#outage-rates)). To calculate each technology's contribution to the seasonal reserve margin, its nameplate capacity is multiplied by $(1 - \bar{FOR})$, where $\bar{FOR}$ is the mean forced outage rate during the top 20 net load hours cross all modeled [weather years](#weather-years) for each season.


#### VRE capacity credit

Expand Down
1 change: 1 addition & 0 deletions inputs/sets/i_subtech.csv
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ COMBINED_CYCLE
COMBUSTION_TURBINE
FUEL_CELL
CONV
THERMAL
CCS
CCS_MOD
CCS_MAX
Expand Down
148 changes: 74 additions & 74 deletions inputs/tech-subset-table.csv

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion reeds/core/setup/b_inputs.gms
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ set
combustion_turbine(i)"combustion turbine technologies",
consume(i) "technologies that consume electricity and add to load",
conv(i) "conventional generation technologies",
thermal(i) "thermal generation technologies",
csp_storage(i) "csp generation technologies with thermal storage",
csp(i) "csp generation technologies",
csp1(i) "csp-tes generation technologies 1",
Expand Down Expand Up @@ -717,6 +718,7 @@ combined_cycle(i)$(not ban(i)) = yes$i_subsets(i,'combined_cycle') ;
combustion_turbine(i)$(not ban(i)) = yes$i_subsets(i,'combustion_turbine') ;
consume(i)$(not ban(i)) = yes$i_subsets(i,'consume') ;
conv(i)$(not ban(i)) = yes$i_subsets(i,'conv') ;
thermal(i)$(not ban(i)) = yes$i_subsets(i,'thermal') ;
csp_storage(i)$(not ban(i)) = yes$i_subsets(i,'csp_storage') ;
csp(i)$(not ban(i)) = yes$i_subsets(i,'csp') ;
csp1(i)$(not ban(i)) = yes$i_subsets(i,'csp1') ;
Expand Down Expand Up @@ -6118,7 +6120,8 @@ Parameter
cc_excess(i,r,ccseason,t) "--MW-- this is the excess capacity credit when assuming marginal capacity credit in intertemporal solve"
vre_gen_last_year(r,allh,t) "--MW-- generation from VRE generators in the prior solve year"
hybrid_cc_derate(i,r,ccseason,sdbin,t) "--fraction-- derate factor for hybrid PV+battery storage capacity credit"
m_cc_mar(i,r,ccseason,t) "--fraction-- marginal capacity credit",
m_cc_mar(i,r,ccseason,t) "--fraction-- marginal capacity credit"
mean_forced_outage_rate(i,r,ccseason,t)"--fraction-- mean forced outage rate for each technology, region, and ccseason - used to derate thermal generator capacity"
* Heuristic climate impacts
trans_cap_delta(allh,allt) "--fraction-- fractional adjustment to transmission capacity from climate heuristics"
* Emissions and policies
Expand All @@ -6140,6 +6143,7 @@ cc_excess(i,r,ccseason,t) = 0 ;
cc_old(i,r,ccseason,t) = 0 ;
m_cc_mar(i,r,ccseason,t) = 0 ;
hybrid_cc_derate(i,r,ccseason,sdbin,t)$[pvb(i)$valcap_irt(i,r,t)] = 1 ;
mean_forced_outage_rate(i,r,ccseason,t) = 0 ;

* Trim some of the largest matrices to reduce file sizes
cost_vom(i,v,r,t)$[not valgen(i,v,r,t)] = 0 ;
Expand Down
1 change: 1 addition & 0 deletions reeds/core/setup/c_model.gms
Original file line number Diff line number Diff line change
Expand Up @@ -1718,6 +1718,7 @@ eq_reserve_margin(r,ccseason,t)
+ sum{(i,v)$[valcap(i,v,r,t)$(not vre(i))$(not hydro(i))$(not storage(i))$(not consume(i))$(not forced_retire(i,r,t))],
CAP(i,v,r,t)
* (1 + ccseason_cap_frac_delta(i,v,r,ccseason,t))
* (1 - mean_forced_outage_rate(i,r,ccseason,t))
}

*[plus] firm capacity from existing VRE or CSP
Expand Down
1 change: 1 addition & 0 deletions reeds/core/setup/e_solveprep.gms
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ parameter
sdbin_size_load(ccreg,ccseason,sdbin,t) "--MW-- bin_size power loading in from the cc_out gdx file"
cc_mar_load(i,r,ccreg,ccseason,t) "--fraction-- cc_mar loading in from the cc_out gdx file"
cc_evmc_load(i,r,ccseason,t) "--fraction-- cc_evmc loading in from the cc_out gdx file"
mean_forced_outage_rate_load(i,r,ccseason,t) "--fraction-- mean_forced_outage_rate loading in from the cc_out gdx file"
;


Expand Down
2 changes: 2 additions & 0 deletions reeds/core/solve/3_solve_oneyear.gms
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ $loaddcr cc_old_load = cc_old
$loaddcr cc_mar_load = cc_mar
$loaddcr cc_evmc_load = cc_evmc
$loaddcr sdbin_size_load = sdbin_size
$loaddcr mean_forced_outage_rate_load = mean_forced_outage_rate
$gdxin

*Note: these values are rounded before they are written to the gdx file, so no need to round them here
Expand Down Expand Up @@ -181,6 +182,7 @@ cc_old(i,r,ccseason,t)$[tload(t)$pvb(i)$sum{(v,tt)$tprev(t,tt), CAP.l(i,v,r,tt)}
min{ cc_old(i,r,ccseason,t) / sum{(v,tt)$tprev(t,tt), CAP.l(i,v,r,tt)}, 1 / ilr(i) - bcr(i) }
* sum{(v,tt)$tprev(t,tt), CAP.l(i,v,r,tt)};

mean_forced_outage_rate(i,r,ccseason,t)$tload(t) = mean_forced_outage_rate_load(i,r,ccseason,t) ;
$endif.tcheck


Expand Down
5 changes: 4 additions & 1 deletion reeds/core/terminus/report.gms
Original file line number Diff line number Diff line change
Expand Up @@ -934,7 +934,10 @@ cc_new(i,r,ccseason,t)$[valcap_irt(i,r,t)$cap_new_cc(i,r,ccseason,t)] = sum{v$iv

cap_firm(i,r,ccseason,t)$[valcap_irt(i,r,t)$[not consume(i)]$tmodel_new(t)$Sw_PRM_CapCredit] =
sum{v$[(not vre(i))$(not hydro(i))$(not storage(i))$(not storage_hybrid(i)$(not csp(i)))$valcap(i,v,r,t)],
CAP.l(i,v,r,t) * (1 + ccseason_cap_frac_delta(i,v,r,ccseason,t)) }
CAP.l(i,v,r,t)
* (1 + ccseason_cap_frac_delta(i,v,r,ccseason,t))
* (1 - mean_forced_outage_rate(i,r,ccseason,t))
}
+ cc_old(i,r,ccseason,t)
+ sum{v$[(vre(i) or csp(i) or storage_hybrid(i)$(not csp(i)))$valinv(i,v,r,t)],
m_cc_mar(i,r,ccseason,t) * (INV.l(i,v,r,t) + INV_REFURB.l(i,v,r,t)$[refurbtech(i)$Sw_Refurb]) }
Expand Down
34 changes: 34 additions & 0 deletions reeds/resource_adequacy/capacity_credit.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,13 +152,25 @@ def reeds_cc(t, tnext, casedir):
### Get the non-duplicated profiles
resource_profiles = resources.drop_duplicates('resource')

### Get forced outage rates for thermal technologies
forced_outage_rate = reeds.io.get_outage_hourly(inputs_case,'forced')

tech_subset_table = reeds.techs.expand_GAMS_tech_groups(
reeds.techs.get_tech_subset_table(casedir).reset_index()
).set_index('tech_group').i
techs_to_keep = tech_subset_table.loc['THERMAL'].tolist()
techs_to_drop = [col for col in forced_outage_rate.columns.levels[0] if col not in techs_to_keep]

forced_outage_rate = forced_outage_rate.drop(columns=techs_to_drop, level=0, errors='ignore')

# Remove the "8760" safety valve bin
safety_bin = max(sdb['bin'].values)
sdb = sdb[sdb['bin'] != max(sdb['bin'])]
sdb = [int(x) for x in sdb['bin']]

# Temporal definitions
h_dt_szn = pd.read_csv(os.path.join('inputs_case', 'rep', 'h_dt_szn.csv'))
hmap_allyrs = pd.read_csv(os.path.join('inputs_case','rep','hmap_allyrs.csv'))

ccseasons = []
if sw['cc_calc_annual']:
Expand Down Expand Up @@ -464,6 +476,27 @@ def pivot_melt_data(df):
### Reorder to match ReEDS convention
net_load_2012 = net_load_2012.reindex(['ccreg','ccseason','year','h','hour','t','value'], axis=1)

# export mean forced outage rate for top load hours in each region and ccseason
top_net_load_hours = net_load.groupby(['ccreg','ccseason']).head(int(sw['GSw_PRM_CapCreditHours']))
# map back to regions from ccreg for ReEDS input
top_net_load_hours = top_net_load_hours.merge(
hierarchy[['r','ccreg']], on='ccreg', how='left'
).drop(['ccreg','year','hour','t','value'], axis=1)
# map h to timestamp to match forced outage rate
top_net_load_hours['*timestamp'] = top_net_load_hours['h'].apply(reeds.timeseries.h2timestamp)
hours = top_net_load_hours.groupby(['ccseason', 'r'])['*timestamp'].agg(list)

mean_forced_outage_rate = (
pd.concat({
(ccseason, r): forced_outage_rate.loc[h].xs(r, 1, 'r').mean()
for (ccseason, r), h in hours.items()
}, names=('ccseason', 'r'))
.reset_index()
.rename(columns={0:'value'})
.assign(t=str(tnext))
.reindex(['i','r','ccseason','t','value'], axis=1)
)

if int(sw['GSw_EVMC']):
cc_evmc = (
pd.concat(dict_cc_evmc, axis=0)
Expand All @@ -484,6 +517,7 @@ def pivot_melt_data(df):
'sdbin_size': sdbin_size,
'net_load': net_load,
'net_load_2012': net_load_2012,
'mean_forced_outage_rate' : mean_forced_outage_rate,
}

return cc_results
Expand Down
2 changes: 1 addition & 1 deletion reeds/resource_adequacy/ra_calcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ def main(t, tnext, casedir, iteration=0):
'cc_old': pd.DataFrame(columns=['i','r','ccreg','szn','t','Value']),
'cc_evmc': pd.DataFrame(columns=['i','r','szn','t','Value']),
'sdbin_size': pd.DataFrame(columns=['ccreg','szn','bin','t','Value']),
'mean_forced_outage_rate': pd.DataFrame(columns=['i','r','szn','t','Value']),
}

reeds.log.toc(tic=tic, year=t, process='ra/capacity_credit.py')
Expand Down Expand Up @@ -165,7 +166,6 @@ def main(t, tnext, casedir, iteration=0):
)

#%% Identify stress periods
print('identifying new stress periods...')
tic = datetime.datetime.now()
if (
('user' not in sw['GSw_PRM_StressModel'].lower())
Expand Down
10 changes: 10 additions & 0 deletions runreeds.py
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,16 @@ def check_compatibility(sw):
except ValueError:
raise ValueError(err)

if int(sw['GSw_PRM_UpdateMethod']) == 0 and int(sw['GSw_PRM_CapCredit']) == 1 and int(sw['GSw_PRM_StressIterateMax']) > 0:
raise ValueError(
"The combination of GSw_PRM_UpdateMethod=0, GSw_PRM_CapCredit=1, "
"and GSw_PRM_StressIterateMax>0 is not supported.\n"
"To iteratively update the PRM, set GSw_PRM_UpdateMethod to an integer between 1-3:"
"\n1: static update set by GSw_PRM_UpdateFraction; "
"\n2: dynamic update informed by PRAS; "
"\n3: dynamic update but only after all new stress periods have been added"
)

for bir in sw['GSw_PVB_BIR'].split('_'):
if not (float(bir) >= 0):
raise ValueError("Fix GSw_PVB_BIR")
Expand Down
Loading