GenXProject / GenX.jl

GenX: a configurable power system capacity expansion model for studying low-carbon energy futures. More details at : https://genx.mit.edu
https://genxproject.github.io/GenX.jl/
GNU General Public License v2.0
256 stars 105 forks source link

Inconsistent treatment of MUST_RUN with VRE #565

Open cfe316 opened 8 months ago

cfe316 commented 8 months ago

On the develop branch, we've recently introduced validation which makes MUST_RUN a type of resource distinct from all others. However, core/reserves mentions resources which are in intersect(VRE, MUST_RUN), which I think is meant to represent non-curtailable variable renewables. As of now, this set must be empty.

I wanted to raise this issue to stimulate discussion about a resolution.

JesseJenkins commented 8 months ago

I believe the core/reserves code is incorrect and reflects older usage of the MUST_RUN set which was originally a modifier similar to UCOMMIT, the set of thermal resources subject to unit commitment, but is now implemented as a separate resource type that can reflect must-run or entirely non-dispatchable resources including variable renewables (like rooftop solar) or baseloaded thermal plants or CHP plants whose generation reflects exogenous heating demand patterns, etc.

Here is the code in question:

# Reg up and down requirements are symmetric
    @expression(EP, eRegReq[t=1:T], inputs["pReg_Req_Load"]*sum(inputs["pD"][t,z] for z=1:Z) +
        inputs["pReg_Req_VRE"]*sum(inputs["pP_Max"][y,t]*EP[:eTotalCap][y] for y in intersect(inputs["VRE"], inputs["MUST_RUN"])))
    # Operating reserve up / contingency reserve requirements as ˚a percentage of load and scheduled variable renewable energy production in each hour
    # and the largest single contingency (generator or transmission line outage)
    @expression(EP, eRsvReq[t=1:T], inputs["pRsv_Req_Load"]*sum(inputs["pD"][t,z] for z=1:Z) +
                inputs["pRsv_Req_VRE"]*sum(inputs["pP_Max"][y,t]*EP[:eTotalCap][y] for y in intersect(inputs["VRE"], inputs["MUST_RUN"])))

The intended usage here I believe is union(inputs["VRE"], inputs["MUST_RUN"]) not intersect, with the intention being to set operating reserve requirements as a percentage of scheduled dispatchable and non-dispatchable renewables, with the idea that MUST_RUN primarly is used to reflect non-dispatchable distributed solar. This requirement is to reflect operating reserves required to manage forecast errors for wind and solar production.

We have two options for a fix: (1) drop the set of MUST_RUN resources from the calculation of operating reserve and assume that only utility-scale dispatchable renewables are used in determining reserve requirements; or (2) correct the code above to use union instead of intersect and then assume that the primary usage of MUST_RUN is to represent non-dispatchable variable renewables, rather than baseloaded must-run thermal generators (which do not contribute in this way to operating reserve requirements).

I am inclined to go for Option 1 above.

cfe316 commented 8 months ago

Thanks, Jesse. Option 1 would be an easy fix.

I do think it would be nicer if we had MUST_RUN as a modifier (like LDS) to represent either non-curtailable variable renewables or baseloaded thermal plants rather than as a separate resource type. I've seen users wanting baseloaded nuclear fission plants and also behind-the-meter solar. Both can be represented as MUST_RUN. For a VRE-like resource one can modify Generators_variability.csv and set fuel to None. But, this would lose out on any special handling of VRE-like things such as for reserves here.

In practice, the utility-scale variable renewables are significantly larger than the behind-the-meter ones in the cases I've run, so it's probably not a huge deal.

cfe316 commented 8 months ago

Reading the code comment again: "a percentage of load and scheduled variable renewable energy production" indicates to me that intersect is intentional, indicating the MUST_RUN-VRE's only.

If we change the intent from this "scheduled VRE production" to "forecasted VRE production" by changing intersect(MUST_RUN, VRE) to just VRE, we're also significantly increasing the required value of eRsvReq.