Derate thermal capacity reserve margin contribution with FOR#85
Derate thermal capacity reserve margin contribution with FOR#85clairehalloran wants to merge 16 commits into
Conversation
patrickbrown4
left a comment
There was a problem hiding this comment.
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.
| 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( |
There was a problem hiding this comment.
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.)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
Co-authored-by: Patrick Brown <25125211+patrickbrown4@users.noreply.github.com>
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.pybased on the same hourly FORs used in PRAS. The mean FOR for each technology is written to thehandoff_{t}.gdxfile for each year that resource adequacy calculations are run. The capacity of thermal generation is multiplied by(1 - FOR)ineq_reserve_margin.Additional changes
runreeds.pyto disallowGSw_PRM_UpdateMethod=0,GSw_PRM_CapCredit=1, andGSw_PRM_StressIterateMax>0, which iterates to reach an NEUE threshold without updating the PRM values between iterations.GSw_PRM_UpdateMethodfrom the default of0to1to 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=1GSw_PRM_UpdateMethod=1GSw_PRM_scenario=0.1GSw_PRM_UpdateFraction=0.05GSw_PRM_StressIterateMax=20Derating thermal capacity increased total capacity in most years:

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:

PRM without derate:

This feature resulted in a small increase in runtime:

Full USA_fast_CC comparison here: results-main,derate.pptx
No change in capacity for USA_defaults:

Full USA_defaults comparison here: results-USA_defaults_main,USA_defaults_firm-FOR-derate.pptx
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
Yes, as an alternative to looking up pandas documentation and for minor line completion.