reserve

Granular operating-reserve constraints (multi-product, LP relaxation).

Generalises the v1.12 / v1.14 reserve module from "one reserve_up + one reserve_down" to named reserve products, each with its own eligibility and zonal requirement -- matching the structure of real ancillary-services markets (CAISO, ERCOT, MISO, etc.) and the way GenX, ReEDS, and PowNet model reserves.

Default product set

Four products ship by default. The direction (up vs down) is encoded in the product name and inferred by the suffix:

  • regulation_up -- frequency regulation, increase output

  • regulation_down -- frequency regulation, decrease output

  • spinning -- contingency reserve, online + ready, up-only

  • non_spinning -- contingency reserve, may be offline, up-only

The full set is read from the reserve_eligible CSV (column product); any product name that ends in _down is treated as a down-direction product, everything else is up.

Constraints

Per (h, m, y, z, te) and per direction d -- headroom is shared across products in the same direction (otherwise we would double-count: a unit's spare megawatts can serve regulation OR spinning, not both at the same instant):

\[\sum_{p \in \text{products}_d} \text{reserve}_{h,m,y,z,t,p} + \text{gen}_{h,m,y,z,t} \le \text{cap}_{y,z,t} \cdot p^{\max}_{t,z,y,m,h} \cdot \Delta h \quad (d = \text{up})\]
\[\sum_{p \in \text{products}_d} \text{reserve}_{h,m,y,z,t,p} \le \text{gen}_{h,m,y,z,t} - \text{cap}_{y,z,t} \cdot p^{\min}_{t,z,y,m,h} \cdot \Delta h \quad (d = \text{down})\]

Per (h, m, y, z, p) zonal requirement:

\[\sum_{t} \text{reserve}_{h,m,y,z,t,p} \ge \text{REQ}_{z,y,p} \cdot \Delta h\]

Non-eligible (tech, product) cells are forced to zero. Products with no eligible techs in a zone produce a non-binding requirement.

Inputs

  • tech_reserve_eligible.csv -- columns tech, product, eligible. Missing rows default to eligible=0. v1.12 files (cols tech, eligible) are NOT readable by this module; migrate by:

    old: Coal,1 new: Coal,regulation_up,1

    Coal,regulation_down,1 Coal,spinning,1 Coal,non_spinning,1

  • reserve_requirement.csv -- columns zone, year, product, unit, value. Missing rows default to 0 (non-binding).

Module is gated by config.json.reserve_parameters.is_reserve; when off, no variables / constraints are built.

class prepshot._model.reserve.AddReserveConstraints(model)[source]

Bases: object

Granular operating-reserve constraints (multi-product).

Parameters

model (object) --

headroom_down_rule(h, m, y, z, te)[source]

sum_{p in down} reserve[..., p] <= gen - cap * p_min * dt.

headroom_up_rule(h, m, y, z, te)[source]

sum_{p in up} reserve[..., p] + gen <= cap * p_max * dt.

non_eligible_zero_rule(h, m, y, z, te, p)[source]

Force reserve[..., te, p] = 0 when (te, p) isn't eligible.

requirement_rule(h, m, y, z, p)[source]

sum_t reserve[..., t, p] >= REQ[z, y, p] * dt.

Requirement file reserve_requirement.csv cols zone, year, product, unit, value. Missing rows -> 0 (the constraint becomes 0 >= 0 and is skipped here for solver cleanliness).