heat_rate

Piecewise-linear heat rates (convex, no UC required).

Standard CEM/PCM addition for thermal generators: their fuel-burn- per-MWh isn't constant across the operating range. Above the design point, marginal heat rate increases (each additional MWh costs more fuel than the previous one). LP-friendly approximation: split the operating range into K segments with monotone-non-decreasing multipliers, force the dispatch to use the cheapest segment first.

The convexity matters: with non-decreasing multipliers across ascending output ranges, the LP optimum naturally fills segments in order (cheapest first) without any binary ordering constraints. Real heat-rate curves below the design point (where part-load losses dominate) are concave -- those can only be modelled with no- load + startup costs in the UC overlay (v1.15+).

Inputs

tech_heat_rate.csv (table format) -- one row per (tech, segment), with columns:

  • tech -- technology name

  • segment -- integer index (1, 2, 3, ...)

  • frac_max -- cumulative cap fraction at the upper edge of this segment (e.g., 0.5 for segment 1 means "0-50 % of cap"). Segments must form a partition: frac_max strictly increasing, last value = 1.0.

  • multiplier -- relative heat rate within this segment, 1.0 == baseline (whatever tech_fuel_price already implies). The multiplier sequence MUST be non-decreasing for LP convexity.

Techs not listed in the CSV keep the v1.18 single-rate fuel cost (fuel_price * gen). Mixing piecewise and flat-rate techs is fine.

Constraints

Per (h, m, y, z, te) in the heat-rate set:

  • sum-to-gen: sum_s gen_segment[h, m, y, z, te, s] == gen[h, m, y, z, te]

  • per-segment width: gen_segment[..., s] <= width[s] * cap_existing[y, z, te] * dt

where width[s] = frac_max[s] - frac_max[s-1].

Fuel cost replaces the flat fuel_price * gen with:

fuel_price[te, y] * sum_s multiplier[s] * gen_segment[..., s]

NPV-discounted via var_factor[y, z] and divided by weight, identical to the rest of cost_var.

Config

  • cost_parameters.is_piecewise_heat_rate (bool, default False). When False, the module is a no-op and PREP-SHOT keeps the v1.18 flat-rate behaviour byte-for-byte.

class prepshot._model.heat_rate.AddHeatRateConstraints(model)[source]

Bases: object

Piecewise-linear heat rate, opt-in.

Parameters

model (object) --

segment_width_rule(h, m, y, z, te, s)[source]

gen_segment[s] <= (width[s] * cap_existing) * dt.

segment_zero_rule(h, m, y, z, te, s)[source]

Force gen_segment[..., s] = 0 for (te, s) not defined.

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

sum_s gen_segment[..., s] == gen[h, m, y, z, te].

prepshot._model.heat_rate.add_heat_rate_fuel_cost(model)[source]

Per-segment fuel cost contribution, NPV-discounted.

For each tech with a heat-rate curve, the fuel cost becomes:

fuel_price[te, y] * sum_s multiplier[s] * gen_segment[..., s]

summed over (h, m, y, z), discounted by var_factor[y, z], and divided by weight. Returned as an ExprBuilder. Returns a zero ExprBuilder when the module is disabled or no heat-rate curves are loaded.

The CALLER (cost.var_cost_rule) must remove the corresponding techs' contribution from the existing flat-rate fuel_cost_breakdown to avoid double-counting -- handled by skipping those techs in the original fuel_cost_breakdown loop when hasattr(model, 'gen_segment') and the tech is in segments_by_tech.

Return type

ExprBuilder

prepshot._model.heat_rate.techs_with_heat_rate_curve(model)[source]

Return the set of techs that have a heat-rate curve loaded.

Used by cost.fuel_cost_breakdown to skip techs that should be priced through add_heat_rate_fuel_cost instead of the flat-rate fallback.

Return type

set