From 5f40e6056bc9f506fa6e0042ccdd518a66eda97b Mon Sep 17 00:00:00 2001 From: Abdelrahman Ayad Date: Tue, 21 Apr 2026 10:46:30 -0600 Subject: [PATCH 01/31] minimal example on how to call CVAR in run_pras.jl --- ReEDS_Augur/run_pras.jl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ReEDS_Augur/run_pras.jl b/ReEDS_Augur/run_pras.jl index a44e86f6..76d919f5 100644 --- a/ReEDS_Augur/run_pras.jl +++ b/ReEDS_Augur/run_pras.jl @@ -208,6 +208,11 @@ function run_pras(pras_system_path::String, args::Dict) @info "$(PRAS.EUE(results["short"])) MWh" @info "NEUE = $(1e6 * PRAS.EUE(results["short"]).eue.estimate / sum(sys.regions.load)) ppm" + # CVAR results + _,_,_,_,energyunit = get_params(sys) + alpha = 0.95 + @info (PRAS.CVAR(energyunit, results["short_samples"], alpha)) + ## Filter out DC regions used for VSC HVDC transmission regions = [r for r in sys.regions.names if !(occursin("|", r))] From 4a867e64064ace45ea388e8ffcf441335091ef55 Mon Sep 17 00:00:00 2001 From: Abdelrahman Ayad Date: Thu, 23 Apr 2026 21:37:01 -0600 Subject: [PATCH 02/31] minor fix to `get_params` --- ReEDS_Augur/run_pras.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReEDS_Augur/run_pras.jl b/ReEDS_Augur/run_pras.jl index 76d919f5..d923adc6 100644 --- a/ReEDS_Augur/run_pras.jl +++ b/ReEDS_Augur/run_pras.jl @@ -209,7 +209,7 @@ function run_pras(pras_system_path::String, args::Dict) @info "NEUE = $(1e6 * PRAS.EUE(results["short"]).eue.estimate / sum(sys.regions.load)) ppm" # CVAR results - _,_,_,_,energyunit = get_params(sys) + _,_,_,_,energyunit = PRAS.get_params(sys) alpha = 0.95 @info (PRAS.CVAR(energyunit, results["short_samples"], alpha)) From b11b5882af845476a9a28ac2027211000270d2cf Mon Sep 17 00:00:00 2001 From: Abdelrahman Ayad Date: Mon, 4 May 2026 00:20:46 -0600 Subject: [PATCH 03/31] generalize eue methods to stress metrics --- ReEDS_Augur/stress_periods.py | 245 +++++++++++++++++++--------------- reeds/ra.py | 46 +++---- 2 files changed, 164 insertions(+), 127 deletions(-) diff --git a/ReEDS_Augur/stress_periods.py b/ReEDS_Augur/stress_periods.py index 905a710b..6a9ba210 100644 --- a/ReEDS_Augur/stress_periods.py +++ b/ReEDS_Augur/stress_periods.py @@ -24,10 +24,10 @@ #%%### Functions -def plot_eue_diagnostics(sw, t, iteration, high_eue_periods): +def plot_stress_diagnostics(sw, t, iteration, high_stress_periods, stress_metric='EUE'): try: dates = ( - pd.concat(high_eue_periods) + pd.concat(high_stress_periods) .reset_index().actual_period.map(reeds.timeseries.h2timestamp) .dt.strftime('%Y-%m-%d') .tolist() @@ -89,40 +89,47 @@ def get_and_write_neue(sw, write=True): eue.to_csv(os.path.join(sw['casedir'],'outputs','eue.csv')) return neue - -def get_annual_neue(case, t, iteration=0): +def get_annual_stress_metric(case, t, stress_metric, iteration=0): """ """ - ### Get EUE from PRAS - dfeue = reeds.ra.get_pras_eue(case=case, t=t, iteration=iteration) + ### Get values from PRAS + dfmetric = reeds.ra.get_pras_stress_metric(case=case, t=t, iteration=iteration, stress_metric=stress_metric) ### Get load (for calculating NEUE) - dfload = reeds.io.read_h5py_file( - os.path.join( - case,'ReEDS_Augur','augur_data',f'pras_load_{t}.h5') - ) - dfload.index = dfeue.index + if stress_metric.upper() == 'NEUE': + dfload = reeds.io.read_h5py_file( + os.path.join( + case,'ReEDS_Augur','augur_data',f'pras_load_{t}.h5') + ) + dfload.index = dfmetric.index levels = ['country','interconnect','nercr','transreg','transgrp','st','r'] - _neue = {} + _metric = {} for hierarchy_level in levels: ### Get the region aggregator rmap = reeds.io.get_rmap(case=case, hierarchy_level=hierarchy_level) - ### Get NEUE summed over year - _neue[hierarchy_level,'sum'] = ( - dfeue.rename(columns=rmap).groupby(axis=1, level=0).sum().sum() + + ### Get stress metric summed over year + _metric[hierarchy_level,'sum'] = dfmetric.rename(columns=rmap).groupby(axis=1, level=0).sum().sum() + + ### Get max stress metric hour + _metric[hierarchy_level,'max'] = dfmetric.rename(columns=rmap).groupby(axis=1, level=0).sum().max() + + if stress_metric.upper() == 'NEUE': + _metric[hierarchy_level,'sum'] = ( + dfmetric.rename(columns=rmap).groupby(axis=1, level=0).sum().sum() / dfload.rename(columns=rmap).groupby(axis=1, level=0).sum().sum() - ) * 1e6 - ### Get max NEUE hour - _neue[hierarchy_level,'max'] = ( - dfeue.rename(columns=rmap).groupby(axis=1, level=0).sum() + ) * 1e6 + ### Get max NEUE hour + _metric[hierarchy_level,'max'] = ( + dfmetric.rename(columns=rmap).groupby(axis=1, level=0).sum() / dfload.rename(columns=rmap).groupby(axis=1, level=0).sum() - ).max() * 1e6 + ).max() * 1e6 ### Combine it - neue = pd.concat(_neue, names=['level','metric','region']).rename('NEUE_ppm') + metric = pd.concat(_metric, names=['level','metric','region']).rename(f'{stress_metric}') - return neue + return metric def get_shoulder_periods(sw, criterion, dfenergy_r, high_eue_periods): @@ -145,7 +152,7 @@ def get_shoulder_periods(sw, criterion, dfenergy_r, high_eue_periods): timeindex = reeds.timeseries.get_timeindex(sw['resource_adequacy_years']) cutofftype, cutoff = sw.GSw_PRM_StressStorageCutoff.lower().split('_') periodhours = {'day':24, 'wek':24*5, 'year':24}[sw.GSw_HourlyType] - (hierarchy_level, ppm, stress_metric, period_agg_method) = criterion.split('_') + (hierarchy_level, stress_level, stress_metric, period_agg_method) = criterion.split('_') ## Aggregate storage energy to hierarchy_level dfenergy_agg = ( @@ -159,6 +166,7 @@ def get_shoulder_periods(sw, criterion, dfenergy_r, high_eue_periods): for i, row in high_eue_periods[criterion, f'high_{stress_metric}'].iterrows(): if row.r not in dfheadspace_MWh: continue + print(f"Evaluating shoulder periods for {row.name} ({row.r}) with {stress_metric} = {row[stress_metric]:.2f}") day = pd.Timestamp('-'.join(row[['y','m','d']].astype(str).tolist())) @@ -201,7 +209,7 @@ def get_shoulder_periods(sw, criterion, dfenergy_r, high_eue_periods): return shoulder_periods -def get_eue_sorted_periods(sw, t, iteration): +def get_stress_metrics_sorted_periods(sw, t, iteration): ### Get storage state of charge (SOC) to use in selection of "shoulder" stress periods dfenergy = reeds.io.read_pras_results( os.path.join(sw['casedir'], 'ReEDS_Augur', 'PRAS', f"PRAS_{t}i{iteration}-energy.h5") @@ -215,12 +223,6 @@ def get_eue_sorted_periods(sw, t, iteration): .groupby(axis=1, level=0).sum() ) - ### Get NEUE - neue = pd.read_csv( - os.path.join(sw.casedir, 'outputs', f'neue_{t}i{iteration}.csv'), - index_col=['level', 'metric', 'region'], - ).squeeze(1) - ### Load this year's stress periods so we don't duplicate stressperiods_this_iteration = pd.read_csv( os.path.join( @@ -228,78 +230,98 @@ def get_eue_sorted_periods(sw, t, iteration): ) ### Check all stress criteria; for regions that fail, add new stress periods - _eue_sorted_periods = {} + # _eue_sorted_periods = {} + _stress_sorted_periods = {} failed = {} - high_eue_periods = {} + high_stress_periods = {} shoulder_periods = {} - for criterion in sw.GSw_PRM_StressThreshold.split('/'): - ## Example: criterion = 'transgrp_10_EUE_sum' - (hierarchy_level, ppm, stress_metric, period_agg_method) = criterion.split('_') - - eue_periods = reeds.ra.get_eue_periods( - case=sw.casedir, t=t, iteration=iteration, - hierarchy_level=hierarchy_level, - stress_metric=stress_metric, - period_agg_method=period_agg_method, - ) - - ### Sort in descending stress_metric order - _eue_sorted_periods[criterion] = ( - eue_periods - .sort_values(stress_metric, ascending=False) - .reset_index().set_index('actual_period') - ) - ### Get the threshold(s) and see if any of them failed - this_test = neue[hierarchy_level][period_agg_method] - - if (this_test > float(ppm)).any(): - failed[criterion] = this_test.loc[this_test > float(ppm)] - print(f"GSw_PRM_StressThreshold = {criterion} failed for:") - print(failed[criterion]) - ###### Add GSw_PRM_StressIncrement periods to the list for the next iteration - high_eue_periods[criterion, f'high_{stress_metric}'] = ( - _eue_sorted_periods[criterion].loc[ - ## Only include new stress periods for the region(s) that failed - _eue_sorted_periods[criterion].r.isin(failed[criterion].index) - ## Don't repeat existing stress periods - & ~(_eue_sorted_periods[criterion].index.isin( - stressperiods_this_iteration.actual_period)) - ] - ## Don't add dates more than once - .drop_duplicates(subset=['y','m','d']) - ## Keep the GSw_PRM_StressIncrement worst periods for each region. - ## If you instead want to keep the GSw_PRM_StressIncrement worst periods - ## overall, use .nlargest(int(sw.GSw_PRM_StressIncrement), stress_metric) - .groupby('r').head(int(sw.GSw_PRM_StressIncrement)) + # stress periods column names for writing outputs + stress_metrics_units_mapping = {'EUE':'MWh', 'NEUE':'ppm', 'LOLE':'events'} + stress_metrics_col_names = {m:f'{m}_{mapping}' for m,mapping + in zip(sw.GSw_PRM_StressThresholdMetrics.split('/'), + stress_metrics_units_mapping.values())} + + for metric in sw.GSw_PRM_StressThresholdMetrics.split('/'): + for criterion in sw[f'GSw_PRM_StressThreshold{metric}'].split('/'): + print(f"Evaluating GSw_PRM_StressThreshold {metric} with criterion: {criterion}") + ## Example: criterion = 'transgrp_10_EUE_sum' + (hierarchy_level, stress_level, stress_metric, period_agg_method) = criterion.split('_') + + ### Get stored stress metric + stress_vals = pd.read_csv( + os.path.join(sw.casedir, 'outputs', f'{stress_metric}_{t}i{iteration}.csv'), + index_col=['level', 'metric', 'region'], + ).squeeze(1) + + stress_periods = reeds.ra.get_stress_metric_periods( + case=sw.casedir, t=t, iteration=iteration, + hierarchy_level=hierarchy_level, + stress_metric=stress_metric, + period_agg_method=period_agg_method, ) - for period, row in high_eue_periods[criterion, f'high_{stress_metric}'].iterrows(): - print( - f"Added {period} " - f"({reeds.timeseries.h2timestamp(period).strftime('%Y-%m-%d')}) " - f"as stress period for {row.r} " - f"({stress_metric} = {row[stress_metric]})" - ) - - ### Include "shoulder periods" before or after each period - ### if the storage state of charge is low - shoulder_periods = { - **shoulder_periods, - **get_shoulder_periods(sw, criterion, dfenergy_r, high_eue_periods) - } - ### Dealing with earlier criteria may also address later criteria, so stop here - break - - else: - print(f"GSw_PRM_StressThreshold = {criterion} passed") + ### Sort in descending stress_metric order + _stress_sorted_periods[criterion] = ( + stress_periods + .sort_values(stress_metric, ascending=False) + .reset_index().set_index('actual_period') + ) - eue_sorted_periods = pd.concat(_eue_sorted_periods, names=['criterion']) + ### Get the threshold(s) and see if any of them failed + this_test = stress_vals[hierarchy_level][period_agg_method] + + if (this_test > float(stress_level)).any(): + failed[criterion] = this_test.loc[this_test > float(stress_level)] + print(f"GSw_PRM_StressThreshold = {criterion} failed for:") + print(failed[criterion]) + ###### Add GSw_PRM_StressIncrement periods to the list for the next iteration + high_stress_periods[criterion, f'high_{stress_metric}'] = ( + _stress_sorted_periods[criterion].loc[ + ## Only include new stress periods for the region(s) that failed + _stress_sorted_periods[criterion].r.isin(failed[criterion].index) + ## Don't repeat existing stress periods + & ~(_stress_sorted_periods[criterion].index.isin( + stressperiods_this_iteration.actual_period)) + ] + ## Don't add dates more than once + .drop_duplicates(subset=['y','m','d']) + ## Keep the GSw_PRM_StressIncrement worst periods for each region. + ## If you instead want to keep the GSw_PRM_StressIncrement worst periods + ## overall, use .nlargest(int(sw.GSw_PRM_StressIncrement), stress_metric) + .groupby('r').head(int(sw.GSw_PRM_StressIncrement)) + ) + for period, row in high_stress_periods[criterion, f'high_{stress_metric}'].iterrows(): + print( + f"Added {period} " + f"({reeds.timeseries.h2timestamp(period).strftime('%Y-%m-%d')}) " + f"as stress period for {row.r} " + f"({stress_metric} = {row[stress_metric]})" + ) + + ### Include "shoulder periods" before or after each period + ### if the storage state of charge is low + ### Check only if stress metric is EUE + if stress_metric.upper() == 'EUE': + shoulder_periods = { + **shoulder_periods, + **get_shoulder_periods(sw, criterion, dfenergy_r, high_stress_periods) + } + + ### Dealing with earlier criteria may also address later criteria, so stop here + break + + else: + print(f"GSw_PRM_StressThreshold = {criterion} passed") + + stress_sorted_periods = pd.concat(_stress_sorted_periods, names=['criterion']) ### Get lists of stress periods: new (added this iteration) and all + ##TODO: What if same high stress period is added for multiple criteria? + # Currently the duplicates are dropped. if len(failed): new_stress_periods = pd.concat( - {**high_eue_periods, **shoulder_periods}, names=['criterion','periodtype'], + {**high_stress_periods, **shoulder_periods}, names=['criterion','periodtype'], ).reset_index().drop_duplicates(subset='actual_period', keep='first') else: return failed, None, None @@ -334,14 +356,14 @@ def get_eue_sorted_periods(sw, t, iteration): combined_periods_write.to_csv(outpath, index=False) ### Tables and plots for debugging - eue_sorted_periods.round(2).rename(columns={'EUE':'EUE_MWh','NEUE':'NEUE_ppm'}).to_csv( - os.path.join(sw.casedir, 'inputs_case', newstresspath, 'eue_sorted_periods.csv') + stress_sorted_periods.round(2).rename(columns=stress_metrics_col_names).to_csv( + os.path.join(sw.casedir, 'inputs_case', newstresspath, f'{stress_metric}_sorted_periods.csv') ) - new_stress_periods.round(2).rename(columns={'EUE':'EUE_MWh','NEUE':'NEUE_ppm'}).to_csv( + new_stress_periods.round(2).rename(columns=stress_metrics_col_names).to_csv( os.path.join(sw.casedir, 'inputs_case', newstresspath, 'new_stress_periods.csv'), index=False, ) - plot_eue_diagnostics(sw, t, iteration, high_eue_periods) + plot_stress_diagnostics(sw, t, iteration, high_stress_periods, stress_metric=stress_metric) return failed, new_stressperiods_write, combined_periods_write @@ -468,7 +490,7 @@ def prm_increment_pras(sw, t, iteration, combined_periods_write, failed_regions) return prm_increment -def update_prm(sw, t, iteration, failed, combined_periods_write): +def update_prm(sw, t, iteration, failed, combined_periods_write, metric): """Update the energy reserve margin by region r for stress periods, either using a static increment (GSw_PRM_UpdateMethod=1) or based on the estimated surplus needed by PRAS to recover the desired reliabiliaty criteria (GSw_PRM_UpdateMethod>1). @@ -479,7 +501,8 @@ def update_prm(sw, t, iteration, failed, combined_periods_write): iteration (int): ReEDS-PRAS iteration failed (dict): Dictionary of regions with unserved energy at the hierarchy_level and their criterion evaluations - stress_hours (pd.DataFrame): data frame of stress periods + combined_periods_write (pd.DataFrame): Data frame of combined stress periods + metric (str): Stress metric for this update Returns: pd.DataFrame: Table of prm levels for the next PRAS iteration @@ -541,12 +564,15 @@ def main(sw, t, iteration=0, logging=True): import hourly_writetimeseries newstresspath = f'stress{t}i{iteration+1}' - #%% Write consolidated NEUE so far + #%% Write consolidated stress metrics so far try: - _neue_simple = get_and_write_neue(sw, write=True) - neue = get_annual_neue(sw.casedir, t, iteration=iteration) - neue.round(2).to_csv( - os.path.join(sw.casedir, 'outputs', f"neue_{t}i{iteration}.csv") + ## TODO: Check whether to keep _neue_simple or not. + # _neue_simple = get_and_write_neue(sw, write=True) + for stress_metric in sw.GSw_PRM_StressThresholdMetrics.split('/'): + print(f"Calculating and writing annual {stress_metric} for iteration {iteration}") + dfmetric = get_annual_stress_metric(sw.casedir, t, stress_metric, iteration=iteration) + dfmetric.round(2).to_csv( + os.path.join(sw.casedir, 'outputs', f"{stress_metric}_{t}i{iteration}.csv") ) except Exception as err: @@ -560,7 +586,7 @@ def main(sw, t, iteration=0, logging=True): return #%% Identify and write new stress periods - failed, new_stressperiods_write, combined_periods_write = get_eue_sorted_periods( + failed, new_stressperiods_write, combined_periods_write = get_stress_metrics_sorted_periods( sw=sw, t=t, iteration=iteration, ) @@ -613,3 +639,14 @@ def main(sw, t, iteration=0, logging=True): # sw['GSw_PRM_UpdateMethod'] = 2 # #%%### # main(sw, t, iteration, logging=False) + + #%%### option to run script directly for debugging + casedir = "/Users/aayad/Work/repos/ReEDS/runs/v20260419_224354_Pacific" + t = 2020 # previous solve year + iteration = 0 + # load switches + sw = reeds.io.get_switches(casedir) + sw['t'] = t + sw['GSw_PRM_UpdateMethod'] = 2 + #%%### + main(sw, t, iteration, logging=False) diff --git a/reeds/ra.py b/reeds/ra.py index 22d8ee35..70409fa4 100644 --- a/reeds/ra.py +++ b/reeds/ra.py @@ -8,31 +8,31 @@ ### Functions -def get_pras_eue(case, t, iteration=0): +def get_pras_stress_metric(case, t, iteration=0, stress_metric='EUE'): """ """ ### Get PRAS outputs dfpras = reeds.io.read_pras_results( os.path.join(case, 'ReEDS_Augur', 'PRAS', f"PRAS_{t}i{iteration}.h5") ) + dfpras.to_csv('pras_results.csv') ### Create the time index sw = reeds.io.get_switches(case) dfpras.index = reeds.timeseries.get_timeindex(sw['resource_adequacy_years']) - ### Keep the EUE columns by zone - eue_tail = '_EUE' - dfeue = dfpras[[ + ### Keep the metric columns by zone + metric_tail = '_' + stress_metric.upper() + dfmetric = dfpras[[ c for c in dfpras - if (c.endswith(eue_tail) and not c.startswith('USA')) + if (c.endswith(metric_tail) and not c.startswith('USA')) ]].copy() ## Drop the tailing _EUE - dfeue = dfeue.rename( - columns=dict(zip(dfeue.columns, [c[:-len(eue_tail)] for c in dfeue]))) + dfmetric = dfmetric.rename( + columns=dict(zip(dfmetric.columns, [c[:-len(metric_tail)] for c in dfmetric]))) - return dfeue + return dfmetric - -def get_eue_periods( +def get_stress_metric_periods( case, t, iteration=0, hierarchy_level='transgrp', stress_metric='EUE', @@ -60,21 +60,21 @@ def get_eue_periods( ### Get the region aggregator rmap = reeds.io.get_rmap(case=case, hierarchy_level=hierarchy_level) - ### Get EUE from PRAS - dfeue = get_pras_eue(case=case, t=t, iteration=iteration) + ### Get stress metric from PRAS + dfmetric = get_pras_stress_metric(case=case, t=t, iteration=iteration, stress_metric=stress_metric) ## Aggregate to hierarchy_level - dfeue = ( - dfeue + dfmetric = ( + dfmetric .rename_axis('r', axis=1).rename_axis('h', axis=0) .rename(columns=rmap).groupby(axis=1, level=0).sum() ) ###### Calculate the stress metric by period - if stress_metric.upper() == 'EUE': + if stress_metric.upper() in ['EUE', 'LOLE']: ### Aggregate according to period_agg_method dfmetric_period = ( - dfeue - .groupby([dfeue.index.year, dfeue.index.month, dfeue.index.day]) + dfmetric + .groupby([dfmetric.index.year, dfmetric.index.month, dfmetric.index.day]) .agg(period_agg_method) .rename_axis(['y','m','d']) ) @@ -84,13 +84,13 @@ def get_eue_periods( os.path.join( case,'ReEDS_Augur','augur_data',f'pras_load_{t}.h5') ).rename(columns=rmap).groupby(level=0, axis=1).sum() - dfload.index = dfeue.index + dfload.index = dfmetric.index ### Recalculate NEUE [ppm] and aggregate appropriately if period_agg_method == 'sum': dfmetric_period = ( - dfeue - .groupby([dfeue.index.year, dfeue.index.month, dfeue.index.day]) + dfmetric + .groupby([dfmetric.index.year, dfmetric.index.month, dfmetric.index.day]) .agg(period_agg_method) .rename_axis(['y','m','d']) ) / ( @@ -101,8 +101,8 @@ def get_eue_periods( ) * 1e6 elif period_agg_method == 'max': dfmetric_period = ( - (dfeue / dfload) - .groupby([dfeue.index.year, dfeue.index.month, dfeue.index.day]) + (dfmetric / dfload) + .groupby([dfmetric.index.year, dfmetric.index.month, dfmetric.index.day]) .agg(period_agg_method) .rename_axis(['y','m','d']) ) * 1e6 @@ -122,4 +122,4 @@ def get_eue_periods( for d in dfmetric_top.index.values ] - return dfmetric_top + return dfmetric_top \ No newline at end of file From 42f38c5aba4b2a8d8c351114b8183931529c9bd7 Mon Sep 17 00:00:00 2001 From: Abdelrahman Ayad Date: Mon, 4 May 2026 00:22:38 -0600 Subject: [PATCH 04/31] update stress metrics in cases.csv --- cases.csv | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cases.csv b/cases.csv index 9d0752e7..d17137f0 100644 --- a/cases.csv +++ b/cases.csv @@ -260,14 +260,16 @@ GSw_PRM_NetImportLimit,Turn on (1) or off (0) the eq_firm_transfer_limit constra GSw_PRM_NetImportLimitScen,/-delimited list of {year}_{max percent import OR 'hist' for historical percent OR 'histmax' for max historical percent across all regions} values to use in eq_firm_transfer_limit,N/A,2031_hist/2050_100, GSw_PRM_scenario,"column of inputs/reserves/prm_annual.csv from which to draw the PRM (""nerc"" means the annual values from the 2024 NERC LTRA; ""static"" means the 2024 values from the same source); or a float (like 0.12) which gets applied as a fraction to all regions in all years (0.12 means 12%)",(static|nerc|ramp2025_20by50|^\d*\.?\d+$),0.12, GSw_PRM_StressIncrement,How many stress periods to add per iteration,int,2, -GSw_PRM_StressIterateMax,Max number of times to iterate on a given solve year to achieve GSw_PRM_StressThreshold when using stress periods (0 means don't iterate; 1 means 1 extra ReEDS run; etc),int,5, +GSw_PRM_StressIterateMax,Max number of times to iterate on a given solve year to achieve adequacy stress levels (e.g., GSw_PRM_StressThresholdNEUE) when using stress periods (0 means don't iterate; 1 means 1 extra ReEDS run; etc),int,5, GSw_PRM_StressLoadAggMethod,How to aggregate load for stress periods within the chunks specified by GSw_HourlyChunkLengthStress (only used if GSw_HourlyChunkAggMethod=mean; otherwise GSw_HourlyChunkAggMethod is used),mean; max,max, GSw_PRM_StressModel,Model used to identify stress periods: pras or a string starting with 'user' which specifies a file at inputs/temporal/stressperiods_{GSw_PRM_StressModel}.csv,N/A,pras, GSw_PRM_StressOutages,Whether to apply the availability factor (forced + scheduled outages) during stress periods,0; 1,1, GSw_PRM_StressSeedLoadLevel,Region hierarchy level at which to include peak coincident load days as seeded stress periods (or False to ignore peaks),false; False; FALSE; r; nercr; transreg; transgrp; cendiv; st; interconnect; country; usda_region; ccreg,transgrp, GSw_PRM_StressSeedMinRElevel,Region hierarchy level at which to include minimum wind and solar capacity factor days as seeded stress periods (or False to ignore min-wind/solar CF days),false; False; FALSE; r; nercr; transreg; transgrp; cendiv; st; interconnect; country; usda_region; ccreg,interconnect, GSw_PRM_StressStorageCutoff,"How to select ""shoulder"" stress periods giving storage time to recharge before/after high-unserved-energy periods. Two-part switch separated by _. The first argument is ""EUE"" or ""capacity"" or ""absolute"". If ""EUE"" the second argument specifies a PRAS storage headspace / EUE threshold [fraction]; if ""cap"" it specifies a headspace / storage capacity threshold [fraction]; if ""abs"" it specifies absolute number of periods before/after [integer]. Turned off if set to ""off"".",N/A,EUE_0.1, -GSw_PRM_StressThreshold,/-delimited list of annual NEUE level [ppm] above which to re-solve the latest model year with new stress periods; formulated as HierarchyLevel_NEUEppm_StressMetric_PeriodAggMethod where HierarchyLevel is a column in hierarchy.csv; NEUEppm is normalized expected unserved energy in parts per million; StressMetric is EUE or NEUE (only used in period selection); PeriodAggMethod is 'sum' or 'max' over the hours in each period (only used in period selection) (see README.md for detailed notes),N/A,transgrp_1_EUE_sum, +GSw_PRM_StressThresholdMetrics,/-delimited list of metrics for identifying stress periods,N/A,EUE/LOLE, +GSw_PRM_StressThresholdLOLE,/-delimited list of annual LOLE above which to re-solve the latest model year with new stress periods; formulated as HierarchyLevel_nLOLE_StressMetric_PeriodAggMethod where HierarchyLevel is a column in hierarchy.csv; nLOLE is number of loss of load events; StressMetric is LOLE; PeriodAggMethod is 'sum' or 'max' over the hours in each period (only used in period selection) (see README.md for detailed notes),N/A,transgrp_10_LOLE_sum, +GSw_PRM_StressThresholdEUE,/-delimited list of annual NEUE level [ppm] above which to re-solve the latest model year with new stress periods; formulated as HierarchyLevel_NEUEppm_StressMetric_PeriodAggMethod where HierarchyLevel is a column in hierarchy.csv; NEUEppm is normalized expected unserved energy in parts per million; StressMetric is EUE or NEUE (only used in period selection); PeriodAggMethod is 'sum' or 'max' over the hours in each period (only used in period selection) (see README.md for detailed notes),N/A,transgrp_1_EUE_sum, GSw_PRM_UpdateFraction,Fraction to add to the PRM if a region fails RA threshold (only used if GSw_PRM_UpdateMethod = 1),float,0.02, GSw_PRM_UpdateMethod,Option to update PRM: (0) no update; (1) static update set by GSw_PRM_UpdateFraction; (2) dynamic update informed by PRAS; (3) dynamic update but only after all new stress periods have been added,0; 1; 2; 3,0, GSw_PRMTRADE_level,hierarchy level within which to allow PRM trading,r; nercr; transreg; transgrp; cendiv; st; interconnect; country; usda_region,country, From 62e0be2daad1b5435e0bad6444a30fce61256d8c Mon Sep 17 00:00:00 2001 From: Abdelrahman Ayad Date: Mon, 4 May 2026 14:21:36 -0600 Subject: [PATCH 05/31] add validation checks for switches --- ReEDS_Augur/stress_periods.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/ReEDS_Augur/stress_periods.py b/ReEDS_Augur/stress_periods.py index 6a9ba210..e4d038ba 100644 --- a/ReEDS_Augur/stress_periods.py +++ b/ReEDS_Augur/stress_periods.py @@ -230,12 +230,21 @@ def get_stress_metrics_sorted_periods(sw, t, iteration): ) ### Check all stress criteria; for regions that fail, add new stress periods - # _eue_sorted_periods = {} _stress_sorted_periods = {} failed = {} high_stress_periods = {} shoulder_periods = {} + # Validation check: Display any GSw_PRM_StressThreshold{metric} + # that is not specified in GSw_PRM_StressThresholdMetrics + stressThresholdMetrics = [s.split('GSw_PRM_StressThreshold')[1] + for s in sw.keys() if s.startswith('GSw_PRM_StressThreshold') + and not s.endswith('Metrics') and s.split('GSw_PRM_StressThreshold')[1] != "" + ] + for s in stressThresholdMetrics: + if s not in sw.GSw_PRM_StressThresholdMetrics.split('/'): + print(f"Warning: {s} is not included in GSw_PRM_StressThresholdMetrics, so it will not be evaluated") + # stress periods column names for writing outputs stress_metrics_units_mapping = {'EUE':'MWh', 'NEUE':'ppm', 'LOLE':'events'} stress_metrics_col_names = {m:f'{m}_{mapping}' for m,mapping @@ -243,8 +252,12 @@ def get_stress_metrics_sorted_periods(sw, t, iteration): stress_metrics_units_mapping.values())} for metric in sw.GSw_PRM_StressThresholdMetrics.split('/'): - for criterion in sw[f'GSw_PRM_StressThreshold{metric}'].split('/'): - print(f"Evaluating GSw_PRM_StressThreshold {metric} with criterion: {criterion}") + if sw[f'GSw_PRM_StressThreshold{metric.upper()}'] not in sw.keys(): + raise NotImplementedError( + f"GSw_PRM_StressThreshold{metric.upper()} not found in switches" + ) + for criterion in sw[f'GSw_PRM_StressThreshold{metric.upper()}'].split('/'): + print(f"Evaluating GSw_PRM_StressThreshold {metric.upper()} with criterion: {criterion}") ## Example: criterion = 'transgrp_10_EUE_sum' (hierarchy_level, stress_level, stress_metric, period_agg_method) = criterion.split('_') From 926d6d82999b3d27e5f72aeb5951ca3a6c0587c5 Mon Sep 17 00:00:00 2001 From: Abdelrahman Ayad Date: Mon, 4 May 2026 16:40:50 -0600 Subject: [PATCH 06/31] fix a bug when searching for the switch --- ReEDS_Augur/stress_periods.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ReEDS_Augur/stress_periods.py b/ReEDS_Augur/stress_periods.py index e4d038ba..c3a553dc 100644 --- a/ReEDS_Augur/stress_periods.py +++ b/ReEDS_Augur/stress_periods.py @@ -235,6 +235,7 @@ def get_stress_metrics_sorted_periods(sw, t, iteration): high_stress_periods = {} shoulder_periods = {} + stress_metric_switches = sw.GSw_PRM_StressThresholdMetrics.split('/') # Validation check: Display any GSw_PRM_StressThreshold{metric} # that is not specified in GSw_PRM_StressThresholdMetrics stressThresholdMetrics = [s.split('GSw_PRM_StressThreshold')[1] @@ -242,17 +243,16 @@ def get_stress_metrics_sorted_periods(sw, t, iteration): and not s.endswith('Metrics') and s.split('GSw_PRM_StressThreshold')[1] != "" ] for s in stressThresholdMetrics: - if s not in sw.GSw_PRM_StressThresholdMetrics.split('/'): + if s not in stress_metric_switches: print(f"Warning: {s} is not included in GSw_PRM_StressThresholdMetrics, so it will not be evaluated") # stress periods column names for writing outputs - stress_metrics_units_mapping = {'EUE':'MWh', 'NEUE':'ppm', 'LOLE':'events'} - stress_metrics_col_names = {m:f'{m}_{mapping}' for m,mapping - in zip(sw.GSw_PRM_StressThresholdMetrics.split('/'), - stress_metrics_units_mapping.values())} + stress_metrics_units = {'EUE':'MWh', 'NEUE':'ppm', 'LOLE':'days'} + stress_metrics_col_names = {m:f'{m}_{stress_metrics_units[m]}' for m in + stress_metric_switches} - for metric in sw.GSw_PRM_StressThresholdMetrics.split('/'): - if sw[f'GSw_PRM_StressThreshold{metric.upper()}'] not in sw.keys(): + for metric in stress_metric_switches: + if f'GSw_PRM_StressThreshold{metric.upper()}' not in sw: raise NotImplementedError( f"GSw_PRM_StressThreshold{metric.upper()} not found in switches" ) From fc03be888a99490177ad53157f7065f1035b3729 Mon Sep 17 00:00:00 2001 From: Abdelrahman Ayad Date: Mon, 4 May 2026 17:24:52 -0600 Subject: [PATCH 07/31] generalize check for stressThresholdMetrics --- runbatch.py | 59 +++++++++++++++++++++++++++++------------------------ 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/runbatch.py b/runbatch.py index 60c2e2fb..fa2ced1d 100644 --- a/runbatch.py +++ b/runbatch.py @@ -319,33 +319,38 @@ def check_compatibility(sw): reeds_path = os.path.dirname(__file__) hierarchy = reeds.io.get_hierarchy(GSw_ZoneSet=sw['GSw_ZoneSet']).reset_index() - for threshold in sw['GSw_PRM_StressThreshold'].split('/'): - ## Example: threshold = 'transgrp_10_EUE_sum' - allowed_levels = ['country','interconnect','nercr','transreg','transgrp','st','r'] - (hierarchy_level, ppm, stress_metric, period_agg_method) = threshold.split('_') - if hierarchy_level not in allowed_levels: - raise ValueError( - f"GSw_PRM_StressThreshold: level={hierarchy_level} but must be in:\n" - + '\n'.join(allowed_levels) - ) - if period_agg_method.lower() not in ['sum','max']: - raise ValueError("Fix period agg method in GSw_PRM_StressThreshold") - if not (float(ppm) >= 0): - raise ValueError( - "ppm in GSw_PRM_StressThreshold must be a positive number " - f"but '{ppm}' was provided" - ) - if stress_metric.upper() not in ['EUE','NEUE']: - raise ValueError( - "stress metric in GSw_PRM_StressThreshold must be 'EUE' or 'NEUE' " - f"but '{stress_metric}' was provided" - ) - if (sw['GSw_PRM_StressModel'].lower() != 'pras') and (stress_metric.upper() != 'EUE'): - err = ( - f"The combination of GSw_PRM_StressModel={sw['GSw_PRM_StressModel']} and " - f"stress_metric={stress_metric} is not supported." - ) - raise NotImplementedError(err) + stressThresholdMetrics = [s.split('GSw_PRM_StressThreshold')[1] + for s in sw.keys() if s.startswith('GSw_PRM_StressThreshold') + and not s.endswith('Metrics') + ] + for stress_metric in stressThresholdMetrics: + for threshold in sw[f'GSw_PRM_StressThreshold{stress_metric}'].split('/'): + ## Example: threshold = 'transgrp_10_EUE_sum' + allowed_levels = ['country','interconnect','nercr','transreg','transgrp','st','r'] + (hierarchy_level, ppm, stress_metric, period_agg_method) = threshold.split('_') + if hierarchy_level not in allowed_levels: + raise ValueError( + f"GSw_PRM_StressThreshold{stress_metric}: level={hierarchy_level} but must be in:\n" + + '\n'.join(allowed_levels) + ) + if period_agg_method.lower() not in ['sum','max']: + raise ValueError(f"Fix period agg method in GSw_PRM_StressThreshold{stress_metric}") + if not (float(ppm) >= 0): + raise ValueError( + f"ppm in GSw_PRM_StressThreshold{stress_metric} must be a positive number " + f"but '{ppm}' was provided" + ) + if stress_metric.upper() not in ['EUE','NEUE', 'LOLE']: + raise ValueError( + f"stress metric in GSw_PRM_StressThreshold{stress_metric} must be 'EUE', 'NEUE', or 'LOLE' " + f"but '{stress_metric}' was provided" + ) + if (sw['GSw_PRM_StressModel'].lower() != 'pras') and (stress_metric.upper() != 'EUE'): + err = ( + f"The combination of GSw_PRM_StressModel={sw['GSw_PRM_StressModel']} and " + f"stress_metric={stress_metric} is not supported." + ) + raise NotImplementedError(err) if sw['GSw_PRM_StressStorageCutoff'].lower() not in ['off','0','false']: metric, value = sw['GSw_PRM_StressStorageCutoff'].split('_') From d67e2376d7f011fe58ab7a1f46480015e2be5d9f Mon Sep 17 00:00:00 2001 From: Abdelrahman Ayad Date: Mon, 4 May 2026 17:25:14 -0600 Subject: [PATCH 08/31] minor comment edit --- reeds/ra.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reeds/ra.py b/reeds/ra.py index 70409fa4..3da5e05c 100644 --- a/reeds/ra.py +++ b/reeds/ra.py @@ -26,7 +26,7 @@ def get_pras_stress_metric(case, t, iteration=0, stress_metric='EUE'): c for c in dfpras if (c.endswith(metric_tail) and not c.startswith('USA')) ]].copy() - ## Drop the tailing _EUE + ## Drop the tailing metric tail dfmetric = dfmetric.rename( columns=dict(zip(dfmetric.columns, [c[:-len(metric_tail)] for c in dfmetric]))) From 064828be0779854921b58b02b50770906e590a11 Mon Sep 17 00:00:00 2001 From: Abdelrahman Ayad Date: Tue, 5 May 2026 08:58:26 -0600 Subject: [PATCH 09/31] comment stress_periods debugging code --- ReEDS_Augur/stress_periods.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ReEDS_Augur/stress_periods.py b/ReEDS_Augur/stress_periods.py index c3a553dc..24af3fa3 100644 --- a/ReEDS_Augur/stress_periods.py +++ b/ReEDS_Augur/stress_periods.py @@ -654,12 +654,12 @@ def main(sw, t, iteration=0, logging=True): # main(sw, t, iteration, logging=False) #%%### option to run script directly for debugging - casedir = "/Users/aayad/Work/repos/ReEDS/runs/v20260419_224354_Pacific" - t = 2020 # previous solve year - iteration = 0 + # casedir = "/Users/aayad/Work/repos/ReEDS/runs/v20260419_224354_Pacific" + # t = 2020 # previous solve year + # iteration = 0 # load switches - sw = reeds.io.get_switches(casedir) - sw['t'] = t - sw['GSw_PRM_UpdateMethod'] = 2 - #%%### - main(sw, t, iteration, logging=False) + # sw = reeds.io.get_switches(casedir) + # sw['t'] = t + # sw['GSw_PRM_UpdateMethod'] = 2 + # %%### + # main(sw, t, iteration, logging=False) From c2c9cc5ae3aff4869bfcb690172f6b17c7cbfd3c Mon Sep 17 00:00:00 2001 From: Abdelrahman Ayad Date: Tue, 19 May 2026 16:18:44 -0600 Subject: [PATCH 10/31] refactor stress_periods; get_shoulder periods uses capacity only (not eue) --- ReEDS_Augur/stress_periods.py | 250 ++++++++++++++++++---------------- 1 file changed, 132 insertions(+), 118 deletions(-) diff --git a/ReEDS_Augur/stress_periods.py b/ReEDS_Augur/stress_periods.py index 24af3fa3..f205376c 100644 --- a/ReEDS_Augur/stress_periods.py +++ b/ReEDS_Augur/stress_periods.py @@ -22,9 +22,8 @@ # import importlib # importlib.reload(functions) - #%%### Functions -def plot_stress_diagnostics(sw, t, iteration, high_stress_periods, stress_metric='EUE'): +def plot_stress_diagnostics(sw, t, iteration, high_stress_periods): try: dates = ( pd.concat(high_stress_periods) @@ -131,7 +130,6 @@ def get_annual_stress_metric(case, t, stress_metric, iteration=0): return metric - def get_shoulder_periods(sw, criterion, dfenergy_r, high_eue_periods): ## Stop if not needed if sw.GSw_PRM_StressStorageCutoff.lower() in ['off', '0', 'false']: @@ -166,17 +164,12 @@ def get_shoulder_periods(sw, criterion, dfenergy_r, high_eue_periods): for i, row in high_eue_periods[criterion, f'high_{stress_metric}'].iterrows(): if row.r not in dfheadspace_MWh: continue - print(f"Evaluating shoulder periods for {row.name} ({row.r}) with {stress_metric} = {row[stress_metric]:.2f}") day = pd.Timestamp('-'.join(row[['y','m','d']].astype(str).tolist())) - start_headspace_MWh = dfheadspace_MWh.loc[day.strftime('%Y-%m-%d'),row.r].iloc[0] - end_headspace_MWh = dfheadspace_MWh.loc[day.strftime('%Y-%m-%d'),row.r].iloc[-1] - start_headspace_frac = dfheadspace_frac.loc[day.strftime('%Y-%m-%d'),row.r].iloc[0] end_headspace_frac = dfheadspace_frac.loc[day.strftime('%Y-%m-%d'),row.r].iloc[-1] - day_eue = high_eue_periods[criterion, f'high_{stress_metric}'].loc[i,'EUE'] day_index = np.where( timeindex == dfenergy_agg.loc[day.strftime('%Y-%m-%d')].iloc[0].name )[0][0] @@ -185,8 +178,7 @@ def get_shoulder_periods(sw, criterion, dfenergy_r, high_eue_periods): day_after = timeindex[(day_index + periodhours) % len(timeindex)] if ( - ((cutofftype == 'eue') and (end_headspace_MWh / day_eue >= float(cutoff))) - or ((cutofftype[:3] == 'cap') and (end_headspace_frac >= float(cutoff))) + (cutofftype[:3] == 'cap') and (end_headspace_frac >= float(cutoff)) or (cutofftype[:3] == 'abs') ): shoulder_periods[criterion, f'after_{row.name}'] = pd.Series({ @@ -196,8 +188,7 @@ def get_shoulder_periods(sw, criterion, dfenergy_r, high_eue_periods): print(f"Added {day_after} as shoulder stress period after {day}") if ( - ((cutofftype == 'eue') and (start_headspace_MWh / day_eue >= float(cutoff))) - or ((cutofftype[:3] == 'cap') and (start_headspace_frac >= float(cutoff))) + (cutofftype[:3] == 'cap') and (start_headspace_frac >= float(cutoff)) or (cutofftype[:3] == 'abs') ): shoulder_periods[criterion, f'before_{row.name}'] = pd.Series({ @@ -208,7 +199,83 @@ def get_shoulder_periods(sw, criterion, dfenergy_r, high_eue_periods): return shoulder_periods +def _evaluate_stress_threshold_criterion(stress_criteria, criterion, sw, t, iteration, dfenergy_r, stressperiods_this_iteration): + _stress_sorted_periods = stress_criteria['stress_sorted_periods'] + _failed = stress_criteria['failed'] + _high_stress_periods = stress_criteria['high_stress_periods'] + _shoulder_periods = stress_criteria['shoulder_periods'] + + ## Example: criterion = 'transgrp_10_EUE_sum' + (hierarchy_level, stress_level, stress_metric, period_agg_method) = criterion.split('_') + + ### Get stored stress metric + stress_vals = pd.read_csv( + os.path.join(sw.casedir, 'outputs', f'{stress_metric}_{t}i{iteration}.csv'), + index_col=['level', 'metric', 'region'], + ).squeeze(1) + + stress_periods = reeds.ra.get_stress_metric_periods( + case=sw.casedir, t=t, iteration=iteration, + hierarchy_level=hierarchy_level, + stress_metric=stress_metric, + period_agg_method=period_agg_method, + ) + + ### Sort in descending stress_metric order + _stress_sorted_periods[criterion] = ( + stress_periods + .sort_values(stress_metric, ascending=False) + .reset_index().set_index('actual_period') + ) + + ### Get the threshold(s) and see if any of them failed + this_test = stress_vals[hierarchy_level][period_agg_method] + + if (this_test > float(stress_level)).any(): + _failed[criterion] = this_test.loc[this_test > float(stress_level)] + print(f"GSw_PRM_StressThreshold = {criterion} failed for:") + print(_failed[criterion]) + ###### Add GSw_PRM_StressIncrement periods to the list for the next iteration + _high_stress_periods[criterion, f'high_{stress_metric}'] = ( + _stress_sorted_periods[criterion].loc[ + ## Only include new stress periods for the region(s) that failed + _stress_sorted_periods[criterion].r.isin(_failed[criterion].index) + ## Don't repeat existing stress periods + & ~(_stress_sorted_periods[criterion].index.isin( + stressperiods_this_iteration.actual_period)) + ] + ## Don't add dates more than once + .drop_duplicates(subset=['y','m','d']) + ## Keep the GSw_PRM_StressIncrement worst periods for each region. + ## If you instead want to keep the GSw_PRM_StressIncrement worst periods + ## overall, use .nlargest(int(sw.GSw_PRM_StressIncrement), stress_metric) + .groupby('r').head(int(sw.GSw_PRM_StressIncrement)) + ) + for period, row in _high_stress_periods[criterion, f'high_{stress_metric}'].iterrows(): + print( + f"Added {period} " + f"({reeds.timeseries.h2timestamp(period).strftime('%Y-%m-%d')}) " + f"as stress period for {row.r} " + f"({stress_metric} = {row[stress_metric]})" + ) + + ### Include "shoulder periods" before or after each period + ### if the storage state of charge is low + _shoulder_periods = { + **_shoulder_periods, + **get_shoulder_periods(sw, criterion, dfenergy_r, _high_stress_periods) + } + + stress_criteria['stress_sorted_periods'] = _stress_sorted_periods + stress_criteria['failed'] = _failed + stress_criteria['high_stress_periods'] = _high_stress_periods + stress_criteria['shoulder_periods'] = _shoulder_periods + + else: + print(f"GSw_PRM_StressThreshold = {criterion} passed") + return stress_criteria + def get_stress_metrics_sorted_periods(sw, t, iteration): ### Get storage state of charge (SOC) to use in selection of "shoulder" stress periods dfenergy = reeds.io.read_pras_results( @@ -230,18 +297,16 @@ def get_stress_metrics_sorted_periods(sw, t, iteration): ) ### Check all stress criteria; for regions that fail, add new stress periods - _stress_sorted_periods = {} - failed = {} - high_stress_periods = {} - shoulder_periods = {} + stress_criteria = {'stress_sorted_periods': {}, 'failed': {}, 'high_stress_periods': {}, 'shoulder_periods': {}} - stress_metric_switches = sw.GSw_PRM_StressThresholdMetrics.split('/') # Validation check: Display any GSw_PRM_StressThreshold{metric} # that is not specified in GSw_PRM_StressThresholdMetrics + stress_metric_switches = sw.GSw_PRM_StressThresholdMetrics.split('/') stressThresholdMetrics = [s.split('GSw_PRM_StressThreshold')[1] for s in sw.keys() if s.startswith('GSw_PRM_StressThreshold') - and not s.endswith('Metrics') and s.split('GSw_PRM_StressThreshold')[1] != "" + and not s.endswith('Metrics') ] + stressThresholdMetrics.remove('') for s in stressThresholdMetrics: if s not in stress_metric_switches: print(f"Warning: {s} is not included in GSw_PRM_StressThresholdMetrics, so it will not be evaluated") @@ -250,84 +315,25 @@ def get_stress_metrics_sorted_periods(sw, t, iteration): stress_metrics_units = {'EUE':'MWh', 'NEUE':'ppm', 'LOLE':'days'} stress_metrics_col_names = {m:f'{m}_{stress_metrics_units[m]}' for m in stress_metric_switches} - + for metric in stress_metric_switches: - if f'GSw_PRM_StressThreshold{metric.upper()}' not in sw: - raise NotImplementedError( - f"GSw_PRM_StressThreshold{metric.upper()} not found in switches" - ) - for criterion in sw[f'GSw_PRM_StressThreshold{metric.upper()}'].split('/'): + for criterion in sw[f'GSw_PRM_StressThreshold{metric.upper()}'].split('/'): print(f"Evaluating GSw_PRM_StressThreshold {metric.upper()} with criterion: {criterion}") - ## Example: criterion = 'transgrp_10_EUE_sum' - (hierarchy_level, stress_level, stress_metric, period_agg_method) = criterion.split('_') - - ### Get stored stress metric - stress_vals = pd.read_csv( - os.path.join(sw.casedir, 'outputs', f'{stress_metric}_{t}i{iteration}.csv'), - index_col=['level', 'metric', 'region'], - ).squeeze(1) - - stress_periods = reeds.ra.get_stress_metric_periods( - case=sw.casedir, t=t, iteration=iteration, - hierarchy_level=hierarchy_level, - stress_metric=stress_metric, - period_agg_method=period_agg_method, - ) - - ### Sort in descending stress_metric order - _stress_sorted_periods[criterion] = ( - stress_periods - .sort_values(stress_metric, ascending=False) - .reset_index().set_index('actual_period') - ) - - ### Get the threshold(s) and see if any of them failed - this_test = stress_vals[hierarchy_level][period_agg_method] - - if (this_test > float(stress_level)).any(): - failed[criterion] = this_test.loc[this_test > float(stress_level)] - print(f"GSw_PRM_StressThreshold = {criterion} failed for:") - print(failed[criterion]) - ###### Add GSw_PRM_StressIncrement periods to the list for the next iteration - high_stress_periods[criterion, f'high_{stress_metric}'] = ( - _stress_sorted_periods[criterion].loc[ - ## Only include new stress periods for the region(s) that failed - _stress_sorted_periods[criterion].r.isin(failed[criterion].index) - ## Don't repeat existing stress periods - & ~(_stress_sorted_periods[criterion].index.isin( - stressperiods_this_iteration.actual_period)) - ] - ## Don't add dates more than once - .drop_duplicates(subset=['y','m','d']) - ## Keep the GSw_PRM_StressIncrement worst periods for each region. - ## If you instead want to keep the GSw_PRM_StressIncrement worst periods - ## overall, use .nlargest(int(sw.GSw_PRM_StressIncrement), stress_metric) - .groupby('r').head(int(sw.GSw_PRM_StressIncrement)) - ) - for period, row in high_stress_periods[criterion, f'high_{stress_metric}'].iterrows(): - print( - f"Added {period} " - f"({reeds.timeseries.h2timestamp(period).strftime('%Y-%m-%d')}) " - f"as stress period for {row.r} " - f"({stress_metric} = {row[stress_metric]})" - ) - - ### Include "shoulder periods" before or after each period - ### if the storage state of charge is low - ### Check only if stress metric is EUE - if stress_metric.upper() == 'EUE': - shoulder_periods = { - **shoulder_periods, - **get_shoulder_periods(sw, criterion, dfenergy_r, high_stress_periods) - } - - ### Dealing with earlier criteria may also address later criteria, so stop here - break - - else: - print(f"GSw_PRM_StressThreshold = {criterion} passed") - - stress_sorted_periods = pd.concat(_stress_sorted_periods, names=['criterion']) + stress_criteria = _evaluate_stress_threshold_criterion(stress_criteria, + criterion, + sw, + t, + iteration, + dfenergy_r, + stressperiods_this_iteration, + ) + + stress_sorted_periods = stress_criteria['stress_sorted_periods'] + failed = stress_criteria['failed'] + high_stress_periods = stress_criteria['high_stress_periods'] + shoulder_periods = stress_criteria['shoulder_periods'] + + stress_sorted_periods = pd.concat(stress_sorted_periods, names=['criterion']) ### Get lists of stress periods: new (added this iteration) and all ##TODO: What if same high stress period is added for multiple criteria? @@ -370,17 +376,16 @@ def get_stress_metrics_sorted_periods(sw, t, iteration): ### Tables and plots for debugging stress_sorted_periods.round(2).rename(columns=stress_metrics_col_names).to_csv( - os.path.join(sw.casedir, 'inputs_case', newstresspath, f'{stress_metric}_sorted_periods.csv') + os.path.join(sw.casedir, 'inputs_case', newstresspath, 'stress_metrics_sorted_periods.csv') ) new_stress_periods.round(2).rename(columns=stress_metrics_col_names).to_csv( os.path.join(sw.casedir, 'inputs_case', newstresspath, 'new_stress_periods.csv'), index=False, ) - plot_stress_diagnostics(sw, t, iteration, high_stress_periods, stress_metric=stress_metric) + plot_stress_diagnostics(sw, t, iteration, high_stress_periods) return failed, new_stressperiods_write, combined_periods_write - def prm_increment_pras(sw, t, iteration, combined_periods_write, failed_regions): try: hmap = pd.read_csv( @@ -503,7 +508,7 @@ def prm_increment_pras(sw, t, iteration, combined_periods_write, failed_regions) return prm_increment -def update_prm(sw, t, iteration, failed, combined_periods_write, metric): +def update_prm(sw, t, iteration, failed, combined_periods_write, stress_metrics): """Update the energy reserve margin by region r for stress periods, either using a static increment (GSw_PRM_UpdateMethod=1) or based on the estimated surplus needed by PRAS to recover the desired reliabiliaty criteria (GSw_PRM_UpdateMethod>1). @@ -515,7 +520,8 @@ def update_prm(sw, t, iteration, failed, combined_periods_write, metric): failed (dict): Dictionary of regions with unserved energy at the hierarchy_level and their criterion evaluations combined_periods_write (pd.DataFrame): Data frame of combined stress periods - metric (str): Stress metric for this update + stress_metrics (list): List of stress metrics. If EUE is not included, + we can only use Fixed-increment update Returns: pd.DataFrame: Table of prm levels for the next PRAS iteration @@ -541,7 +547,13 @@ def update_prm(sw, t, iteration, failed, combined_periods_write, metric): ) ## Fixed-increment update - if int(sw.GSw_PRM_UpdateMethod) == 1: + if int(sw.GSw_PRM_UpdateMethod) == 1 or 'EUE' not in stress_metrics: + if int(sw.GSw_PRM_UpdateMethod) > 1: + print( + f"Warning: GSw_PRM_UpdateMethod is set to {sw.GSw_PRM_UpdateMethod}, " + "but EUE is not included in GSw_PRM_StressThresholdMetrics, so defaulting to fixed increment. " + "Add EUE to GSw_PRM_StressThresholdMetrics to use PRAS-informed update method." + ) prm_increment = failed_regions.copy() prm_increment['fraction'] = float(sw['GSw_PRM_UpdateFraction']) ## PRAS-informed PRM update @@ -581,12 +593,14 @@ def main(sw, t, iteration=0, logging=True): try: ## TODO: Check whether to keep _neue_simple or not. # _neue_simple = get_and_write_neue(sw, write=True) - for stress_metric in sw.GSw_PRM_StressThresholdMetrics.split('/'): + # Check if EUE is added, if not add it since it's needed for selection of shoulder stress periods + stress_metrics = sw.GSw_PRM_StressThresholdMetrics.split('/') + for stress_metric in stress_metrics: print(f"Calculating and writing annual {stress_metric} for iteration {iteration}") dfmetric = get_annual_stress_metric(sw.casedir, t, stress_metric, iteration=iteration) dfmetric.round(2).to_csv( os.path.join(sw.casedir, 'outputs', f"{stress_metric}_{t}i{iteration}.csv") - ) + ) except Exception as err: if int(sw['pras']) == 2: @@ -631,7 +645,7 @@ def main(sw, t, iteration=0, logging=True): index_col='*r', ) else: - prm_next_iteration = update_prm(sw, t, iteration, failed, combined_periods_write) + prm_next_iteration = update_prm(sw, t, iteration, failed, combined_periods_write, stress_metrics) prm_next_iteration.to_csv( os.path.join(sw.casedir, 'inputs_case', newstresspath, 'prm.csv'), @@ -642,24 +656,24 @@ def main(sw, t, iteration=0, logging=True): # if __name__ == '__main__': -# #%%### option to run script directly for debugging -# casedir = "/path/to/ReEDS-2.0/runs/runname" -# t = 2030 # previous solve year -# iteration = 0 -# # load switches -# sw = reeds.io.get_switches(casedir) -# sw['t'] = t -# sw['GSw_PRM_UpdateMethod'] = 2 -# #%%### -# main(sw, t, iteration, logging=False) - #%%### option to run script directly for debugging - # casedir = "/Users/aayad/Work/repos/ReEDS/runs/v20260419_224354_Pacific" - # t = 2020 # previous solve year + # casedir = "/path/to/ReEDS-2.0/runs/runname" + # t = 2030 # previous solve year # iteration = 0 - # load switches + # # load switches # sw = reeds.io.get_switches(casedir) # sw['t'] = t # sw['GSw_PRM_UpdateMethod'] = 2 - # %%### + # #%%### # main(sw, t, iteration, logging=False) + + # %%### option to run script directly for debugging + casedir = "/Users/aayad/Work/repos/ReEDS/runs/v20260419_224354_Pacific" + t = 2020 # previous solve year + iteration = 0 + # load switches + sw = reeds.io.get_switches(casedir) + sw['t'] = t + sw['GSw_PRM_UpdateMethod'] = 2 + # %%### + main(sw, t, iteration, logging=False) From 52bdfdd3a02253bcf84a6102fd5380852373e961 Mon Sep 17 00:00:00 2001 From: Abdelrahman Ayad Date: Tue, 19 May 2026 16:19:03 -0600 Subject: [PATCH 11/31] remove debugging csv output --- reeds/ra.py | 1 - 1 file changed, 1 deletion(-) diff --git a/reeds/ra.py b/reeds/ra.py index 3da5e05c..f6bc64c7 100644 --- a/reeds/ra.py +++ b/reeds/ra.py @@ -15,7 +15,6 @@ def get_pras_stress_metric(case, t, iteration=0, stress_metric='EUE'): dfpras = reeds.io.read_pras_results( os.path.join(case, 'ReEDS_Augur', 'PRAS', f"PRAS_{t}i{iteration}.h5") ) - dfpras.to_csv('pras_results.csv') ### Create the time index sw = reeds.io.get_switches(case) dfpras.index = reeds.timeseries.get_timeindex(sw['resource_adequacy_years']) From b632dad04e755ced15d87d8034ebaa138e6d0c18 Mon Sep 17 00:00:00 2001 From: Abdelrahman Ayad Date: Tue, 19 May 2026 16:19:48 -0600 Subject: [PATCH 12/31] minor fix --- cases.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cases.csv b/cases.csv index a28d8190..2d12838c 100644 --- a/cases.csv +++ b/cases.csv @@ -261,7 +261,7 @@ GSw_PRM_NetImportLimit,Turn on (1) or off (0) the eq_firm_transfer_limit constra GSw_PRM_NetImportLimitScen,/-delimited list of {year}_{max percent import OR 'hist' for historical percent OR 'histmax' for max historical percent across all regions} values to use in eq_firm_transfer_limit,N/A,2031_hist/2050_100, GSw_PRM_scenario,"column of inputs/reserves/prm_annual.csv from which to draw the PRM (""nerc"" means the annual values from the 2024 NERC LTRA; ""static"" means the 2024 values from the same source); or a float (like 0.12) which gets applied as a fraction to all regions in all years (0.12 means 12%)",(static|nerc|ramp2025_20by50|^\d*\.?\d+$),0.12, GSw_PRM_StressIncrement,How many stress periods to add per iteration,int,2, -GSw_PRM_StressIterateMax,Max number of times to iterate on a given solve year to achieve adequacy stress levels (e.g., GSw_PRM_StressThresholdNEUE) when using stress periods (0 means don't iterate; 1 means 1 extra ReEDS run; etc),int,5, +GSw_PRM_StressIterateMax,"Max number of times to iterate on a given solve year to achieve adequacy stress levels (e.g., GSw_PRM_StressThresholdNEUE) when using stress periods (0 means don't iterate; 1 means 1 extra ReEDS run; etc)",int,5, GSw_PRM_StressLoadAggMethod,How to aggregate load for stress periods within the chunks specified by GSw_HourlyChunkLengthStress (only used if GSw_HourlyChunkAggMethod=mean; otherwise GSw_HourlyChunkAggMethod is used),mean; max,max, GSw_PRM_StressModel,Model used to identify stress periods: pras or a string starting with 'user' which specifies a file at inputs/temporal/stressperiods_{GSw_PRM_StressModel}.csv,N/A,pras, GSw_PRM_StressOutages,Whether to apply the availability factor (forced + scheduled outages) during stress periods,0; 1,1, From 558e38462eac2da0c517424cb2f5027fe009784f Mon Sep 17 00:00:00 2001 From: Abdelrahman Ayad Date: Tue, 19 May 2026 16:20:34 -0600 Subject: [PATCH 13/31] warning and error messages for `stressThresholdMetricSwitches` --- runbatch.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/runbatch.py b/runbatch.py index fa2ced1d..e08e0eb1 100644 --- a/runbatch.py +++ b/runbatch.py @@ -319,12 +319,26 @@ def check_compatibility(sw): reeds_path = os.path.dirname(__file__) hierarchy = reeds.io.get_hierarchy(GSw_ZoneSet=sw['GSw_ZoneSet']).reset_index() - stressThresholdMetrics = [s.split('GSw_PRM_StressThreshold')[1] + ### Check that the stress metrics specified in GSw_PRM_StressThresholdMetrics + # have corresponding GSw_PRM_StressThreshold{metric} switches + stressThresholdMetricSwitches = [s.split('GSw_PRM_StressThreshold')[1] for s in sw.keys() if s.startswith('GSw_PRM_StressThreshold') - and not s.endswith('Metrics') + and not s.endswith('Metrics') ] - for stress_metric in stressThresholdMetrics: - for threshold in sw[f'GSw_PRM_StressThreshold{stress_metric}'].split('/'): + stressTresholdMetricControls = sw['GSw_PRM_StressThresholdMetrics'].split('/') + + # Metric control but not defined as switch + for metric in stressTresholdMetricControls: + if metric.upper() not in stressThresholdMetricSwitches: + raise NotImplementedError(f"GSw_PRM_StressThreshold{metric.upper()} is not defined as a switch") + + # Metric switch but not defined in control + for metric in stressThresholdMetricSwitches: + if metric.upper() not in stressTresholdMetricControls: + raise NotImplementedError(f"GSw_PRM_StressThreshold{metric.upper()} is added but not found in GSw_PRM_StressThresholdMetrics") + + for metric in stressThresholdMetricSwitches: + for threshold in sw[f'GSw_PRM_StressThreshold{metric}'].split('/'): ## Example: threshold = 'transgrp_10_EUE_sum' allowed_levels = ['country','interconnect','nercr','transreg','transgrp','st','r'] (hierarchy_level, ppm, stress_metric, period_agg_method) = threshold.split('_') @@ -340,7 +354,7 @@ def check_compatibility(sw): f"ppm in GSw_PRM_StressThreshold{stress_metric} must be a positive number " f"but '{ppm}' was provided" ) - if stress_metric.upper() not in ['EUE','NEUE', 'LOLE']: + if stress_metric.upper() not in ['EUE','NEUE','LOLE']: raise ValueError( f"stress metric in GSw_PRM_StressThreshold{stress_metric} must be 'EUE', 'NEUE', or 'LOLE' " f"but '{stress_metric}' was provided" From 37703fe0870c0d09b7e1145e5748b247db7177b9 Mon Sep 17 00:00:00 2001 From: Abdelrahman Ayad Date: Tue, 19 May 2026 16:32:02 -0600 Subject: [PATCH 14/31] comment debugging lines --- ReEDS_Augur/stress_periods.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/ReEDS_Augur/stress_periods.py b/ReEDS_Augur/stress_periods.py index f205376c..88ebc5c1 100644 --- a/ReEDS_Augur/stress_periods.py +++ b/ReEDS_Augur/stress_periods.py @@ -668,12 +668,12 @@ def main(sw, t, iteration=0, logging=True): # main(sw, t, iteration, logging=False) # %%### option to run script directly for debugging - casedir = "/Users/aayad/Work/repos/ReEDS/runs/v20260419_224354_Pacific" - t = 2020 # previous solve year - iteration = 0 - # load switches - sw = reeds.io.get_switches(casedir) - sw['t'] = t - sw['GSw_PRM_UpdateMethod'] = 2 - # %%### - main(sw, t, iteration, logging=False) + # casedir = "/Users/aayad/Work/repos/ReEDS/runs/v20260419_224354_Pacific" + # t = 2020 # previous solve year + # iteration = 0 + # # load switches + # sw = reeds.io.get_switches(casedir) + # sw['t'] = t + # sw['GSw_PRM_UpdateMethod'] = 2 + # # %%### + # main(sw, t, iteration, logging=False) From 50fae675e04ebe5c7ef0e733780d5b2a76270592 Mon Sep 17 00:00:00 2001 From: Abdelrahman Ayad Date: Tue, 19 May 2026 17:19:03 -0600 Subject: [PATCH 15/31] update get_pras_stress_metric call --- reeds/resource_adequacy/stress_periods.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/reeds/resource_adequacy/stress_periods.py b/reeds/resource_adequacy/stress_periods.py index c0a1cb67..cce91ef2 100644 --- a/reeds/resource_adequacy/stress_periods.py +++ b/reeds/resource_adequacy/stress_periods.py @@ -203,7 +203,7 @@ def get_annual_stress_metric(case, t, stress_metric, iteration=0): """ """ ### Get values from PRAS - dfmetric = reeds.ra.get_pras_stress_metric(case=case, t=t, iteration=iteration, stress_metric=stress_metric) + dfmetric = get_pras_stress_metric(case=case, t=t, iteration=iteration, stress_metric=stress_metric) ### Get load (for calculating NEUE) if stress_metric.upper() == 'NEUE': @@ -325,7 +325,7 @@ def _evaluate_stress_threshold_criterion(stress_criteria, criterion, sw, t, iter index_col=['level', 'metric', 'region'], ).squeeze(1) - stress_periods = reeds.ra.get_stress_metric_periods( + stress_periods = get_stress_metric_periods( case=sw.casedir, t=t, iteration=iteration, hierarchy_level=hierarchy_level, stress_metric=stress_metric, From 10f7ba5efdbee149b0c41041b898c5cd8786d906 Mon Sep 17 00:00:00 2001 From: Abdelrahman Ayad Date: Tue, 19 May 2026 17:52:37 -0600 Subject: [PATCH 16/31] replace ReEDS_Augur by handoff dir name --- reeds/resource_adequacy/stress_periods.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/reeds/resource_adequacy/stress_periods.py b/reeds/resource_adequacy/stress_periods.py index cce91ef2..49170fc6 100644 --- a/reeds/resource_adequacy/stress_periods.py +++ b/reeds/resource_adequacy/stress_periods.py @@ -23,7 +23,7 @@ def get_pras_stress_metric(case, t, iteration=0, stress_metric='EUE'): """ ### Get PRAS outputs dfpras = reeds.io.read_pras_results( - os.path.join(case, 'ReEDS_Augur', 'PRAS', f"PRAS_{t}i{iteration}.h5") + os.path.join(case, 'handoff', 'PRAS', f"PRAS_{t}i{iteration}.h5") ) ### Create the time index sw = reeds.io.get_switches(case) @@ -91,7 +91,7 @@ def get_stress_metric_periods( ### Get load at hierarchy_level dfload = reeds.io.read_h5py_file( os.path.join( - case,'ReEDS_Augur','augur_data',f'pras_load_{t}.h5') + case,'handoff','augur_data',f'pras_load_{t}.h5') ).rename(columns=rmap).groupby(level=0, axis=1).sum() dfload.index = dfmetric.index @@ -209,7 +209,7 @@ def get_annual_stress_metric(case, t, stress_metric, iteration=0): if stress_metric.upper() == 'NEUE': dfload = reeds.io.read_h5py_file( os.path.join( - case,'ReEDS_Augur','augur_data',f'pras_load_{t}.h5') + case,'handoff','augur_data',f'pras_load_{t}.h5') ) dfload.index = dfmetric.index From b5c0384cd16df03435deb8422b6aed176c6b7b9d Mon Sep 17 00:00:00 2001 From: Abdelrahman Ayad Date: Tue, 19 May 2026 20:54:33 -0600 Subject: [PATCH 17/31] minor fix --- reeds/resource_adequacy/stress_periods.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reeds/resource_adequacy/stress_periods.py b/reeds/resource_adequacy/stress_periods.py index 49170fc6..1ed30e62 100644 --- a/reeds/resource_adequacy/stress_periods.py +++ b/reeds/resource_adequacy/stress_periods.py @@ -417,7 +417,7 @@ def get_stress_metrics_sorted_periods(sw, t, iteration): for s in sw.keys() if s.startswith('GSw_PRM_StressThreshold') and not s.endswith('Metrics') ] - stressThresholdMetrics.remove('') + for s in stressThresholdMetrics: if s not in stress_metric_switches: print(f"Warning: {s} is not included in GSw_PRM_StressThresholdMetrics, so it will not be evaluated") From a0c7959d1437323ff4a9e39503229578a9f45fa3 Mon Sep 17 00:00:00 2001 From: Abdelrahman Ayad Date: Tue, 19 May 2026 22:12:19 -0600 Subject: [PATCH 18/31] minor fixes --- runreeds.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/runreeds.py b/runreeds.py index 5dbdbcd7..397886af 100644 --- a/runreeds.py +++ b/runreeds.py @@ -320,22 +320,26 @@ def check_compatibility(sw): hierarchy = reeds.io.get_hierarchy(GSw_ZoneSet=sw['GSw_ZoneSet']).reset_index() ### Check that the stress metrics specified in GSw_PRM_StressThresholdMetrics - # have corresponding GSw_PRM_StressThreshold{metric} switches + # have corresponding GSw_PRM_StressThreshold{metric} switches stressThresholdMetricSwitches = [s.split('GSw_PRM_StressThreshold')[1] for s in sw.keys() if s.startswith('GSw_PRM_StressThreshold') and not s.endswith('Metrics') ] stressTresholdMetricControls = sw['GSw_PRM_StressThresholdMetrics'].split('/') + print(f"stressThresholdMetricSwitches: {stressThresholdMetricSwitches}") + print(f"stressTresholdMetricControls: {stressTresholdMetricControls}") + # Metric control but not defined as switch for metric in stressTresholdMetricControls: if metric.upper() not in stressThresholdMetricSwitches: - raise NotImplementedError(f"GSw_PRM_StressThreshold{metric.upper()} is not defined as a switch") + raise NotImplementedError(f"GSw_PRM_StressThreshold{metric} is not defined as a switch.") - # Metric switch but not defined in control + # Metric switch but not defined in control for metric in stressThresholdMetricSwitches: if metric.upper() not in stressTresholdMetricControls: - raise NotImplementedError(f"GSw_PRM_StressThreshold{metric.upper()} is added but not found in GSw_PRM_StressThresholdMetrics") + raise NotImplementedError(f"GSw_PRM_StressThreshold{metric} is defined as a switch, " + f"but not added to GSw_PRM_StressThresholdMetrics list {stressTresholdMetricControls}") for metric in stressThresholdMetricSwitches: for threshold in sw[f'GSw_PRM_StressThreshold{metric}'].split('/'): From 767547e74eea143ef4b58be13f181175c82924cf Mon Sep 17 00:00:00 2001 From: Abdelrahman Ayad Date: Tue, 19 May 2026 22:13:26 -0600 Subject: [PATCH 19/31] keep EUE as default metric in cases.csv --- cases.csv | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cases.csv b/cases.csv index 5dcac5ab..2b0260d7 100644 --- a/cases.csv +++ b/cases.csv @@ -268,8 +268,7 @@ GSw_PRM_StressOutages,Whether to apply the availability factor (forced + schedul GSw_PRM_StressSeedLoadLevel,Region hierarchy level at which to include peak coincident load days as seeded stress periods (or False to ignore peaks),false; False; FALSE; r; nercr; transreg; transgrp; cendiv; st; interconnect; country; usda_region; ccreg,transgrp, GSw_PRM_StressSeedMinRElevel,Region hierarchy level at which to include minimum wind and solar capacity factor days as seeded stress periods (or False to ignore min-wind/solar CF days),false; False; FALSE; r; nercr; transreg; transgrp; cendiv; st; interconnect; country; usda_region; ccreg,interconnect, GSw_PRM_StressStorageCutoff,"How to select ""shoulder"" stress periods giving storage time to recharge before/after high-unserved-energy periods. Two-part switch separated by _. The first argument is ""EUE"" or ""capacity"" or ""absolute"". If ""EUE"" the second argument specifies a PRAS storage headspace / EUE threshold [fraction]; if ""cap"" it specifies a headspace / storage capacity threshold [fraction]; if ""abs"" it specifies absolute number of periods before/after [integer]. Turned off if set to ""off"".",N/A,EUE_0.1, -GSw_PRM_StressThresholdMetrics,/-delimited list of metrics for identifying stress periods,N/A,EUE/LOLE, -GSw_PRM_StressThresholdLOLE,/-delimited list of annual LOLE above which to re-solve the latest model year with new stress periods; formulated as HierarchyLevel_nLOLE_StressMetric_PeriodAggMethod where HierarchyLevel is a column in hierarchy.csv; nLOLE is number of loss of load events; StressMetric is LOLE; PeriodAggMethod is 'sum' or 'max' over the hours in each period (only used in period selection) (see README.md for detailed notes),N/A,transgrp_10_LOLE_sum, +GSw_PRM_StressThresholdMetrics,/-delimited list of metrics for identifying stress periods,N/A,EUE, GSw_PRM_StressThresholdEUE,/-delimited list of annual NEUE level [ppm] above which to re-solve the latest model year with new stress periods; formulated as HierarchyLevel_NEUEppm_StressMetric_PeriodAggMethod where HierarchyLevel is a column in hierarchy.csv; NEUEppm is normalized expected unserved energy in parts per million; StressMetric is EUE or NEUE (only used in period selection); PeriodAggMethod is 'sum' or 'max' over the hours in each period (only used in period selection) (see README.md for detailed notes),N/A,transgrp_1_EUE_sum, GSw_PRM_UpdateFraction,Fraction to add to the PRM if a region fails RA threshold (only used if GSw_PRM_UpdateMethod = 1),float,0.02, GSw_PRM_UpdateMethod,Option to update PRM: (0) no update; (1) static update set by GSw_PRM_UpdateFraction; (2) dynamic update informed by PRAS; (3) dynamic update but only after all new stress periods have been added,0; 1; 2; 3,0, From 435dcdba1f0a1f044a206019d2eda27fd054878c Mon Sep 17 00:00:00 2001 From: Abdelrahman Ayad Date: Wed, 20 May 2026 10:19:48 -0600 Subject: [PATCH 20/31] update switches logic --- cases.csv | 4 +++- runreeds.py | 22 +++++++++++----------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/cases.csv b/cases.csv index 2b0260d7..8c1309ba 100644 --- a/cases.csv +++ b/cases.csv @@ -269,7 +269,9 @@ GSw_PRM_StressSeedLoadLevel,Region hierarchy level at which to include peak coin GSw_PRM_StressSeedMinRElevel,Region hierarchy level at which to include minimum wind and solar capacity factor days as seeded stress periods (or False to ignore min-wind/solar CF days),false; False; FALSE; r; nercr; transreg; transgrp; cendiv; st; interconnect; country; usda_region; ccreg,interconnect, GSw_PRM_StressStorageCutoff,"How to select ""shoulder"" stress periods giving storage time to recharge before/after high-unserved-energy periods. Two-part switch separated by _. The first argument is ""EUE"" or ""capacity"" or ""absolute"". If ""EUE"" the second argument specifies a PRAS storage headspace / EUE threshold [fraction]; if ""cap"" it specifies a headspace / storage capacity threshold [fraction]; if ""abs"" it specifies absolute number of periods before/after [integer]. Turned off if set to ""off"".",N/A,EUE_0.1, GSw_PRM_StressThresholdMetrics,/-delimited list of metrics for identifying stress periods,N/A,EUE, -GSw_PRM_StressThresholdEUE,/-delimited list of annual NEUE level [ppm] above which to re-solve the latest model year with new stress periods; formulated as HierarchyLevel_NEUEppm_StressMetric_PeriodAggMethod where HierarchyLevel is a column in hierarchy.csv; NEUEppm is normalized expected unserved energy in parts per million; StressMetric is EUE or NEUE (only used in period selection); PeriodAggMethod is 'sum' or 'max' over the hours in each period (only used in period selection) (see README.md for detailed notes),N/A,transgrp_1_EUE_sum, +GSw_PRM_StressThresholdLOLE,LOLE threshold above which to re-solve the latest model year with new stress periods; formulated as HierarchyLevel_nLOLE_StressMetric_PeriodAggMethod where HierarchyLevel is a column in hierarchy.csv; nLOLE is number of loss of load events; StressMetric is LOLE; PeriodAggMethod is 'sum' or 'max' over the hours in each period (only used in period selection) (see README.md for detailed notes),N/A,transgrp_10_LOLE_sum, +GSw_PRM_StressThresholdEUE,/Annual EUE level [MWh] threshold above which to re-solve the latest model year with new stress periods; formulated as HierarchyLevel_EUE_StressMetric_PeriodAggMethod where HierarchyLevel is a column in hierarchy.csv; EUE is expected unserved energy; StressMetric EUE (only used in period selection); PeriodAggMethod is 'sum' or 'max' over the hours in each period (only used in period selection) (see README.md for detailed notes),N/A,transgrp_1000_EUE_sum, +GSw_PRM_StressThresholdNEUE,/Annual NEUE level [ppm] threshold above which to re-solve the latest model year with new stress periods; formulated as HierarchyLevel_NEUEppm_StressMetric_PeriodAggMethod where HierarchyLevel is a column in hierarchy.csv; NEUEppm is normalized expected unserved energy in parts per million; StressMetric is EUE or NEUE (only used in period selection); PeriodAggMethod is 'sum' or 'max' over the hours in each period (only used in period selection) (see README.md for detailed notes),N/A,transgrp_1_NEUE_sum, GSw_PRM_UpdateFraction,Fraction to add to the PRM if a region fails RA threshold (only used if GSw_PRM_UpdateMethod = 1),float,0.02, GSw_PRM_UpdateMethod,Option to update PRM: (0) no update; (1) static update set by GSw_PRM_UpdateFraction; (2) dynamic update informed by PRAS; (3) dynamic update but only after all new stress periods have been added,0; 1; 2; 3,0, GSw_PRMTRADE_level,hierarchy level within which to allow PRM trading,r; nercr; transreg; transgrp; cendiv; st; interconnect; country; usda_region,country, diff --git a/runreeds.py b/runreeds.py index 397886af..10138de3 100644 --- a/runreeds.py +++ b/runreeds.py @@ -325,21 +325,21 @@ def check_compatibility(sw): for s in sw.keys() if s.startswith('GSw_PRM_StressThreshold') and not s.endswith('Metrics') ] - stressTresholdMetricControls = sw['GSw_PRM_StressThresholdMetrics'].split('/') + stressTresholdMetrics = sw['GSw_PRM_StressThresholdMetrics'].split('/') - print(f"stressThresholdMetricSwitches: {stressThresholdMetricSwitches}") - print(f"stressTresholdMetricControls: {stressTresholdMetricControls}") - - # Metric control but not defined as switch - for metric in stressTresholdMetricControls: + print(f"stressTresholdMetrics: {stressTresholdMetrics}") + print(f"stressThresholdMetricSwitches: {stressThresholdMetricSwitches}") + + # Threshold metric added but not specified as a switch + for metric in stressTresholdMetrics: if metric.upper() not in stressThresholdMetricSwitches: - raise NotImplementedError(f"GSw_PRM_StressThreshold{metric} is not defined as a switch.") + raise NotImplementedError(f"GSw_PRM_StressThreshold{metric} is not specified as a switch.") - # Metric switch but not defined in control + # Threshold metric is not added but specified as a switch for metric in stressThresholdMetricSwitches: - if metric.upper() not in stressTresholdMetricControls: - raise NotImplementedError(f"GSw_PRM_StressThreshold{metric} is defined as a switch, " - f"but not added to GSw_PRM_StressThresholdMetrics list {stressTresholdMetricControls}") + if metric.upper() not in stressTresholdMetrics: + raise NotImplementedError(f"GSw_PRM_StressThreshold{metric} is specified as a switch," + f"but not added to GSw_PRM_StressThresholdMetrics list {stressTresholdMetrics}") for metric in stressThresholdMetricSwitches: for threshold in sw[f'GSw_PRM_StressThreshold{metric}'].split('/'): From 4e054d64ae975a63c7e93389a2a4512137206971 Mon Sep 17 00:00:00 2001 From: Abdelrahman Ayad Date: Wed, 20 May 2026 10:25:28 -0600 Subject: [PATCH 21/31] remove error if switch is not specified --- cases_multi.csv | 67 +++++++++++++++++++++++++++++++++++++++++++++++++ runreeds.py | 10 ++++---- 2 files changed, 72 insertions(+), 5 deletions(-) create mode 100644 cases_multi.csv diff --git a/cases_multi.csv b/cases_multi.csv new file mode 100644 index 00000000..94ec8f28 --- /dev/null +++ b/cases_multi.csv @@ -0,0 +1,67 @@ +,Default Value,Pacific_EUE,Pacific_LOLE,Pacific_NEUE,Pacific_EUE/LOLE,Pacific_EUE/LOLE/NEUE,Pacific,USA_defaults,Mid_Case,USA_decarb,github_Pacific,github_Everything,github_MA_county_CC,Pacific_CC,Pacific_weks,Pacific_full_year,Interday_storage,Pacific_2020,Pacific_rep7,WY_county,WECC_county,PJM_county_CC,NYVT_mixed,OR_water,MonteCarlo,Everything,Simple,USA_fast,USA_faster,Pacific_DR,Pacific_MGA,Pacific_LoadSite95,MARICTNYNJPAOH_Offshore,R2P +ignore,1,0,0,0,0,0,0,,,,,,,,,,,,,,,,,,,,,,,,,,, +GSw_Region,cendiv/Pacific,,,,,,,country/USA,country/USA,country/USA,,st/ID.WY.NE.IA.IL,st/MA,,,,,,,st/WY,interconnect/western,transreg/PJM,st/NY.VT,st/OR,st/NE.NY.PA,st/ID.WY.NE.IA.IL,st/KS,country/USA,country/USA,,,,st/MA.RI.CT.NY.NJ.PA.OH, +endyear,2032,,,,,,,2050,2050,2050,2029,2060,2026,,,,,,,,,,,2035,2030,2060,2035,2050,2050,,,,, +yearset,,,,,,,,,,,,2010..2060..10,,,,,,,,,,,,,2010..2050..5,2010..2060..10,,,2010_2025_2050,,,,, +GSw_ZoneSet,,,,,,,,,,,,z54,z3109,,,,,,,z3109,z3109,z3109,PJMcounty,,,z54,,z54,z48,,,,, +GSw_GasCurve,2,,,,,,,1,1,,,,,,,,,,,,,,,,,,,1,1,,,,, +GSw_Geothermal,,,,,,,,,2,,,,,,,,,,,,,,,,,,0,,0,,,,, +GSw_GrowthPenalties,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,, +GSw_Upstream,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,, +GSw_TransHurdleRate,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,, +distpvscen,,,,,,,,,,stscen2023_mid_case_95_by_2035,,,,,,,,,,,,,,,,,,,,,,,, +GSw_AnnualCap,,,,,,,,,,2,,1,,,,,,,,,,,,,,1,,,,,,,, +GSw_AnnualCapScen,,,,,,,,,,start2024_90pct2035_100pct2045,,start2027_95pct2035,,,,,,,,,,,,,,start2027_95pct2035,,,,,,,, +GSw_LoadProfiles,,,,,,,,,,EER2025_100by2050,EER2025_IRAlow,EER2025_IRAlow,EER2025_IRAlow,,,,,historic,,,,,,,,EER2025_100by2050,,,,historic,,,, +GSw_NG_CRF_penalty,,,,,,,,,,ramp_2045,,ramp_2023_2035,,,,,,,,,,,,,,ramp_2023_2035,,,,,,,, +GSw_PRM_NetImportLimit,,,,,,,,,,0,,,,,,,,,,,,,,,,,,,,,,,, +GSw_RetirePenalty,,,,,,,,,,0,,,,,,,,,,,,,,,,,,,,,,,, +GSw_PRM_CapCredit,,,,,,,,,,,,,1,1,,,,,,,,1,,,,,,,,,,,, +GSw_PRM_scenario,,,,,,,,,,,,,,static,,,,,,,,static,,,,,,,,,,,, +GSw_HourlyType,,,,,,,,,,,,,,,wek,year,,,,,,,,,,,,,,,,,, +GSw_InterDayLinkage,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,, +GSw_HourlyWeatherYears,,,,,,,,,,,,2012_2013,,,,,,2020,2007_2008_2009_2010_2011_2012_2013,,,,,,,2012_2013,,,,2018,,,, +GSw_WaterCapacity,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,, +GSw_WaterMain,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,, +GSw_WaterUse,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,, +resource_adequacy_years,,,,,,,,,,,,2011_2012_2013_2021_2022_2023,,,,,,,,,,,,,,2011_2012_2013_2021_2022_2023,,,,,,,, +GSw_HourlyClusterAlgorithm,,,,,,,,,,,,,,,,,,,,,,,,,user,,,,,,,,, +MCS_runs,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,, +MCS_dist_groups,,,,,,,,,,,,,,,,,,,,,,,,,tech.hydro.nuclear.gas.coal.ng_fuel_price,,,,,,,,, +GSw_PRM_StressIterateMax,,,,,,,,,,,,,0,,,,,,,,0,0,,,1,,,,,,,,, +GSw_ReducedResource,,,,,,,,,,,,1,,,,,,,,,,,,,,1,,,,,,,, +GSw_SitingUPV,,,,,,,,,,,limited,limited,limited,,,,,,,,,,,,,limited,,,,,,,, +GSw_SitingWindOfs,,,,,,,,,,,limited,limited,limited,,,,,,,,,,,,,open,,,,,,,, +GSw_SitingWindOns,,,,,,,,,,,limited,limited,limited,,,,,,,,,,,,,limited,,,,,,,, +GSw_TransScen,,,,,,,,,,,,VSC_all,,,,,,,,,,,,,,VSC_all,,,,,,,, +GSw_CO2_Detail,,,,,,,,,,,,1,,,,,,,,,,,,,,1,,,,,,,, +GSw_DAC,,,,,,,,,,,,1,,,,,,,,,,,,,,1,,,,,,,, +GSw_NoFossilOffsetCDR,,,,,,,,,,,,1,,,,,,,,,,,,,,1,,,,,,,, +GSw_Biopower,,,,,,,,,,,,,,,,,,,,,,,,,,,0,,0,,,,, +GSw_HourlyChunkLengthRep,,,,,,,,,,,,,,,,,,,,,,,,,,,6,4,4,,,,, +GSw_HourlyChunkLengthStress,,,,,,,,,,,,,,,,,,,,,,,,,,,6,4,4,,,,, +GSw_LfillGas,,,,,,,,,,,,,,,,,,,,,,,,,,,0,,0,,,,, +GSw_Nuclear,,,,,,,,,,,,,,,,,,,,,,,,,,,0,,0,,,,, +GSw_OpRes,,,,,,,,,,,,,,,,,,,,,,,,,,,0,,0,,,,, +GSw_StartCost,,,,,,,,,,,,,,,,,,,,,,,,,,,0,0,0,,,,, +GSw_H2,,,,,,,,,,,,,,,,,,,,,,,,,,,,0,0,,,,, +GSw_H2_PTC,,,,,,,,,,,,,,,,,,,,,,,,,,,,0,0,,,,, +GSw_H2Combustion,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0,,,,, +GSw_DRShed,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,, +GSw_MGA_CostDelta,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0.01,,, +GSw_LoadSiteCF,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0.95,, +GSw_OffshoreZones,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, +GSw_OffshoreBackbone,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, +GSw_OffshoreBackflow,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, +pras_agg_ogs_lfillgas,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1 +pras_existing_unit_size,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0 +pras_scheduled_outage,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0 +pras_unitsize_source,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,r2x +pras_vre_combine,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1 +pras_samples,,,,,,,,,,,10,10,10,,,,,,,,,,,,,,,10,10,,,,, +keep_resource_adequacy_files,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +GSw_PRM_UpdateMethod,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +GSw_PRM_StressThresholdMetrics,,EUE,LOLE,NEUE,EUE/LOLE,EUE/LOLE/NEUE,,,,,,,,,,,,,,,,,,,,,,,,,,,, +GSw_PRM_StressThresholdLOLE,,,transgrp_10_LOLE_sum,,transgrp_10_LOLE_sum,transgrp_10_LOLE_sum,,,,,,,,,,,,,,,,,,,,,,,,,,,, +GSw_PRM_StressThresholdEUE,,transgrp_1000_EUE_sum,,,transgrp_1000_EUE_sum,transgrp_1000_EUE_sum,,,,,,,,,,,,,,,,,,,,,,,,,,,, +GSw_PRM_StressThresholdNEUE,,,,transgrp_1_NEUE_sum,,transgrp_1_NEUE_sum,,,,,,,,,,,,,,,,,,,,,,,,,,,, \ No newline at end of file diff --git a/runreeds.py b/runreeds.py index 10138de3..5ba301fb 100644 --- a/runreeds.py +++ b/runreeds.py @@ -335,11 +335,11 @@ def check_compatibility(sw): if metric.upper() not in stressThresholdMetricSwitches: raise NotImplementedError(f"GSw_PRM_StressThreshold{metric} is not specified as a switch.") - # Threshold metric is not added but specified as a switch - for metric in stressThresholdMetricSwitches: - if metric.upper() not in stressTresholdMetrics: - raise NotImplementedError(f"GSw_PRM_StressThreshold{metric} is specified as a switch," - f"but not added to GSw_PRM_StressThresholdMetrics list {stressTresholdMetrics}") + # # Threshold metric is not added but specified as a switch + # for metric in stressThresholdMetricSwitches: + # if metric.upper() not in stressTresholdMetrics: + # raise NotImplementedError(f"GSw_PRM_StressThreshold{metric} is specified as a switch," + # f"but not added to GSw_PRM_StressThresholdMetrics list {stressTresholdMetrics}") for metric in stressThresholdMetricSwitches: for threshold in sw[f'GSw_PRM_StressThreshold{metric}'].split('/'): From 0280c95f240c76bfd7c24684d3956f10aaea8df8 Mon Sep 17 00:00:00 2001 From: aayad Date: Wed, 20 May 2026 13:05:12 -0600 Subject: [PATCH 22/31] remove CVAR info --- reeds/resource_adequacy/run_pras.jl | 5 ----- 1 file changed, 5 deletions(-) diff --git a/reeds/resource_adequacy/run_pras.jl b/reeds/resource_adequacy/run_pras.jl index a100ac74..10f59e0c 100644 --- a/reeds/resource_adequacy/run_pras.jl +++ b/reeds/resource_adequacy/run_pras.jl @@ -208,11 +208,6 @@ function run_pras(pras_system_path::String, args::Dict) @info "$(PRAS.EUE(results["short"])) MWh" @info "NEUE = $(1e6 * PRAS.EUE(results["short"]).eue.estimate / sum(sys.regions.load)) ppm" - # CVAR results - _,_,_,_,energyunit = PRAS.get_params(sys) - alpha = 0.95 - @info (PRAS.CVAR(energyunit, results["short_samples"], alpha)) - ## Filter out DC regions used for VSC HVDC transmission regions = [r for r in sys.regions.names if !(occursin("|", r))] From 5ea585a7a8526a11f12f29144f034df39c0d024d Mon Sep 17 00:00:00 2001 From: aayad Date: Wed, 20 May 2026 15:55:20 -0600 Subject: [PATCH 23/31] untrack cases_multi and srun_template --- cases_multi.csv | 67 -------------------------------------- reeds/hpc/srun_template.sh | 9 ----- 2 files changed, 76 deletions(-) delete mode 100644 cases_multi.csv delete mode 100644 reeds/hpc/srun_template.sh diff --git a/cases_multi.csv b/cases_multi.csv deleted file mode 100644 index 94ec8f28..00000000 --- a/cases_multi.csv +++ /dev/null @@ -1,67 +0,0 @@ -,Default Value,Pacific_EUE,Pacific_LOLE,Pacific_NEUE,Pacific_EUE/LOLE,Pacific_EUE/LOLE/NEUE,Pacific,USA_defaults,Mid_Case,USA_decarb,github_Pacific,github_Everything,github_MA_county_CC,Pacific_CC,Pacific_weks,Pacific_full_year,Interday_storage,Pacific_2020,Pacific_rep7,WY_county,WECC_county,PJM_county_CC,NYVT_mixed,OR_water,MonteCarlo,Everything,Simple,USA_fast,USA_faster,Pacific_DR,Pacific_MGA,Pacific_LoadSite95,MARICTNYNJPAOH_Offshore,R2P -ignore,1,0,0,0,0,0,0,,,,,,,,,,,,,,,,,,,,,,,,,,, -GSw_Region,cendiv/Pacific,,,,,,,country/USA,country/USA,country/USA,,st/ID.WY.NE.IA.IL,st/MA,,,,,,,st/WY,interconnect/western,transreg/PJM,st/NY.VT,st/OR,st/NE.NY.PA,st/ID.WY.NE.IA.IL,st/KS,country/USA,country/USA,,,,st/MA.RI.CT.NY.NJ.PA.OH, -endyear,2032,,,,,,,2050,2050,2050,2029,2060,2026,,,,,,,,,,,2035,2030,2060,2035,2050,2050,,,,, -yearset,,,,,,,,,,,,2010..2060..10,,,,,,,,,,,,,2010..2050..5,2010..2060..10,,,2010_2025_2050,,,,, -GSw_ZoneSet,,,,,,,,,,,,z54,z3109,,,,,,,z3109,z3109,z3109,PJMcounty,,,z54,,z54,z48,,,,, -GSw_GasCurve,2,,,,,,,1,1,,,,,,,,,,,,,,,,,,,1,1,,,,, -GSw_Geothermal,,,,,,,,,2,,,,,,,,,,,,,,,,,,0,,0,,,,, -GSw_GrowthPenalties,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,, -GSw_Upstream,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,, -GSw_TransHurdleRate,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,, -distpvscen,,,,,,,,,,stscen2023_mid_case_95_by_2035,,,,,,,,,,,,,,,,,,,,,,,, -GSw_AnnualCap,,,,,,,,,,2,,1,,,,,,,,,,,,,,1,,,,,,,, -GSw_AnnualCapScen,,,,,,,,,,start2024_90pct2035_100pct2045,,start2027_95pct2035,,,,,,,,,,,,,,start2027_95pct2035,,,,,,,, -GSw_LoadProfiles,,,,,,,,,,EER2025_100by2050,EER2025_IRAlow,EER2025_IRAlow,EER2025_IRAlow,,,,,historic,,,,,,,,EER2025_100by2050,,,,historic,,,, -GSw_NG_CRF_penalty,,,,,,,,,,ramp_2045,,ramp_2023_2035,,,,,,,,,,,,,,ramp_2023_2035,,,,,,,, -GSw_PRM_NetImportLimit,,,,,,,,,,0,,,,,,,,,,,,,,,,,,,,,,,, -GSw_RetirePenalty,,,,,,,,,,0,,,,,,,,,,,,,,,,,,,,,,,, -GSw_PRM_CapCredit,,,,,,,,,,,,,1,1,,,,,,,,1,,,,,,,,,,,, -GSw_PRM_scenario,,,,,,,,,,,,,,static,,,,,,,,static,,,,,,,,,,,, -GSw_HourlyType,,,,,,,,,,,,,,,wek,year,,,,,,,,,,,,,,,,,, -GSw_InterDayLinkage,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,, -GSw_HourlyWeatherYears,,,,,,,,,,,,2012_2013,,,,,,2020,2007_2008_2009_2010_2011_2012_2013,,,,,,,2012_2013,,,,2018,,,, -GSw_WaterCapacity,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,, -GSw_WaterMain,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,, -GSw_WaterUse,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,, -resource_adequacy_years,,,,,,,,,,,,2011_2012_2013_2021_2022_2023,,,,,,,,,,,,,,2011_2012_2013_2021_2022_2023,,,,,,,, -GSw_HourlyClusterAlgorithm,,,,,,,,,,,,,,,,,,,,,,,,,user,,,,,,,,, -MCS_runs,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,, -MCS_dist_groups,,,,,,,,,,,,,,,,,,,,,,,,,tech.hydro.nuclear.gas.coal.ng_fuel_price,,,,,,,,, -GSw_PRM_StressIterateMax,,,,,,,,,,,,,0,,,,,,,,0,0,,,1,,,,,,,,, -GSw_ReducedResource,,,,,,,,,,,,1,,,,,,,,,,,,,,1,,,,,,,, -GSw_SitingUPV,,,,,,,,,,,limited,limited,limited,,,,,,,,,,,,,limited,,,,,,,, -GSw_SitingWindOfs,,,,,,,,,,,limited,limited,limited,,,,,,,,,,,,,open,,,,,,,, -GSw_SitingWindOns,,,,,,,,,,,limited,limited,limited,,,,,,,,,,,,,limited,,,,,,,, -GSw_TransScen,,,,,,,,,,,,VSC_all,,,,,,,,,,,,,,VSC_all,,,,,,,, -GSw_CO2_Detail,,,,,,,,,,,,1,,,,,,,,,,,,,,1,,,,,,,, -GSw_DAC,,,,,,,,,,,,1,,,,,,,,,,,,,,1,,,,,,,, -GSw_NoFossilOffsetCDR,,,,,,,,,,,,1,,,,,,,,,,,,,,1,,,,,,,, -GSw_Biopower,,,,,,,,,,,,,,,,,,,,,,,,,,,0,,0,,,,, -GSw_HourlyChunkLengthRep,,,,,,,,,,,,,,,,,,,,,,,,,,,6,4,4,,,,, -GSw_HourlyChunkLengthStress,,,,,,,,,,,,,,,,,,,,,,,,,,,6,4,4,,,,, -GSw_LfillGas,,,,,,,,,,,,,,,,,,,,,,,,,,,0,,0,,,,, -GSw_Nuclear,,,,,,,,,,,,,,,,,,,,,,,,,,,0,,0,,,,, -GSw_OpRes,,,,,,,,,,,,,,,,,,,,,,,,,,,0,,0,,,,, -GSw_StartCost,,,,,,,,,,,,,,,,,,,,,,,,,,,0,0,0,,,,, -GSw_H2,,,,,,,,,,,,,,,,,,,,,,,,,,,,0,0,,,,, -GSw_H2_PTC,,,,,,,,,,,,,,,,,,,,,,,,,,,,0,0,,,,, -GSw_H2Combustion,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0,,,,, -GSw_DRShed,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,, -GSw_MGA_CostDelta,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0.01,,, -GSw_LoadSiteCF,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0.95,, -GSw_OffshoreZones,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, -GSw_OffshoreBackbone,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, -GSw_OffshoreBackflow,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, -pras_agg_ogs_lfillgas,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1 -pras_existing_unit_size,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0 -pras_scheduled_outage,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0 -pras_unitsize_source,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,r2x -pras_vre_combine,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1 -pras_samples,,,,,,,,,,,10,10,10,,,,,,,,,,,,,,,10,10,,,,, -keep_resource_adequacy_files,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -GSw_PRM_UpdateMethod,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -GSw_PRM_StressThresholdMetrics,,EUE,LOLE,NEUE,EUE/LOLE,EUE/LOLE/NEUE,,,,,,,,,,,,,,,,,,,,,,,,,,,, -GSw_PRM_StressThresholdLOLE,,,transgrp_10_LOLE_sum,,transgrp_10_LOLE_sum,transgrp_10_LOLE_sum,,,,,,,,,,,,,,,,,,,,,,,,,,,, -GSw_PRM_StressThresholdEUE,,transgrp_1000_EUE_sum,,,transgrp_1000_EUE_sum,transgrp_1000_EUE_sum,,,,,,,,,,,,,,,,,,,,,,,,,,,, -GSw_PRM_StressThresholdNEUE,,,,transgrp_1_NEUE_sum,,transgrp_1_NEUE_sum,,,,,,,,,,,,,,,,,,,,,,,,,,,, \ No newline at end of file diff --git a/reeds/hpc/srun_template.sh b/reeds/hpc/srun_template.sh deleted file mode 100644 index 4bebd071..00000000 --- a/reeds/hpc/srun_template.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash -#SBATCH --account=[your HPC allocation] -#SBATCH --time=2-00:00:00 -#SBATCH --nodes=1 -#SBATCH --ntasks-per-node=1 -#SBATCH --mail-user=[your email address] -#SBATCH --mail-type=BEGIN,END,FAIL -#SBATCH --mem=246000 # RAM in MB; up to 246000 for normal or 2000000 for bigmem on kestrel -# add >>> #SBATCH --qos=high <<< above for quicker launch at double AU cost \ No newline at end of file From 40fffa5ea55ef12cf2a6cb9fc342b78a93b9a46e Mon Sep 17 00:00:00 2001 From: aayad Date: Fri, 22 May 2026 12:32:52 -0600 Subject: [PATCH 24/31] update dir from augur_data to PRAS --- reeds/hpc/srun_template.sh | 9 +++++++++ reeds/resource_adequacy/stress_periods.py | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 reeds/hpc/srun_template.sh diff --git a/reeds/hpc/srun_template.sh b/reeds/hpc/srun_template.sh new file mode 100644 index 00000000..4bebd071 --- /dev/null +++ b/reeds/hpc/srun_template.sh @@ -0,0 +1,9 @@ +#!/bin/bash +#SBATCH --account=[your HPC allocation] +#SBATCH --time=2-00:00:00 +#SBATCH --nodes=1 +#SBATCH --ntasks-per-node=1 +#SBATCH --mail-user=[your email address] +#SBATCH --mail-type=BEGIN,END,FAIL +#SBATCH --mem=246000 # RAM in MB; up to 246000 for normal or 2000000 for bigmem on kestrel +# add >>> #SBATCH --qos=high <<< above for quicker launch at double AU cost \ No newline at end of file diff --git a/reeds/resource_adequacy/stress_periods.py b/reeds/resource_adequacy/stress_periods.py index 1ed30e62..9d964ef1 100644 --- a/reeds/resource_adequacy/stress_periods.py +++ b/reeds/resource_adequacy/stress_periods.py @@ -91,7 +91,7 @@ def get_stress_metric_periods( ### Get load at hierarchy_level dfload = reeds.io.read_h5py_file( os.path.join( - case,'handoff','augur_data',f'pras_load_{t}.h5') + case,'handoff','PRAS',f'pras_load_{t}.h5') ).rename(columns=rmap).groupby(level=0, axis=1).sum() dfload.index = dfmetric.index @@ -209,7 +209,7 @@ def get_annual_stress_metric(case, t, stress_metric, iteration=0): if stress_metric.upper() == 'NEUE': dfload = reeds.io.read_h5py_file( os.path.join( - case,'handoff','augur_data',f'pras_load_{t}.h5') + case,'handoff','PRAS',f'pras_load_{t}.h5') ) dfload.index = dfmetric.index From 4070ea7b4544c269745471a772dbfe65200a3a47 Mon Sep 17 00:00:00 2001 From: aayad Date: Fri, 22 May 2026 12:55:36 -0600 Subject: [PATCH 25/31] `reeds_data` instead of `PRAS` folder --- reeds/resource_adequacy/stress_periods.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/reeds/resource_adequacy/stress_periods.py b/reeds/resource_adequacy/stress_periods.py index 9d964ef1..454ce3bd 100644 --- a/reeds/resource_adequacy/stress_periods.py +++ b/reeds/resource_adequacy/stress_periods.py @@ -91,7 +91,7 @@ def get_stress_metric_periods( ### Get load at hierarchy_level dfload = reeds.io.read_h5py_file( os.path.join( - case,'handoff','PRAS',f'pras_load_{t}.h5') + case,'handoff','reeds_data',f'pras_load_{t}.h5') ).rename(columns=rmap).groupby(level=0, axis=1).sum() dfload.index = dfmetric.index @@ -209,7 +209,7 @@ def get_annual_stress_metric(case, t, stress_metric, iteration=0): if stress_metric.upper() == 'NEUE': dfload = reeds.io.read_h5py_file( os.path.join( - case,'handoff','PRAS',f'pras_load_{t}.h5') + case,'handoff','reeds_data',f'pras_load_{t}.h5') ) dfload.index = dfmetric.index From 987bc0e322fecf88a73d366dc5ae290370b5cffb Mon Sep 17 00:00:00 2001 From: Abdelrahman Ayad Date: Fri, 22 May 2026 18:26:21 -0600 Subject: [PATCH 26/31] USE PRAS NEUE calculations --- reeds/resource_adequacy/run_pras.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/reeds/resource_adequacy/run_pras.jl b/reeds/resource_adequacy/run_pras.jl index 10f59e0c..a1d8b598 100644 --- a/reeds/resource_adequacy/run_pras.jl +++ b/reeds/resource_adequacy/run_pras.jl @@ -204,9 +204,9 @@ function run_pras(pras_system_path::String, args::Dict) results = Dict{String,Any}(zip(keys(resultspec), results_tuple)) #%% Print some results for the entire modeled region to show it worked - @info "$(PRAS.LOLE(results["short"])) event-h" - @info "$(PRAS.EUE(results["short"])) MWh" - @info "NEUE = $(1e6 * PRAS.EUE(results["short"]).eue.estimate / sum(sys.regions.load)) ppm" + @info "$(PRAS.LOLE(results["short"]))" + @info "$( PRAS.EUE(results["short"]))" + @info "$(PRAS.NEUE(results["short"]))" ## Filter out DC regions used for VSC HVDC transmission regions = [r for r in sys.regions.names if !(occursin("|", r))] From aab0a0886aac9efc3ccb552cf1bcc0efa8c146f0 Mon Sep 17 00:00:00 2001 From: Abdelrahman Ayad Date: Fri, 22 May 2026 18:27:41 -0600 Subject: [PATCH 27/31] fix a bug in get_pras_stress_metric not getting NEUE values --- reeds/resource_adequacy/stress_periods.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/reeds/resource_adequacy/stress_periods.py b/reeds/resource_adequacy/stress_periods.py index 454ce3bd..e51c5f7f 100644 --- a/reeds/resource_adequacy/stress_periods.py +++ b/reeds/resource_adequacy/stress_periods.py @@ -22,6 +22,11 @@ def get_pras_stress_metric(case, t, iteration=0, stress_metric='EUE'): """ """ ### Get PRAS outputs + # NEUE load division takes place in the caller function + # get_stress_metric_periods() or get_annual_stress_metric() based on agg_period + if stress_metric.upper() == 'NEUE': + stress_metric = 'EUE' + dfpras = reeds.io.read_pras_results( os.path.join(case, 'handoff', 'PRAS', f"PRAS_{t}i{iteration}.h5") ) @@ -79,7 +84,7 @@ def get_stress_metric_periods( ) ###### Calculate the stress metric by period - if stress_metric.upper() in ['EUE', 'LOLE']: + if stress_metric.upper() != 'NEUE': ### Aggregate according to period_agg_method dfmetric_period = ( dfmetric @@ -87,7 +92,7 @@ def get_stress_metric_periods( .agg(period_agg_method) .rename_axis(['y','m','d']) ) - elif stress_metric.upper() == 'NEUE': + else: ### Get load at hierarchy_level dfload = reeds.io.read_h5py_file( os.path.join( @@ -413,15 +418,7 @@ def get_stress_metrics_sorted_periods(sw, t, iteration): # Validation check: Display any GSw_PRM_StressThreshold{metric} # that is not specified in GSw_PRM_StressThresholdMetrics stress_metric_switches = sw.GSw_PRM_StressThresholdMetrics.split('/') - stressThresholdMetrics = [s.split('GSw_PRM_StressThreshold')[1] - for s in sw.keys() if s.startswith('GSw_PRM_StressThreshold') - and not s.endswith('Metrics') - ] - for s in stressThresholdMetrics: - if s not in stress_metric_switches: - print(f"Warning: {s} is not included in GSw_PRM_StressThresholdMetrics, so it will not be evaluated") - # stress periods column names for writing outputs stress_metrics_units = {'EUE':'MWh', 'NEUE':'ppm', 'LOLE':'days'} stress_metrics_col_names = {m:f'{m}_{stress_metrics_units[m]}' for m in From cc52dd80e216146cedab1f1b2d29b0b549273c24 Mon Sep 17 00:00:00 2001 From: aayad Date: Mon, 25 May 2026 21:10:19 -0600 Subject: [PATCH 28/31] remove debug statements --- runreeds.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/runreeds.py b/runreeds.py index 5ba301fb..3a88e531 100644 --- a/runreeds.py +++ b/runreeds.py @@ -326,9 +326,6 @@ def check_compatibility(sw): and not s.endswith('Metrics') ] stressTresholdMetrics = sw['GSw_PRM_StressThresholdMetrics'].split('/') - - print(f"stressTresholdMetrics: {stressTresholdMetrics}") - print(f"stressThresholdMetricSwitches: {stressThresholdMetricSwitches}") # Threshold metric added but not specified as a switch for metric in stressTresholdMetrics: From 1aa6e04897e3c834fc2507784a6c6eea5237fa93 Mon Sep 17 00:00:00 2001 From: aayad Date: Mon, 25 May 2026 21:10:37 -0600 Subject: [PATCH 29/31] minor PRAS print change --- reeds/resource_adequacy/run_pras.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reeds/resource_adequacy/run_pras.jl b/reeds/resource_adequacy/run_pras.jl index a1d8b598..ec516737 100644 --- a/reeds/resource_adequacy/run_pras.jl +++ b/reeds/resource_adequacy/run_pras.jl @@ -205,7 +205,7 @@ function run_pras(pras_system_path::String, args::Dict) #%% Print some results for the entire modeled region to show it worked @info "$(PRAS.LOLE(results["short"]))" - @info "$( PRAS.EUE(results["short"]))" + @info "$(PRAS.EUE(results["short"]))" @info "$(PRAS.NEUE(results["short"]))" ## Filter out DC regions used for VSC HVDC transmission From 5e1271d32bbb708f5dd829213d3af17efd431e4e Mon Sep 17 00:00:00 2001 From: aayad Date: Thu, 28 May 2026 08:53:05 -0600 Subject: [PATCH 30/31] update NEUE switch name --- postprocessing/compare_cases.py | 11 +++++++---- postprocessing/single_case_plots.py | 4 +++- reeds/reedsplots.py | 21 +++++++++++++-------- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/postprocessing/compare_cases.py b/postprocessing/compare_cases.py index 57e21efb..722eed96 100644 --- a/postprocessing/compare_cases.py +++ b/postprocessing/compare_cases.py @@ -1436,7 +1436,7 @@ def two_bars(dfplot, basecase, colors, ax, col=0, ypad=0.02): #%%### SCOE, NEUE try: - neue_threshold = float(sw.GSw_PRM_StressThreshold.split('_')[1]) + neue_threshold = float(sw.GSw_PRM_StressThresholdNEUE.split('_')[1]) width = 9 + len(cases)*0.5 plt.close() f,ax = plt.subplots( @@ -1549,9 +1549,9 @@ def two_bars(dfplot, basecase, colors, ax, col=0, ypad=0.02): #%% Regional NEUE try: - neue_threshold = float(sw.GSw_PRM_StressThreshold.split('_')[1]) + neue_threshold = float(sw.GSw_PRM_StressThresholdNEUE.split('_')[1]) dfmap = reeds.io.get_dfmap(cases[basecase]) - level = sw.GSw_PRM_StressThreshold.split('_')[0] + level = sw.GSw_PRM_StressThresholdNEUE.split('_')[0] regions = dfmap[level].loc[hierarchy[basecase][level].unique()].bounds.minx.sort_values().index _nrows, _ncols, _coords = reeds.plots.get_coordinates(regions, ncols=6) labelcoords = { @@ -2500,7 +2500,10 @@ def two_bars(dfplot, basecase, colors, ax, col=0, ypad=0.02): #%%### Copy some premade single-case plots -level = dictin_sw[basecase]['GSw_PRM_StressThreshold'].split('_')[0] +# Use first stress metric level +## TODO: add a check for choosing level if there are multiple stress metrics +stress_metrics = dictin_sw[basecase]['GSw_PRM_StressThresholdMetrics'].split('/') +level = dictin_sw[basecase][f'GSw_PRM_StressThreshold{stress_metrics[0]}'].split('_')[0] wide = 1 if len(hierarchy[basecase]['transreg'].unique()) > 6 else 0 metrics = [ 'cap', diff --git a/postprocessing/single_case_plots.py b/postprocessing/single_case_plots.py index 79a31000..58b4dfec 100644 --- a/postprocessing/single_case_plots.py +++ b/postprocessing/single_case_plots.py @@ -701,7 +701,9 @@ print(traceback.format_exc()) try: - level, threshold, _, metric = sw['GSw_PRM_StressThreshold'].split('/')[0].split('_') + _first_metric = sw['GSw_PRM_StressThresholdMetrics'].split('/')[0].upper() + _parts = sw[f'GSw_PRM_StressThreshold{_first_metric}'].split('_') + level, threshold, metric = _parts[0], _parts[1], _parts[2] plt.close() f,ax = reedsplots.plot_stressperiod_evolution( case=case, level=level, metric=metric) diff --git a/reeds/reedsplots.py b/reeds/reedsplots.py index dae1aa92..851bacca 100644 --- a/reeds/reedsplots.py +++ b/reeds/reedsplots.py @@ -484,7 +484,7 @@ def plot_diff( ymax = max(ax[0].get_ylim()[1], ax[1].get_ylim()[1]) ymin = min(ax[0].get_ylim()[0], ax[1].get_ylim()[0]) if val == 'NEUE (ppm)': - neue_threshold = float(sw.GSw_PRM_StressThreshold.split('_')[1]) + neue_threshold = float(sw.GSw_PRM_StressThresholdNEUE.split('_')[1]) ymax = max(ymax, 10, neue_threshold*1.05) ax[0].axhline(neue_threshold, c='C7', ls='--', lw=0.75) ax[1].axhline(neue_threshold, c='C7', ls='--', lw=0.75) @@ -4137,7 +4137,9 @@ def plot_stressperiod_evolution( """Plot NEUE by year and stress period iteration""" ### Parse inputs sw = reeds.io.get_switches(case) - _level, _threshold, _, _metric = sw['GSw_PRM_StressThreshold'].split('/')[0].split('_') + _first_metric = sw['GSw_PRM_StressThresholdMetrics'].split('/')[0].upper() + _parts = sw[f'GSw_PRM_StressThreshold{_first_metric}'].split('_') + _level, _threshold, _metric = _parts[0], _parts[1], _parts[2] level = _level if level is None else level threshold = float(_threshold) if threshold is None else threshold metric = _metric if metric is None else metric @@ -4261,8 +4263,9 @@ def plot_neue_bylevel( norm = {'sum':1, 'max':1e-4} ylabel = {'sum': 'Sum of NEUE [ppm]', 'max':'Max NEUE [%]'} thresholds = { - i.split('_')[0]: float(i.split('_')[1]) - for i in sw.GSw_PRM_StressThreshold.split('/') + sw[f'GSw_PRM_StressThreshold{m.upper()}'].split('_')[0]: + float(sw[f'GSw_PRM_StressThreshold{m.upper()}'].split('_')[1]) + for m in sw.GSw_PRM_StressThresholdMetrics.split('/') } ### Plot it plt.close() @@ -4326,8 +4329,8 @@ def map_neue( neue = reeds.io.read_output(case, f'neue_{year}i{_iteration}.csv') neue = neue.loc[neue.metric==metric].set_index(['level','region']).NEUE_ppm sw = reeds.io.get_switches(case) - neue_threshold = float(sw.GSw_PRM_StressThreshold.split('_')[1]) - neue_threshold_level = sw.GSw_PRM_StressThreshold.split('_')[0] + neue_threshold = float(sw.GSw_PRM_StressThresholdNEUE.split('_')[1]) + neue_threshold_level = sw.GSw_PRM_StressThresholdNEUE.split('_')[0] ### Set up plot levels = ['interconnect','nercr','transreg','transgrp','st','r'] @@ -6195,7 +6198,8 @@ def map_stressors( dfmap[k] = v.to_crs(crs) ### Derived inputs - criterion = sw.GSw_PRM_StressThreshold.split('/')[0] + _first_metric = sw.GSw_PRM_StressThresholdMetrics.split('/')[0].upper() + criterion = sw[f'GSw_PRM_StressThreshold{_first_metric}'] level = criterion.split('_')[0] regions = hierarchy[level].unique() region2rs = { @@ -6656,7 +6660,8 @@ def map_prm(case, tmin=2023, cmap=cmocean.cm.rain, scale=3, fontsize=7, vmax=Non ### Plot it nrows, ncols, coords = plots.get_coordinates(year2iteration.index, aspect=1.8) - level = sw.GSw_PRM_StressThreshold.split('_')[0] + _first_metric = sw.GSw_PRM_StressThresholdMetrics.split('/')[0].upper() + level = sw[f'GSw_PRM_StressThreshold{_first_metric}'].split('_')[0] plt.close() f,ax = plt.subplots( nrows, ncols, figsize=(ncols*scale, nrows*scale*0.8), sharex=True, sharey=True, From 041985be395522f4ed327b05886ba666247065f2 Mon Sep 17 00:00:00 2001 From: aayad Date: Fri, 29 May 2026 17:54:35 -0600 Subject: [PATCH 31/31] change default `GSw_PRM_StressThresholdMetrics` to NEUE --- cases.csv | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cases.csv b/cases.csv index 8c1309ba..9ea18ea7 100644 --- a/cases.csv +++ b/cases.csv @@ -268,9 +268,9 @@ GSw_PRM_StressOutages,Whether to apply the availability factor (forced + schedul GSw_PRM_StressSeedLoadLevel,Region hierarchy level at which to include peak coincident load days as seeded stress periods (or False to ignore peaks),false; False; FALSE; r; nercr; transreg; transgrp; cendiv; st; interconnect; country; usda_region; ccreg,transgrp, GSw_PRM_StressSeedMinRElevel,Region hierarchy level at which to include minimum wind and solar capacity factor days as seeded stress periods (or False to ignore min-wind/solar CF days),false; False; FALSE; r; nercr; transreg; transgrp; cendiv; st; interconnect; country; usda_region; ccreg,interconnect, GSw_PRM_StressStorageCutoff,"How to select ""shoulder"" stress periods giving storage time to recharge before/after high-unserved-energy periods. Two-part switch separated by _. The first argument is ""EUE"" or ""capacity"" or ""absolute"". If ""EUE"" the second argument specifies a PRAS storage headspace / EUE threshold [fraction]; if ""cap"" it specifies a headspace / storage capacity threshold [fraction]; if ""abs"" it specifies absolute number of periods before/after [integer]. Turned off if set to ""off"".",N/A,EUE_0.1, -GSw_PRM_StressThresholdMetrics,/-delimited list of metrics for identifying stress periods,N/A,EUE, -GSw_PRM_StressThresholdLOLE,LOLE threshold above which to re-solve the latest model year with new stress periods; formulated as HierarchyLevel_nLOLE_StressMetric_PeriodAggMethod where HierarchyLevel is a column in hierarchy.csv; nLOLE is number of loss of load events; StressMetric is LOLE; PeriodAggMethod is 'sum' or 'max' over the hours in each period (only used in period selection) (see README.md for detailed notes),N/A,transgrp_10_LOLE_sum, -GSw_PRM_StressThresholdEUE,/Annual EUE level [MWh] threshold above which to re-solve the latest model year with new stress periods; formulated as HierarchyLevel_EUE_StressMetric_PeriodAggMethod where HierarchyLevel is a column in hierarchy.csv; EUE is expected unserved energy; StressMetric EUE (only used in period selection); PeriodAggMethod is 'sum' or 'max' over the hours in each period (only used in period selection) (see README.md for detailed notes),N/A,transgrp_1000_EUE_sum, +GSw_PRM_StressThresholdMetrics,/-delimited list of metrics for identifying stress periods,N/A,NEUE, +GSw_PRM_StressThresholdLOLE,LOLE threshold above which to re-solve the latest model year with new stress periods; formulated as HierarchyLevel_nLOLE_StressMetric_PeriodAggMethod where HierarchyLevel is a column in hierarchy.csv; nLOLE is number of loss of load events; StressMetric is LOLE; PeriodAggMethod is 'sum' or 'max' over the hours in each period (only used in period selection) (see README.md for detailed notes),N/A,transgrp_36_LOLE_sum, +GSw_PRM_StressThresholdEUE,/Annual EUE level [MWh] threshold above which to re-solve the latest model year with new stress periods; formulated as HierarchyLevel_EUE_StressMetric_PeriodAggMethod where HierarchyLevel is a column in hierarchy.csv; EUE is expected unserved energy; StressMetric EUE (only used in period selection); PeriodAggMethod is 'sum' or 'max' over the hours in each period (only used in period selection) (see README.md for detailed notes),N/A,transgrp_10000_EUE_sum, GSw_PRM_StressThresholdNEUE,/Annual NEUE level [ppm] threshold above which to re-solve the latest model year with new stress periods; formulated as HierarchyLevel_NEUEppm_StressMetric_PeriodAggMethod where HierarchyLevel is a column in hierarchy.csv; NEUEppm is normalized expected unserved energy in parts per million; StressMetric is EUE or NEUE (only used in period selection); PeriodAggMethod is 'sum' or 'max' over the hours in each period (only used in period selection) (see README.md for detailed notes),N/A,transgrp_1_NEUE_sum, GSw_PRM_UpdateFraction,Fraction to add to the PRM if a region fails RA threshold (only used if GSw_PRM_UpdateMethod = 1),float,0.02, GSw_PRM_UpdateMethod,Option to update PRM: (0) no update; (1) static update set by GSw_PRM_UpdateFraction; (2) dynamic update informed by PRAS; (3) dynamic update but only after all new stress periods have been added,0; 1; 2; 3,0,