RAChronoOps provides a systematic storage-dispatch fidelity ladder for sequential Monte Carlo resource adequacy (RA), building on prior work showing that storage dispatch affects adequacy metrics (Gonzato et al. 2023; PRAS/Stephen 2021). The project compares naive proactive heuristics, PRAS/Evans-style emergency-only dispatch, event-window LP, full-year ED, PCM-ED, and PCM-UCED under common random numbers on the public RTS-GMLC single-zone test system. It quantifies which storage dispatch approximations preserve EUE, LOLH, CVaR-EUE, and runtime performance, and introduces a storage-energy sufficiency-bound diagnostic that explains when simpler methods recover full-year ED EUE.
| Label | Paper name | Method | Optimization | Status |
|---|---|---|---|---|
| MC-NoStorage | Traditional MC | Traditional hourly sequential MC without storage | None | Implemented — no-storage baseline |
| RA-1a / M1 | Naive Storage MC | Sequential MC + naive peak-shaving storage heuristic | None | Implemented — cautionary baseline |
| RA-1b / M1b | Reserve-Floor MC | Sequential MC + reserve-aware storage heuristic | None | Implemented and validated |
| RA-1c / M1c | Emergency-Only MC | Sequential MC + PRAS/Evans-style emergency-only dispatch (system-surplus charging) | None | Implemented and validated |
| M1c_VREOnly | VRE-Surplus MC | Same as M1c but charges from VRE surplus only | None | Implemented — appendix sensitivity |
| RA-1d / M1d | Risk-Hour MC | Sequential MC + risk-hour allocation heuristic (earliest_first / largest_first) | None | Implemented — within-event allocation study |
| RA-2 / M2 | Event-Window LP-MC | Sequential MC + screened event-window LP near risk periods (proposed scalable method) | Small LPs | Implemented and validated |
| RA-3 / M3 | Full-Year ED-MC | Sequential MC + full-year ED LP per scenario | LP (Gurobi) | Implemented — benchmark |
| HOPE-ED | PCM-ED | Full-year HOPE ED LP (PCM economic dispatch mode) | LP (Gurobi) | Validated — matches M3 |
| HOPE-UC / M4 | PCM-UCED | Full-year HOPE UC/PCM with real Pmin/ramp/startup/min-up/down | MILP (Gurobi) | Validated — N=5 and N=20 |
All implemented methods use common random numbers: one shared
ScenarioSet of thermal outage draws is passed to every method, so
reliability metric differences are attributable to dispatch strategy alone.
# 1. Install Julia dependencies
julia --project=. -e "using Pkg; Pkg.instantiate()"
# 2. Download RTS-GMLC data (requires git on PATH)
julia --project=. scripts/00_get_rts_gmlc_data.jl
# 3. Build the processed single-zone dataset
julia --project=. scripts/01_build_single_zone_rts.jl
# 4. Build VRE experiment cases
julia --project=. scripts/06_build_experiment_cases.jl
# 5. Build no-storage case variants
julia --project=. scripts/33_build_no_storage_cases.jl
# 6. No-storage MC vs ED baseline comparison (N=20)
julia --project=. scripts/34_compare_no_storage_classic_vs_ed.jl \
--cases VRE120_base,VRE120_wind_hvy --n-scenarios 20 --seed 42
# 7. Export HOPE full-year cases (5 scenarios, ED + UC)
julia --project=. scripts/25_build_hope_full_year_cases.jl \
--case VRE120_base --modes ED,UC --n-scenarios 20 --seed 42
# 8. HOPE no-storage four-model comparison (requires script 29 + 27 first)
julia --project=. scripts/36_compare_nostorage_hope_uc_n5.jl
# 9. Wind-heavy five-model storage-enabled HOPE-UC comparison
julia --project=. scripts/37_compare_wind_hvy_hope_uc_n5.jlThe full experiment sequence is complete. Clean narrative: traditional sequential MC is valid without storage; storage introduces intertemporal SOC coupling that makes reliability estimates sensitive to dispatch assumptions; M1c and M2 recover M3/HOPE EUE at much lower runtime; HOPE-UC mainly changes LOLH/event timing, not EUE. A storage-energy sufficiency bound explains why EUE convergence across M1c/M1d/M2/M3 is observed in the tested RTS-GMLC cases: in these configurations, the binding constraint is storage energy availability (MWh) rather than dispatch complexity.
MC-NoStorage = M3-NoStorage exactly for VRE120_base and VRE120_wind_hvy at N=20. For VRE120_base_nostorage at N=5, all four models agree: MC-NoStorage = M3-NoStorage = PCM-ED-NS (HOPE-ED-NoStorage) = PCM-UCED-NS (HOPE-UC-NoStorage) (ΔEUE = 0.00 MWh, ΔLOLH = 0.0 h).
Interpretation: traditional sequential MC is valid in the classic no-storage RA setting. Without storage there is no intertemporal state variable, so LP and MILP dispatch collapse to the same feasible set as the classical capacity check.
Scripts: scripts/33_build_no_storage_cases.jl,
scripts/34_compare_no_storage_classic_vs_ed.jl,
scripts/36_compare_nostorage_hope_uc_n5.jl
Results: results/no_storage_comparison/, results/nostorage_hope_uc_comparison/
| Model | LOLH vs M3 | EUE vs M3 | Runtime |
|---|---|---|---|
| M1 (naive heuristic) | High bias | High bias | ~1 s/scenario |
| M1b (reserve-aware) | Reduced bias | Reduced bias | ~1 s/scenario |
| M1c (emergency-only) | Matches M3 | Matches M3 | ~1–2 s/scenario |
| M2 (event-window LP) | Within 0.2 h | Matches M3 | 5–10 s/scenario |
| M3 (full-year ED) | — | — (benchmark) | ~9–10 s/scenario |
M1 / M1b overestimate reliability risk (naive peak-shaving depletes storage before shortage events). M1c and M2 recover M3 EUE/CVaR closely.
Recommended M2 config: risk_margin_mw=1000, window_buffer_hours=48.
Scripts: scripts/14_run_ra1b_validation.jl,
scripts/16_run_vre_method_comparison.jl, scripts/30_compare_all_models_hope_n5.jl
PCM-ED (HOPE-ED internally) matches M3 once ED-mode ramp constraints are disabled (ΔEUE < 1 MWh). PCM-UCED (HOPE-UC internally) uses real Pmin, ramp rates, startup costs, and min up/down times from the RTS-GMLC dataset.
In tested cases (VRE120_base N=20; VRE120_wind_hvy N=5), PCM-UCED mainly changes LOLH/event timing, not EUE:
| Case | Model | LOLH (h) | EUE (MWh) | Runtime (s) |
|---|---|---|---|---|
| VRE120_base | PCM-ED | 6.2 | 2,479 | 2,356 |
| VRE120_base | PCM-UCED | 7.2 | 2,479 | 11,438 |
| VRE120_wind_hvy | PCM-ED | 3.8 | 1,113 | 588 |
| VRE120_wind_hvy | PCM-UCED | 4.2 | 1,113 | 2,712 |
Runtime note: PCM-UCED is 4–5× slower than PCM-ED with zero EUE benefit in the tested cases. UC is worth running only when storage is present (storage SOC is the intertemporal link that activates UC timing effects).
Scripts: scripts/25_build_hope_full_year_cases.jl,
scripts/29_run_hope_n5_pilot.jl, scripts/27_collect_hope_results.jl,
scripts/37_compare_wind_hvy_hope_uc_n5.jl
Results: results/wind_hvy_hope_uc_comparison/, results/hope_wind_hvy_n5_pilot/
The sufficiency bound derives a per-scenario ceiling on EUE reduction from storage, given surplus energy available in a 72-hour lookback window before each shortage event:
coverage_bound = min(pre_event_EUE,
feasible_discharge_energy, -- SOC × η_dis after charging
power_limited_coverage) -- Σ min(shortfall[h], power_mw)
| Case | Pre-storage EUE | Bound/M3 EUE | Sufficiency ratio |
|---|---|---|---|
| VRE120_base | 31,017 MWh | 2,479 MWh | 0.941 |
| VRE120_wind_hvy | 15,801 MWh | 648 MWh | 0.972 |
The bound matches M3 EUE exactly per scenario (ΔEUE = 0.00 MWh). Binding constraint in tested cases: storage energy (MWh), not power (MW).
Key insight: M1c, M1d_earliest, M2, and M3 all achieve the bound because they satisfy its two sufficient conditions (charge from surplus; discharge only at shortfall hours). M1/M1b violate condition 2 by proactively discharging, depleting SOC before shortage events and moving away from the bound.
Why LOLH can differ even when EUE matches: EUE is set by the storage energy budget; LOLH counts hours with any positive load shed. M1d_largest reallocates discharge to the highest-shortfall hours first, leaving smaller shortfall hours partially served (more LOLH, same EUE). LP degeneracy and HOPE-UC commitment constraints produce the same effect for different reasons.
Scripts: scripts/38_compare_m1d_storage_heuristics.jl,
scripts/39_storage_energy_sufficiency_bound.jl
Results: results/m1d_storage_heuristic_comparison/,
results/storage_energy_sufficiency_bound/
Documentation: docs/storage_energy_sufficiency_bound.md
Full numeric results and per-script output paths: docs/results_index.md and docs/current_findings_synthesis.md.
The following early experiments informed the current design but are no longer the primary results:
- Rolling-window M2 (
M2RollingWindow.jl): solves one LP per hour per scenario — no runtime advantage over M3. Replaced by the event-window M2 (M2EventWindowLP.jl). - HOPE stress-week validation (Phase E placeholder): the project moved to full-year HOPE ED/UC, making the stress-week-only approach unnecessary.
- RA-1a / M1 diagnosis: priority-2 proactive discharge depletes SOC to zero before 100% of shortage events; motivates M1b and M1c.
See docs/experiment_archive.md for full diagnostic results.
The official RTS-GMLC dataset is hosted at https://github.com/GridMod/RTS-GMLC.
# Clone RTS-GMLC into data_raw/RTS-GMLC/ (idempotent)
julia --project=. scripts/00_get_rts_gmlc_data.jl
# Build the 8760-hour single-zone processed dataset
julia --project=. scripts/01_build_single_zone_rts.jl
# Optionally verify: print system summary + write results/data_summary/ CSVs
julia --project=. scripts/04_summarize_processed_data.jlBuildRTSSingleZone.jl aggregates the RTS-GMLC three-area system into a
single copper-plate zone:
| Step | Detail |
|---|---|
| Generator fleet | 133 units: 73 thermal + 60 VRE (4 WIND, 25 PV, 31 RTPV) |
| Excluded types | HYDRO (19), ROR (1), CSP (1), STORAGE (1), SYNC_COND (3) — logged as warnings |
| Heat rate | HR_avg_0 column (BTU/kWh) ÷ 1000 → MMBTU/MWh; variable_cost = VOM + HR × fuel_price |
| Load | DAY_AHEAD_regional_Load.csv columns 5+ (three regions) summed per hour, truncated to 8760 h |
| Wind CF | WIND/DAY_AHEAD_wind.csv MW ÷ total wind capacity (2507.9 MW), clamped to [0, 1] |
| Solar CF | PV/DAY_AHEAD_pv.csv + RTPV/DAY_AHEAD_rtpv.csv MW ÷ total solar capacity (2715.9 MW) |
| Storage | One aggregate 4-hour battery: 10% of peak load power, η = √0.90 per half-trip |
| Validation | Strict (8760 h required) when RTS data detected; lenient for synthetic fallback |
After running script 01, results/data_summary/system_summary.csv should show:
| Metric | Value |
|---|---|
| n_hours | 8760 |
| Peak load | 8191.8 MW |
| Annual load | 37,561 GWh |
| Thermal capacity | 8076 MW (73 units) |
| Wind capacity | 2508 MW (CF 32.4%) |
| Solar capacity | 2716 MW (CF 24.7%) |
| Storage | 819 MW / 3276 MWh (4 h) |
If RTS-GMLC data is absent (e.g., CI or first checkout without step 00), the builder writes a deterministic 168-hour system so that tests and scripts still run.
scripts/06_build_experiment_cases.jl writes one subfolder per case under
data_processed/cases/.
| Experiment group | Scale factors applied | Status |
|---|---|---|
| Load scaling (×5) | load_scale ∈ {1.00, 1.05, 1.10, 1.15, 1.20} |
Diagnostic — completed |
| Storage matrix (×12) | storage_power_pct_peak ∈ {5%, 10%, 20%} × storage_duration_hours ∈ {2, 4, 8, 12} |
Diagnostic — completed |
| VRE penetration/profile (×6) | wind/solar scale pairs — see docs/redesigned_experiment_plan.md | Main experiment — completed |
julia --project=. scripts/06_build_experiment_cases.jlAll implemented methods compute a common set of metrics stored in
MetricsResult (see src/metrics/ReliabilityMetrics.jl).
| Metric | Field | Definition |
|---|---|---|
| LOLH | lolh |
Mean loss-of-load hours per year (h/yr) |
| LOLP | lolp |
Loss-of-load probability = LOLH / 8760 (direct normalization in hourly sequential simulation) |
| LOLE days | lole_days |
Mean days per year with ≥ 1 shortage hour |
| Metric | Field | Definition |
|---|---|---|
| EUE | eue |
Expected unserved energy (MWh/yr) |
| nEUE | neue |
EUE / annual load energy (fraction; report in ppm = × 10⁶) |
| Metric | Field | Definition |
|---|---|---|
| Event count | n_shortage_events |
Mean number of distinct contiguous shortage events per scenario |
| Mean duration | mean_shortage_duration |
Mean event duration (h) |
| Max duration | max_shortage_duration |
Maximum event duration across all scenarios (h) |
| p95 duration | p95_shortage_duration |
95th percentile of event duration pooled across scenarios (h) |
| Metric | Field | Definition |
|---|---|---|
| Max shortfall | max_shortfall |
Maximum single-hour load shed across all scenario-hours (MW) |
| Mean shortfall | mean_shortfall_when_shedding |
Mean load shed conditional on shed > 0 (MW) |
| Metric | Field | Definition |
|---|---|---|
| p50/p90/p95/p99 EUE | p{q}_scenario_eue |
Percentiles of the per-scenario EUE distribution (MWh) |
| CVaR-EUE | cvar_eue |
Mean of the top 5% of per-scenario EUEs (MWh) |
| Metric | Field | Definition |
|---|---|---|
| LOLH CI95 | lolh_ci95_halfwidth |
1.96 × std(per-scenario LOLH) / √N (h) |
| EUE CI95 | eue_ci95_halfwidth |
1.96 × std(per-scenario EUE) / √N (MWh) |
The study evaluates centralized adequacy-oriented storage dispatch. The following are explicitly outside scope:
- Merchant storage behavior: no bidding, capacity withholding, strategic behavior, or decision-dependent storage availability.
- Operating reserves and ancillary services: no reserve requirements or forecast-error-driven unit commitment beyond traditional RA assumptions.
- Network constraints: single copper-plate zone; multi-area systems are future work.
- Transmission network, demand response, hydro water budget.
- Imperfect foresight and investment re-optimization.
These exclusions align with traditional RA conventions and define the boundary of applicability. Unit commitment is introduced only in PCM-UCED (HOPE-UC) as the high-fidelity validation benchmark.
RAChronoOps/
Project.toml
docs/
current_findings_synthesis.md # key findings across all experiments
redesigned_experiment_plan.md # current experiment design
results_index.md # index of result folders
storage_energy_sufficiency_bound.md # theory note on the sufficiency bound
experiment_archive.md # completed diagnostic results
hope_full_year_case_preparation.md # HOPE case export details
next_experiment_design_nostorage_and_real_uc.md
ra2_n20_validation_memo.md
vre_method_comparison_memo.md
m1c_charging_assumption_memo.md
ra1b_validation_memo.md
src/
RAChronoOps.jl
utils/
Config.jl
IO.jl
data/
GetRTSGMLCData.jl
LoadData.jl
BuildRTSSingleZone.jl
ExportHOPECase.jl
scenarios/
SequentialOutages.jl
models/
McNoStorage.jl # classical no-storage MC baseline
M1RuleBasedStorage.jl # RA-1a: naive peak-shaving heuristic
M1bReserveAwareStorage.jl # RA-1b: reserve-aware heuristic
M1cEmergencyOnlyStorage.jl # RA-1c: emergency-only heuristic
M1cVREOnlyCharge.jl # M1c_VREOnly: VRE-surplus charging variant
M1dRiskHourAllocation.jl # RA-1d: risk-hour allocation heuristic
M2EventWindowLP.jl # RA-2: event-window LP
M2RollingWindow.jl # rolling-window LP (archived diagnostic)
M3EDDispatch.jl # RA-3: full-year ED LP benchmark
metrics/
ReliabilityMetrics.jl
experiments/
RunExperiment.jl
scripts/
# ── data preparation ──────────────────────────────────────────────────
00_get_rts_gmlc_data.jl
01_build_single_zone_rts.jl
04_summarize_processed_data.jl
05_smoke_test_full_rts_data.jl
06_build_experiment_cases.jl
# ── main comparison scripts ───────────────────────────────────────────
30_compare_all_models_hope_n5.jl # M1c/M2/M3/HOPE-ED on base case N=5
33_build_no_storage_cases.jl # build <case>_nostorage variants
34_compare_no_storage_classic_vs_ed.jl # MC-NoStorage vs M3-NoStorage
36_compare_nostorage_hope_uc_n5.jl # four-model no-storage HOPE-UC check
37_compare_wind_hvy_hope_uc_n5.jl # five-model wind-heavy HOPE-UC check
38_compare_m1d_storage_heuristics.jl # M1c/M1d/M2/M3 within-event allocation study
39_storage_energy_sufficiency_bound.jl # theoretical sufficiency bound diagnostic
# ── HOPE export and run ───────────────────────────────────────────────
25_build_hope_full_year_cases.jl # export HOPE full-year case folders
27_collect_hope_results.jl # collect HOPE output metrics
29_run_hope_n5_pilot.jl # run HOPE cases via Julia subprocess
# ── diagnostic scripts (archived) ────────────────────────────────────
09_calibrate_load_scaling.jl
10_run_storage_matrix.jl
11_debug_storage_cases.jl
12_run_selected_storage_validation.jl
13_debug_m1_storage_sensitivity.jl
14_run_ra1b_validation.jl
16_run_vre_method_comparison.jl
test/
runtests.jl
test_storage.jl
test_m1d.jl
test_outages.jl
test_power_balance.jl
test_common_scenarios.jl
results/
# main experiment results
no_storage_comparison/ # MC-NoStorage vs M3-NoStorage (N=20)
nostorage_hope_uc_comparison/ # four-model no-storage HOPE-UC check
wind_hvy_hope_uc_comparison/ # five-model wind-heavy HOPE-UC check
hope_wind_hvy_n5_pilot/ # HOPE run status + metrics for wind-hvy
hope_nostorage_n5_pilot/ # HOPE run status + metrics for nostorage
full_model_comparison_with_hope/ # M1c/M2/M3/HOPE-ED on base case
vre_method_comparison/ # M1/M1b/M1c/M2/M3 across VRE cases
m1d_storage_heuristic_comparison/ # M1c/M1d/M2/M3 within-event allocation
storage_energy_sufficiency_bound/ # theoretical sufficiency bound
# diagnostic results (archived)
storage_matrix/
storage_validation/
m1_debug/
load_scaling/
- docs/current_findings_synthesis.md — key findings across all completed experiments
- docs/redesigned_experiment_plan.md — current experiment design: research questions, methods, metrics
- docs/results_index.md — index of result folders with commit policy
- docs/storage_energy_sufficiency_bound.md — theory note on the storage-energy sufficiency bound
- docs/experiment_archive.md — completed diagnostic experiments
- docs/hope_full_year_case_preparation.md — HOPE case export format and UC parameter mapping