Skip to content

Document active-power limit constraints for thermals and loads #73

@luke-kiernan

Description

@luke-kiernan

Context

The active-power upper/lower-bound constraint machinery across thermal and load device families uses a mix of constraint types, meta strings, and row-counting conventions that isn't documented anywhere. This issue captures the current state on paper, as a prerequisite to deciding what's intentional design vs. accumulated technical debt.

All keys below are ConstraintKey(ConstraintType, ComponentType, meta).

Loads

InterruptiblePowerLoad with PowerLoadInterruption + forecast attached:

Key RHS Source
(ActivePowerVariableTimeSeriesLimitsConstraint, InterruptiblePowerLoad, "") p ≤ forecast_t · pmax electric_loads.jl:118-140add_parameterized_upper_bound_range_constraints
(ActivePowerVariableLimitsConstraint, InterruptiblePowerLoad, "binary") p ≤ u · pmax electric_loads.jl:142-169

Plus a JuMP variable lower bound p ≥ 0 from get_variable_lower_bound (electric_loads.jl:9). There is no ActivePowerVariableLimitsConstraint row for the nameplate — the nameplate UB is enforced via the JuMP variable's upper bound, not a constraint.

PowerLoadDispatch omits the "binary" row (no OnVariable) and otherwise looks the same.

Thermals

AbstractThermalDispatchFormulation (no commitment, no forecast):

Key RHS Source
(ActivePowerVariableLimitsConstraint, ThermalGen, "lb"/"ub") pmin ≤ p ≤ pmax via add_range_constraints! thermal_generation.jl:294-310

AbstractThermalUnitCommitment (commitment, no forecast):

Key RHS Source
(ActivePowerVariableLimitsConstraint, ThermalGen, "lb"/"ub") pmin · u ≤ p ≤ pmax · u via add_semicontinuous_range_constraints! thermal_generation.jl:377-391

AbstractThermalUnitCommitment + forecast attached — adds on top of commitment:

Key RHS Source
(ActivePowerVariableTimeSeriesLimitsConstraint, ThermalGen, "") p ≤ forecast_t · pmax thermal_generation.jl:462-484

ThermalMultiStartUnitCommitment — multiple metas under the same constraint type:

Key RHS Source
(ActivePowerVariableLimitsConstraint, ThermalMultiStart, "on") p ≤ (pmax−pmin)·u − max(pmax−startup, 0)·v thermal_generation.jl:489-567
(ActivePowerVariableLimitsConstraint, ThermalMultiStart, "off") startup/shutdown trajectory UB same method
(ActivePowerVariableLimitsConstraint, ThermalMultiStart, "lb") p ≥ 0 same method
(ActivePowerVariableLimitsConstraint, ThermalMultiStart, "ubon") via ActivePowerRangeExpressionUB thermal_generation.jl:614-681
(ActivePowerVariableLimitsConstraint, ThermalMultiStart, "uboff") same same
(ActivePowerVariableLimitsConstraint, ThermalMultiStart, "lb") (expression) expression ≥ 0 via ActivePowerRangeExpressionLB thermal_generation.jl:569-612

Observed inconsistencies

Descriptive, not prescriptive — flagging for discussion, not proposing fixes:

  1. Commitment coupling lives under different keys across device families.

    • Thermals: (ActivePowerVariableLimitsConstraint, …, "lb"/"ub") for both dispatch and UC, plus "on"/"off"/"ubon"/"uboff" for multi-start.
    • Loads: (ActivePowerVariableLimitsConstraint, …, "binary") — a meta string that exists nowhere on the thermal side.
  2. Forecast + commitment are never folded into a single row. Thermal UC with forecast produces two separate rows (p ≤ pmax·u and p ≤ forecast_t · pmax) under different constraint types. Loads do the same. ThermalMultiStartUnitCommitment does fold commitment and startup/shutdown trajectories into a single row — but not forecast.

  3. The meta-string vocabulary is ad-hoc. Thermals use "lb", "ub", "on", "off", "ubon", "uboff". Loads use "binary". Nothing enforces or documents the vocabulary; no registry of valid metas per constraint type.

  4. Lower bounds are enforced three different ways.

    • JuMP variable bound (loads; thermal dispatch via add_range_constraints!)
    • Explicit constraint row with meta "lb" (thermal multi-start)
    • Expression-based ≥ 0 under the same "lb" meta (thermal multi-start with ActivePowerRangeExpressionLB)

    The ThermalMultiStart case reuses the "lb" meta for two semantically different rows (one on the variable, one on an expression), which would collide if both code paths ran for the same device.

  5. Nameplate UB materialization is asymmetric. Thermals always create an ActivePowerVariableLimitsConstraint row. Loads push the nameplate into the JuMP variable's upper bound and only materialize a constraint row when commitment (PowerLoadInterruption) or a forecast is involved.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions