feat: add active parameter to piecewise linear constraints#604
Merged
FabianHofmann merged 9 commits intopiecewise-followupfrom Mar 9, 2026
Merged
feat: add active parameter to piecewise linear constraints#604FabianHofmann merged 9 commits intopiecewise-followupfrom
FabianHofmann merged 9 commits intopiecewise-followupfrom
Conversation
Add an `active` parameter to the `piecewise()` function that accepts a binary variable to gate piecewise linear functions on/off. This enables unit commitment formulations where a commitment binary controls the operating range. The parameter modifies each formulation method as follows: - Incremental: δ_i ≤ active (tightened bounds) + base terms × active - SOS2: Σλ_i = active (instead of 1) - Disjunctive: Σz_k = active (instead of 1) When active=0, all auxiliary variables are forced to zero, collapsing x and y to zero. When active=1, the normal PWL domain is active. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Collaborator
Author
|
Addition to #602 |
Clarify that zero-forcing is the only linear formulation possible — relaxing the constraint would require big-M or indicator constraints. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Example 6 demonstrates the active parameter with a gas unit that stays off at t=1 (low demand) and commits at t=2,3 (high demand), showing power=0 and fuel=0 when the commitment binary is off. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add tests for gaps identified in review: - Inequality + active (incremental and SOS2, on and off) - auto method selection + active (equality and auto-LP rejection) - active with LinearExpression (not just Variable) - active with NaN-masked breakpoints - LP file output comparison (active vs plain) - Multi-dimensional solver test (per-entity on/off) - SOS2 non-zero base + active off - SOS2 inequality + active off - Disjunctive active on (solver) - Fix: reject active when auto resolves to LP 159 tests pass (was 122). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move the active bound constraint name suffix to constants.py, consistent with all other PWL suffix constants. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Collaborator
FabianHofmann
left a comment
There was a problem hiding this comment.
nice one, this is a great extension. found one issue. active is rejected for method="lp" explicitly, but it is still silently ignored when method="auto" resolves to the LP formulation for inequalities.
Concretely, something like
m.add_piecewise_constraints(
piecewise(x, [0, 50, 100], [0, 40, 60], active=u) >= y,
method="auto",
)currently succeeds, but u is not used in the generated formulation at all.
Since auto is the default, this can lead to a model that appears gated but actually isn’t.
We probably want to raise here
Collaborator
|
mind if I quickly push this? |
Keep only tests that exercise unique code paths or verify distinct mathematical properties. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
FabianHofmann
added a commit
that referenced
this pull request
Mar 9, 2026
* Refactor piecewise constraints: add piecewise/segments/slopes_to_points API, LP formulation for convex/concave cases, and simplify tests * piecewise: replace bp_dim/seg_dim params with constants, remove dead code, improve errors * Fix piecewise linear constraints: add binary indicators to incremental formulation, add domain bounds to LP formulation - Incremental method now uses binary indicator variables with link/order constraints to enforce proper segment filling order (Markowitz & Manne) - LP method now adds x ∈ [min(xᵢ), max(xᵢ)] domain bound constraints to prevent extrapolation beyond breakpoints * update signatures of breakpoints and segments, apply convexity check only where needed * update doc * Reject interior NaN and skip_nan_check+NaN in piecewise formulations Validate trailing-NaN-only for SOS2 and disjunctive methods to prevent corrupted adjacency. Fail fast when skip_nan_check=True but breakpoints actually contain NaN. * Allow piecewise() on either side of comparison operators Support reversed syntax (y == piecewise(...)) via __le__/__ge__/__eq__ dispatch in BaseExpression and ScalarLinearExpression. Fix LP example to use power == demand for more illustrative results. * Fix mypy type errors for piecewise constraint types - Add @overload to comparison operators (__le__, __ge__, __eq__) in BaseExpression and Variable to distinguish PiecewiseExpression from SideLike return types - Update ConstraintLike type alias to include PiecewiseConstraintDescriptor - Fix PiecewiseConstraintDescriptor.lhs type from object to LinExprLike - Fix dict/sequence type mismatches in _dict_to_array, _dict_segments_to_array, _segments_list_to_array - Remove unused type: ignore comments - Narrow ScalarLinearExpression/ScalarVariable return types to not include PiecewiseConstraintDescriptor (impossible at runtime) * rename header of jupyter notebook * doc: rename notebook again * feat: add active parameter to piecewise linear constraints (#604) * feat: add `active` parameter to piecewise linear constraints Add an `active` parameter to the `piecewise()` function that accepts a binary variable to gate piecewise linear functions on/off. This enables unit commitment formulations where a commitment binary controls the operating range. The parameter modifies each formulation method as follows: - Incremental: δ_i ≤ active (tightened bounds) + base terms × active - SOS2: Σλ_i = active (instead of 1) - Disjunctive: Σz_k = active (instead of 1) When active=0, all auxiliary variables are forced to zero, collapsing x and y to zero. When active=1, the normal PWL domain is active. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: tighten active parameter docstrings Clarify that zero-forcing is the only linear formulation possible — relaxing the constraint would require big-M or indicator constraints. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: add active parameter to release notes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: resolve mypy type errors for x_base/y_base assignment Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: add unit commitment example to piecewise notebook Example 6 demonstrates the active parameter with a gas unit that stays off at t=1 (low demand) and commits at t=2,3 (high demand), showing power=0 and fuel=0 when the commitment binary is off. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Update notebook * test: comprehensive active parameter test coverage Add tests for gaps identified in review: - Inequality + active (incremental and SOS2, on and off) - auto method selection + active (equality and auto-LP rejection) - active with LinearExpression (not just Variable) - active with NaN-masked breakpoints - LP file output comparison (active vs plain) - Multi-dimensional solver test (per-entity on/off) - SOS2 non-zero base + active off - SOS2 inequality + active off - Disjunctive active on (solver) - Fix: reject active when auto resolves to LP 159 tests pass (was 122). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: extract PWL_ACTIVE_BOUND_SUFFIX constant Move the active bound constraint name suffix to constants.py, consistent with all other PWL suffix constants. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: remove redundant active parameter tests Keep only tests that exercise unique code paths or verify distinct mathematical properties. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: FBumann <117816358+FBumann@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Changes proposed in this Pull Request
Add an
activeparameter topiecewise()that accepts a binary variable to gate piecewise linear functions on/off. This enables unit commitment formulations where a commitment binary controls the operating range of a unit.active=0, all auxiliary variables are forced to zero (x and y collapse to 0)active=1, the normal PWL domain[x₀, xₙ]is activeMotivation
In energy dispatch optimization, units have a binary on/off status and a variable efficiency curve (heat rate). The piecewise linear approximation of this curve needs to be gated by the commitment binary: when the unit is off, both power output and fuel consumption must be zero — not just "unconstrained".
This zero-forcing behavior is the only option that can be expressed with pure linear constraints. The alternative — selectively relaxing the PWL constraint (letting x and y float freely when off) — would require big-M formulations or solver-native indicator constraints, both of which are outside the scope of this feature.
How it works per method
δ_i ≤ active+ base terms× activeΣλ_i = active(instead of= 1)Σz_k = active(instead of= 1)ValueError)Why LP is excluded
The LP tangent-line formulation has no auxiliary variables — it directly constrains the x/y relationship via slope inequalities. This is its strength without
active, but makes clean gating impossible:active=0x=0andy=0Why
activelives onpiecewise()notadd_piecewise_constraints()The active binary is a property of the piecewise function itself (the mapping is gated), not of the constraint method used to implement it. It belongs with the breakpoints.
Naming
activewas chosen over alternatives:indicator— suggests constraint relaxation (Option B), not zero-forcingbinary— describes the type, not the rolecommitment/on— too domain-specific for a general libraryOpen to discussion.
Usage
Checklist
doc.doc/release_notes.rstof the upcoming release is included.Test plan
active=None(default) produces identical formulation to beforeactivewithmethod='lp'raisesValueErroractivework correctly🤖 Generated with Claude Code