Skip to content

Add multi-metrics for RA#99

Open
abdelrahman-ayad wants to merge 46 commits into
mainfrom
aa/multi_metrics
Open

Add multi-metrics for RA#99
abdelrahman-ayad wants to merge 46 commits into
mainfrom
aa/multi_metrics

Conversation

@abdelrahman-ayad
Copy link
Copy Markdown
Collaborator

@abdelrahman-ayad abdelrahman-ayad commented May 20, 2026

Summary

  • This PR expands the use of Resource Adequacy metrics to use a multi-metrics approach. Users can select one or more metric (EUE, LOLH, NEUE, or a combination) with a corresponding threshold to be used in stress periods selection.
  • An upcoming PR will extend the available stress metrics to assess duration, frequency, and magnitude of shortfalls.

Technical details

Implementation notes

  • EUE is no longer used for determining the shoulder periods in function get_houlder_periods(), in case EUE is not defined as a stress metric
  • If EUE is not included as a stress metric, PRAS-informed PRM update is blocked, and fixed-increment update is used, similar to setting GSW_PRM_UpdateMethod=1

Switches added/removed/changed

  • GSw_PRM_StressThreshold -> GSw_PRM_StressThresholdMetrics: This switch now defines the / delimited stress metric switches to be used for evaluation. For each Metric, a dedicated switch is added to define the HierarchyLevel, Criterion, and the PeriodAggMethod. Current tested metrics are EUE, LOLH, and NEUE.

  • GSw_PRM_StressThresholdEUE: Switch to define the EUE stress metric hierarchy, threshold, and PeriodAggMethod.

  • GSw_PRM_StressThresholdLOLH: Switch to define the LOLH stress metric hierarchy, threshold, and PeriodAggMethod.

  • GSw_PRM_StressThresholdNEUE: Switch to define the NEUE stress metric hierarchy, threshold, and PeriodAggMethod.

Metric Switch Definition RA dimension Example
LOLH GSw_PRM_StressThresholdLOLH Expected (average) count of event-hours/year experiencing shortfall. It is expressed in terms of event-hours Frequency transgrp_2.4_sum: The LOLH threshold is set at 2.4 events-hour/year, aggregated using sum over transgrp
EUE GSw_PRM_StressThresholdEUE Expected (average) total energy shortfall over the study period, expressed in energy units (MWh per year) Energy transgrp_10000_sum: The EUE threshold is set at 10000 MWh over the study period, aggregated using sum over transgrp
NEUE GSw_PRM_StressThresholdNEUE Normalized expected (average) total energy shortfall over the study period expressed in parts-per-million (ppm) Energy transgrp_1_sum: The NEUE threshold is set at 1 ppm aggregated using sum over transgrp

Relevant sources or documentation

Slides deck reviewing Resource Adequacy multi-metrics:
RA multi metrics slides deck

Validation, testing, and comparison report(s)

Comparison report

To check

  • Pacific (EUE, LOLH, NEUE, EUE/NEUE/LOLH)
  • USA_defaults (EUE, LOLH, NEUE, EUE/NEUE/LOLH)

Checklist for author

Details to double-check

  • Included comparison reports for appropriate test cases
  • Documentation updated if necessary
  • Code formatting standardized
  • Reusable functions used where possible instead of copy/pasted code

General information to guide review

  • Zero impact on results of default case
  • No large data file(s) added/modified
  • No substantive impact on runtime for full-US reference case
  • No substantive impact on folder size for full-US reference case
  • No change to process flow (runreeds.py, reeds/core/solve/solve.py)
  • No change to code organization
  • No change to package requirements (environment.yml or Project.toml)

Did you use LLM tools (chatbot or copilot) in the preparation of this PR? If so, describe how

  • I used Claude to plan the changes in runreeds.py and stress_periods.py

@abdelrahman-ayad abdelrahman-ayad self-assigned this May 20, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends the Resource Adequacy (RA) stress-period selection workflow from a single-metric approach to a configurable multi-metric approach (EUE/LOLE/NEUE), with new switches to control which metrics and thresholds are applied.

Changes:

  • Replaces GSw_PRM_StressThreshold with GSw_PRM_StressThresholdMetrics plus per-metric threshold switches (...EUE, ...LOLE, ...NEUE) and validates these in runreeds.py.
  • Generalizes stress-period selection and annual metric writing logic to operate across multiple stress metrics in reeds/resource_adequacy/stress_periods.py.
  • Updates plotting and postprocessing utilities to consume the new NEUE output filenames/columns and new threshold switches.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
