Skip to content

Derate thermal capacity reserve margin contribution with FOR#85

Open
clairehalloran wants to merge 16 commits into
mainfrom
ch/firm-derate
Open

Derate thermal capacity reserve margin contribution with FOR#85
clairehalloran wants to merge 16 commits into
mainfrom
ch/firm-derate

Conversation

@clairehalloran
Copy link
Copy Markdown
Collaborator

@clairehalloran clairehalloran commented May 8, 2026

Summary

This PR derates the contribution of thermal capacity to the reserve margin based on technology-specific mean forced outage rates (FORs) during the top net peak load hours during each ccseason. This change only applies when the capacity credit resource adequacy method is used, i.e. GSw_PRM_CapCredit=1. This update does not change capacity credit for VRE, hydro, or storage.

Technical details

Implementation notes

For each thermal technology, the mean FOR during the top net peak load hours is calculated for each ccseason in resource_adequacy/capacity_credit.py based on the same hourly FORs used in PRAS. The mean FOR for each technology is written to the handoff_{t}.gdx file for each year that resource adequacy calculations are run. The capacity of thermal generation is multiplied by (1 - FOR) in eq_reserve_margin.

Additional changes

  • Added ValueError in runreeds.py to disallow GSw_PRM_UpdateMethod=0,GSw_PRM_CapCredit=1, and GSw_PRM_StressIterateMax>0, which iterates to reach an NEUE threshold without updating the PRM values between iterations.
  • In the Pacific_CC test case, updated GSw_PRM_UpdateMethod from the default of 0 to 1 to avoid raising this ValueError and test endogenously increasing the PRM.

Validation, testing, and comparison report(s)

I ran the USA_fast test case with the following switches:

  • GSw_PRM_CapCredit=1
  • GSw_PRM_UpdateMethod=1
  • GSw_PRM_scenario=0.1
  • GSw_PRM_UpdateFraction=0.05
  • GSw_PRM_StressIterateMax=20

Derating thermal capacity increased total capacity in most years:
image

In 2050, there is a net increase in generation capacity with combined cycle and steam turbine prime movers (gas-CC, H2-CC, coal, and coal-CCS), which have lower outage rates than combustion turbines at low temperatures.

Derating also led to lower PRM values:

PRM with derate:
image

PRM without derate:
image

This feature resulted in a small increase in runtime:
image

Full USA_fast_CC comparison here: results-main,derate.pptx

No change in capacity for USA_defaults:
image

Full USA_defaults comparison here: results-USA_defaults_main,USA_defaults_firm-FOR-derate.pptx

Checklist for author

Details to double-check

  • Charge code provided to reviewers
  • 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 (runbatch.py, d_solve_iterate.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

Yes, as an alternative to looking up pandas documentation and for minor line completion.

@clairehalloran clairehalloran added enhancement New feature or request and removed docs labels May 8, 2026
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 a nice addition, thanks!

My main comment is that I think the capacity_credit.py logic could be sped up by avoiding the reshaping of the hourly outage-rate dataframe. I added some snippets to get started but happy to chat if easier.

Comment thread reeds/resource_adequacy/capacity_credit.py Outdated
Comment thread reeds/resource_adequacy/capacity_credit.py Outdated
Comment thread reeds/resource_adequacy/capacity_credit.py Outdated
top_net_load_hours['h'] = top_net_load_hours['actual_h']

# join top hours to outage rates based on timestamp and region
forced_outage_rate_top_hours = top_net_load_hours.merge(
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.

Could you explain the reasoning for using net load instead of load (since net load is a VRE concept but here we're looking at non-VRE)?

For context, the RHS of eq_reserve_margin is peak rather than net peak. (Of course, that's one of the weird things about capacity credit, that we use peak for some things and net peak for others.)

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.

I initially used net load to be consistent with the VRE and storage capacity credit. Revisiting it, I think thermal capacity credit would be more closely aligned with ELCC if we use load. I'll plan to update.

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.

Given that we are using net load as a proxy for the highest risk hours under the capacity credit method, doesn't it make sense to stick with net load here? Happy to talk through this if that would be helpful.

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.

It's not obvious to me which is better.

  • Using net load for everything is consistent in the sense that all the capacity credits use the same profile
  • Subtracting the hourly availability of existing fossil from load is consistent in the sense that that's what we do for VRE (and with hourly outage rates, fossil is also variable)
  • It seems hard to call net load, defined as load - (wind+solar), tech-neutral

The stress periods approach seems the most tech-neutral to me, while I think the point of continuing to support the capacity credit method is to be able to reproduce what some regions are currently doing in practice. If that's the goal, I think the most helpful thing would be to review how regions that currently use tech-dependent capacity credits / ELCCs do it, and try to reproduce that as closely as possible.

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.

Based on a quick review, it seems like using net load as a proxy for highest risk hours is most aligned with current practice in the largest markets:

  • MISO is moving towards accrediting all resource classes based on their UCAP during high-risk periods (see "Transition" on p 28 and "Conclusion" on p 29 here)
  • PJM accredits generation "based on expected contribution during periods of system reliability risk in each season" and uses temperature-dependent FOR for thermal resources (see p 21 here)
  • Based on a 1.5 year old PJM review, it looks like ISONE and NYISO derate thermal generation based on historical seasonal forced outage rates

Comment thread reeds/resource_adequacy/ra_calcs.py Outdated
Comment thread cases_test.csv
Comment thread runreeds.py Outdated
Comment thread docs/source/model_documentation.md Outdated
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