We need partial active triggers in pw formulations.
Model.add_piecewise_formulation(..., active=<var>) gates a piecewise function on a binary active variable: when active=0, the auxiliary variables (and thus the output) are pushed to zero. The gating enters through delta_var <= active and the bp0 * active intercept term in _add_incremental (and the analogous disjunctive path).
This works when active is defined over the entire coordinate of the formulation. It breaks down when active is only defined over a subset of the indexed dimension.
Problem
When the active expression is null/missing for part of the formulation's dimension, those entries get gated as if active=0 and their output is forced to zero — rather than being treated as ungated (always on).
This surfaced downstream in PyPSA (PyPSA/PyPSA#1755). PyPSA builds one piecewise formulation across a mix of committable and non-committable components and passes the unit-commitment status variable as active. The status variable only exists for committable components; for the rest it is null. The result: non-committable components — which should always dispatch — were incorrectly forced to zero by the _active piecewise constraint.
Proposed feature
Let add_piecewise_formulation accept an active expression/variable that is defined only over a subset of the formulation's dimension (i.e. null/missing elsewhere). Entries where active is missing should be treated as always active (gate = 1, ungated), and entries where it is present should be gated as today.
Effectively:
where active.isnull() → no gating
where ~active.isnull() → gated on active
Notes / open questions
- The
active semantics are already flagged as an evolving API (EvolvingAPIWarning), so refining them here fits.
- Decide whether the trigger is "NaN entries in a full-coordinate
active" vs. "active indexed over a strict subset of the dimension" — both should resolve to the same behaviour, ideally without the caller pre-aligning.
- Applies to both the incremental/SOS2 (
_add_incremental) and disjunctive (_add_disjunctive) paths; the LP path already rejects active.
We need partial active triggers in pw formulations.
Note
AI written issue
Model.add_piecewise_formulation(..., active=<var>)gates a piecewise function on a binaryactivevariable: whenactive=0, the auxiliary variables (and thus the output) are pushed to zero. The gating enters throughdelta_var <= activeand thebp0 * activeintercept term in_add_incremental(and the analogous disjunctive path).This works when
activeis defined over the entire coordinate of the formulation. It breaks down whenactiveis only defined over a subset of the indexed dimension.Problem
When the
activeexpression is null/missing for part of the formulation's dimension, those entries get gated as ifactive=0and their output is forced to zero — rather than being treated as ungated (always on).This surfaced downstream in PyPSA (PyPSA/PyPSA#1755). PyPSA builds one piecewise formulation across a mix of committable and non-committable components and passes the unit-commitment
statusvariable asactive. Thestatusvariable only exists for committable components; for the rest it is null. The result: non-committable components — which should always dispatch — were incorrectly forced to zero by the_activepiecewise constraint.Proposed feature
Let
add_piecewise_formulationaccept anactiveexpression/variable that is defined only over a subset of the formulation's dimension (i.e. null/missing elsewhere). Entries whereactiveis missing should be treated as always active (gate = 1, ungated), and entries where it is present should be gated as today.Effectively:
where active.isnull()→ no gatingwhere ~active.isnull()→ gated onactiveNotes / open questions
activesemantics are already flagged as an evolving API (EvolvingAPIWarning), so refining them here fits.active" vs. "activeindexed over a strict subset of the dimension" — both should resolve to the same behaviour, ideally without the caller pre-aligning._add_incremental) and disjunctive (_add_disjunctive) paths; the LP path already rejectsactive.