runreeds.py Validates new multi-metric stress threshold switch configuration.
reeds/resource_adequacy/stress_periods.py Implements multi-metric annual metric output and stress-period selection; updates PRM update gating.
reeds/resource_adequacy/run_pras.jl Updates PRAS summary logging to include NEUE via PRAS API.
reeds/reedsplots.py Updates plotting utilities to read NEUE_*.csv and new switch names/structures.
postprocessing/single_case_plots.py Updates stress-period evolution plot parameter parsing for new switches.
postprocessing/compare_cases.py Updates NEUE file discovery/parsing to match new output filenames.
cases.csv Adds new metric/threshhold switches and updates stress-iteration description.
Comments suppressed due to low confidence (1)

reeds/resource_adequacy/stress_periods.py:675

  • After introducing failed_regions_neue (NEUE-only) for PRAS-informed updates, the PRAS-informed branch should pass failed_regions_neue into prm_increment_pras(), and should fall back to fixed-increment (or raise) when there is no NEUE criterion available. Otherwise LOLE/EUE thresholds can be incorrectly treated as NEUE ppm targets inside prm_increment_pras().
    ## Fixed-increment update
    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
    else:
        prm_increment = prm_increment_pras(
            sw,
            t,
            iteration,
            combined_periods_write,
            failed_regions,
        )

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread reeds/resource_adequacy/stress_periods.py
Comment on lines 267 to 299
@@ -273,13 +283,9 @@

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]
@@ -288,8 +294,7 @@
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')
):
Comment on lines +418 to +425
# Validation check: Display any GSw_PRM_StressThreshold{metric}
# that is not specified in GSw_PRM_StressThresholdMetrics
stress_metric_switches = sw.GSw_PRM_StressThresholdMetrics.split('/')

# 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
stress_metric_switches}
Comment on lines +699 to +703
stress_metrics = sw.GSw_PRM_StressThresholdMetrics.split('/')
## TODO: check if need to refactor or remove
# Include NEUE if not already specified, used for plots - only used for writing files
if 'NEUE' not in stress_metrics:
stress_metrics.append('NEUE')
Comment on lines 637 to 655
# Get regions that failed criteria
_failed_regions = []
for criterion in failed:
# Example: criterion = 'transgrp_10_EUE_sum'
(hierarchy_level, ppm, __, __) = criterion.split('_')
# Recover regions where the PRM criterion failed
rmap = reeds.io.get_rmap(sw['casedir'], hierarchy_level=hierarchy_level).reset_index()
df = rmap.loc[
rmap[hierarchy_level].isin(failed[criterion].index)
].rename(columns={hierarchy_level:'region'})
df['hierarchy_level'] = hierarchy_level
df['ppm'] = float(ppm)
_failed_regions.append(df)
# For zones that failed multiple criteria, use the most stringent (lowest EUE target)
failed_regions = (
pd.concat(_failed_regions)
.sort_values(by=['ppm'])
.drop_duplicates(subset='r', keep='first')
)
Comment thread postprocessing/single_case_plots.py
Comment thread runreeds.py Outdated
Comment thread postprocessing/compare_cases.py Outdated
Comment thread reeds/reedsplots.py
Comment on lines 4221 to 4225
year2iteration = (
pd.DataFrame([
os.path.basename(i).strip('neue_.csv').split('i')
for i in sorted(glob(os.path.join(case, 'outputs', 'neue_*.csv')))
os.path.basename(i).strip('NEUE_.csv').split('i')
for i in sorted(glob(os.path.join(case, 'outputs', 'NEUE_*.csv')))
], columns=['year','iteration']).astype(int)
Comment thread reeds/reedsplots.py
Comment on lines 6636 to 6640
year2iteration = (
pd.DataFrame([
os.path.basename(i).strip('neue_.csv').split('i')
for i in sorted(glob(os.path.join(case, 'outputs', 'neue_*.csv')))
os.path.basename(i).strip('NEUE_.csv').split('i')
for i in sorted(glob(os.path.join(case, 'outputs', 'NEUE_*.csv')))
], columns=['year','iteration']).astype(int)
Copy link
Copy Markdown
Contributor

@patrickbrown4 patrickbrown4 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is cool, thanks! I haven't gone all the way through stress_periods.py yet, but adding some interim comments. My main thoughts are:

  1. I would call it LOLH instead of LOLE if the units are event-hours
  2. I think it would simplify the processing to drop the third argument of the compound GSw_PRM_StressThreshold{LOLE|EUE|NEUE} switches; instead of leaving it up to the user, NEUE- and EUE-based thresholds would always add stress periods with the highest EUE, and the LOLH-based threshold would always add stress periods with the highest LOLH. (When we add the depth-based metric, it would always add stress periods with the highest peak outage MW.)

Comment thread cases.csv Outdated
Comment thread cases.csv Outdated
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,
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,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says "events", but the PR text and run_pras.jl say "event-hours". Assuming it is actually event-hours, can you:

  • Change the name here and throughout the code to LOLH?

It's also not ideal to bake the number of weather years into the threshold, because then users would need to remember to adjust two things if they change the number of weather years.

  • Can you instead formulate it as event-hours per year, with a default of 2.4? (Or if it were LOLE and not LOLH, 0.1?)
  • Add the units to the comment: [hours/year] ([events/year] if it were LOLE)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, is the third element (StressMetric) required? If only one option is supported, it can be dropped. (As noted below, I think you can drop the third element for all of the new switches.)

Comment thread cases.csv Outdated
Comment thread cases.csv Outdated
Comment thread runreeds.py Outdated
Comment thread reeds/resource_adequacy/stress_periods.py Outdated
#%% Write consolidated NEUE so far
#%% Write consolidated stress metrics so far
try:
## TODO: Check if get_and_write_neue() is still needed
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't use the national NEUE within the model so it can be removed (sorry for all the cruft). You would also remove the following blocks. It's kind of a lot though, and it's not directly related to this PR, so it might be best to save for a followup cleanup PR.

('Resource adequacy',
{'file':'neue.csv',
'columns': ['year', 'iteration', 'neue'],
'index': ['year', 'iteration'],
'preprocess': [
{'func': pre_neue, 'args': {}},
],
'presets': collections.OrderedDict((
('Normalized expected unserved energy [%]', {'x':'year', 'y':'neue', 'chart_type':'Bar', 'explode':'scenario', 'bar_width':'1.75'}),
)),
}
),

{'name': 'NEUE (ppm)', 'result': 'Resource adequacy', 'preset': 'Normalized expected unserved energy [%]'},

All the 'NEUE (ppm)' keys in reedsplots.plot_diff() like

'NEUE (ppm)': 'NEUE',

ReEDS/reeds/reedsplots.py

Lines 486 to 490 in ce7c0d5

if val == 'NEUE (ppm)':
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)

Comment thread postprocessing/compare_cases.py Outdated
Comment on lines +25 to +29
# 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'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coercing arguments makes me nervous; my suggestion would just be to call it with the right argument. You could use a mapper like:

use_metric = {'NEUE':'EUE'}
get_pras_stress_metric(stress_metric=use_metric.get(stress_metric, stress_metric))

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in the PR revision.

Comment on lines +87 to +95
if stress_metric.upper() != 'NEUE':
### 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'])
)
elif stress_metric.upper() == 'NEUE':
else:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it'd be clearest to drop support for stress_metric = 'NEUE' and just keep the first block.

