Add multi-metrics for RA#99
Conversation
There was a problem hiding this comment.
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_StressThresholdwithGSw_PRM_StressThresholdMetricsplus per-metric threshold switches (...EUE,...LOLE,...NEUE) and validates these inrunreeds.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 passfailed_regions_neueinto 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.
| @@ -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') | |||
| ): | |||
| # 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} |
| 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') |
| # 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') | ||
| ) |
| 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) |
| 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) |
patrickbrown4
left a comment
There was a problem hiding this comment.
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:
- I would call it LOLH instead of LOLE if the units are event-hours
- 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.)
| 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, |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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.)
| #%% Write consolidated NEUE so far | ||
| #%% Write consolidated stress metrics so far | ||
| try: | ||
| ## TODO: Check if get_and_write_neue() is still needed |
There was a problem hiding this comment.
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.
ReEDS/postprocessing/bokehpivot/reeds2.py
Lines 2764 to 2775 in ce7c0d5
ReEDS/postprocessing/compare_cases.py
Line 196 in ce7c0d5
All the 'NEUE (ppm)' keys in reedsplots.plot_diff() like
Line 117 in ce7c0d5
Lines 486 to 490 in ce7c0d5
| # 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' | ||
|
|
There was a problem hiding this comment.
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))There was a problem hiding this comment.
Fixed in the PR revision.
| 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: |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Addressed in PR revision. dfmetric is now determined using EUE for the EUE and NEUE cases, so dfmetric_period is calculated accordingly
patrickbrown4
left a comment
There was a problem hiding this comment.
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?
| 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, |
There was a problem hiding this comment.
- 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_StressThresholdEUEto per-year as well- including downstream in
stress_periods.pyif it is not already
- including downstream in
| 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 | ||
|
|
||
|
|
There was a problem hiding this comment.
(nitpick) standard is 2 lines between function definitions
| dfmetric.rename(columns=rmap).groupby(axis=1, level=0).sum().sum() | ||
| / dfload.rename(columns=rmap).groupby(axis=1, level=0).sum().sum() |
There was a problem hiding this comment.
| 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() |
| dfmetric.rename(columns=rmap).groupby(axis=1, level=0).sum() | ||
| / dfload.rename(columns=rmap).groupby(axis=1, level=0).sum() |
There was a problem hiding this comment.
| 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() |
| @@ -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))) | |||
There was a problem hiding this comment.
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).
| 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, |
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
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] |
There was a problem hiding this comment.
These are now 3-part switches, so the old 3rd element should be removed
| level, threshold, _stress_metric, metric = _parts[0], _parts[1], _parts[2], _parts[3] | |
| level, threshold, metric = _parts |
| 'Runtime (hours)': 'processtime', | ||
| 'Runtime by year (hours)': 'processtime', | ||
| 'NEUE (ppm)': 'neue', | ||
| 'NEUE (ppm)': 'NEUE', |
There was a problem hiding this comment.
This function uses the bokeh outputs, which I don't think were changed
| 'NEUE (ppm)': 'NEUE', | |
| 'NEUE (ppm)': 'neue', |
| _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] |
There was a problem hiding this comment.
| _level, _threshold, _stress_metric, _metric = _parts[0], _parts[1], _parts[2], _parts[3] | |
| _level, _threshold, _metric = _parts |
Summary
Technical details
Implementation notes
EUEis no longer used for determining the shoulder periods in functionget_houlder_periods(), in caseEUEis not defined as a stress metricEUEis not included as a stress metric, PRAS-informed PRM update is blocked, and fixed-increment update is used, similar to settingGSW_PRM_UpdateMethod=1Switches added/removed/changed
GSw_PRM_StressThreshold->GSw_PRM_StressThresholdMetrics: This switch now defines the/delimited stress metric switches to be used for evaluation. For eachMetric, a dedicated switch is added to define theHierarchyLevel,Criterion, and thePeriodAggMethod. Current tested metrics areEUE,LOLH, andNEUE.GSw_PRM_StressThresholdEUE: Switch to define theEUEstress metric hierarchy, threshold, and PeriodAggMethod.GSw_PRM_StressThresholdLOLH: Switch to define theLOLHstress metric hierarchy, threshold, and PeriodAggMethod.GSw_PRM_StressThresholdNEUE: Switch to define theNEUEstress metric hierarchy, threshold, and PeriodAggMethod.LOLHGSw_PRM_StressThresholdLOLHtransgrp_2.4_sum: TheLOLHthreshold is set at 2.4 events-hour/year, aggregated usingsumovertransgrpEUEGSw_PRM_StressThresholdEUEtransgrp_10000_sum: TheEUEthreshold is set at 10000 MWh over the study period, aggregated usingsumovertransgrpNEUEGSw_PRM_StressThresholdNEUEtransgrp_1_sum: TheNEUEthreshold is set at 1 ppm aggregated usingsumovertransgrpRelevant 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
Checklist for author
Details to double-check
General information to guide review
Did you use LLM tools (chatbot or copilot) in the preparation of this PR? If so, describe how
runreeds.pyandstress_periods.py