To clarify, we'd keep GSw_PRM_StressThresholdNEUE, and the default GSw_PRM_StressThresholdMetrics would still be NEUE; we'd just drop the third argument of the compound switches and use EUE for stress period selection for both the GSw_PRM_StressThresholdEUE and GSw_PRM_StressThresholdNEUE metrics

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in PR revision. dfmetric is now determined using EUE for the EUE and NEUE cases, so dfmetric_period is calculated accordingly

Copy link
Copy Markdown
Contributor

@patrickbrown4 patrickbrown4 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, happy to discuss any of these if helpful (particularly the one about dropping GSw_PRM_StressThresholdEUE). Could you re-request a review when I should take another look?

Comment thread cases.csv
Comment on lines +273 to +275
GSw_PRM_StressThresholdLOLH,LOLH threshold [hours/year] above which to re-solve the latest model year with new stress periods; formulated as HierarchyLevel_LOLH_StressMetric_PeriodAggMethod where HierarchyLevel is a column in hierarchy.csv; LOLH is number of loss of load event-hours; StressMetric is LOLH; 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_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_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_sum,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Change default LOLH from 36 to 2.4
  • Tweak description to match new 3-element structure
  • If the EUE option is kept (see note above), change the input for GSw_PRM_StressThresholdEUE to per-year as well
    • including downstream in stress_periods.py if it is not already
Suggested change
GSw_PRM_StressThresholdLOLH,LOLH threshold [hours/year] above which to re-solve the latest model year with new stress periods; formulated as HierarchyLevel_LOLH_StressMetric_PeriodAggMethod where HierarchyLevel is a column in hierarchy.csv; LOLH is number of loss of load event-hours; StressMetric is LOLH; 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_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_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_sum,
GSw_PRM_StressThresholdLOLH,LOLH threshold [hours/year] above which to re-solve the latest model year with new stress periods; formulated as HierarchyLevel_LOLH_PeriodAggMethod where HierarchyLevel is a column in hierarchy.csv; LOLH is loss of load event-hours per year [event-h/year]; 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_2.4_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_PeriodAggMethod where HierarchyLevel is a column in hierarchy.csv; EUE is expected unserved energy per year [MWh/year]; 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_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_PeriodAggMethod where HierarchyLevel is a column in hierarchy.csv; NEUEppm is normalized expected unserved energy in parts per million [ppm]; 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_sum,

eue.to_csv(os.path.join(sw['casedir'],'outputs','eue.csv'))
return neue


Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(nitpick) standard is 2 lines between function definitions

Comment on lines +204 to 205
dfmetric.rename(columns=rmap).groupby(axis=1, level=0).sum().sum()
/ dfload.rename(columns=rmap).groupby(axis=1, level=0).sum().sum()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
dfmetric.rename(columns=rmap).groupby(axis=1, level=0).sum().sum()
/ dfload.rename(columns=rmap).groupby(axis=1, level=0).sum().sum()
dfmetric.rename(columns=rmap).groupby(axis=1, level=0).sum().sum()
/ dfload.rename(columns=rmap).groupby(axis=1, level=0).sum().sum()

Comment on lines +209 to 210
dfmetric.rename(columns=rmap).groupby(axis=1, level=0).sum()
/ dfload.rename(columns=rmap).groupby(axis=1, level=0).sum()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
dfmetric.rename(columns=rmap).groupby(axis=1, level=0).sum()
/ dfload.rename(columns=rmap).groupby(axis=1, level=0).sum()
dfmetric.rename(columns=rmap).groupby(axis=1, level=0).sum()
/ dfload.rename(columns=rmap).groupby(axis=1, level=0).sum()

Comment on lines 276 to -292
@@ -288,8 +263,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)))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default GSw_PRM_StressStorageCutoff is still EUE_0.1, which looks like it means the default shoulder-period functionality would be removed with this change.

I think we should keep the original EUE formulation in get_shoulder_periods(). It should apply for both GSw_PRM_StressThresholdEUE and GSw_PRM_StressThresholdNEUE (both of which use EUE to select stress periods).

I'm not sure if it should apply to GSw_PRM_StressThresholdLOLH-identified periods. If it does, I think it's fine to still use EUE to identify those shoulder periods. If that complicates things, you can just drop the shoulder functionality for LOLH-identified periods (but keep it for the NEUE-/EUE-identified periods).

Comment thread cases.csv
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 (supported options are: NEUE, EUE, LOLH)",N/A,NEUE,
GSw_PRM_StressThresholdLOLH,LOLH threshold [hours/year] above which to re-solve the latest model year with new stress periods; formulated as HierarchyLevel_LOLH_StressMetric_PeriodAggMethod where HierarchyLevel is a column in hierarchy.csv; LOLH is number of loss of load event-hours; StressMetric is LOLH; 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_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_sum,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you remind me, do any of the regions you reviewed formulate their RA target as EUE [MWh] instead of NEUE [ppm]? EUE MWh would be a bit of a weird threshold in the context of load growth, and a single number wouldn't really make sense to apply across different transgrp regions with different loads (as is specified in the default value).

So I guess my feeling is that it might be simpler just to drop GSw_PRM_StressThresholdEUE and stick with NEUE and LOLH as the two options. What do you think?


## Fixed-increment update
if int(sw.GSw_PRM_UpdateMethod) == 1:
if int(sw.GSw_PRM_UpdateMethod) == 1 or 'EUE' not in stress_metrics:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

update_prm() currently works with NEUE as the only stress metric, so we should maintain that functionality.

Instead of adding stress_metrics as an input and adding this conditional, could you instead just pass it the entries of failed that arise from the NEUE criterion?

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, _stress_metric, metric = _parts[0], _parts[1], _parts[2], _parts[3]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are now 3-part switches, so the old 3rd element should be removed

Suggested change
level, threshold, _stress_metric, metric = _parts[0], _parts[1], _parts[2], _parts[3]
level, threshold, metric = _parts

Comment thread reeds/reedsplots.py
'Runtime (hours)': 'processtime',
'Runtime by year (hours)': 'processtime',
'NEUE (ppm)': 'neue',
'NEUE (ppm)': 'NEUE',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function uses the bokeh outputs, which I don't think were changed

Suggested change
'NEUE (ppm)': 'NEUE',
'NEUE (ppm)': 'neue',

Comment thread reeds/reedsplots.py
_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, _stress_metric, _metric = _parts[0], _parts[1], _parts[2], _parts[3]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
_level, _threshold, _stress_metric, _metric = _parts[0], _parts[1], _parts[2], _parts[3]
_level, _threshold, _metric = _parts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants