refactor(piecewise): per-tuple sign + categorized internal flow#664
Merged
FBumann merged 4 commits intofeat/piecewise-api-refactorfrom Apr 26, 2026
Merged
Conversation
Public API
- Drop the formulation-level `sign=` keyword on `add_piecewise_formulation`.
Pass the sign per-tuple as an optional 3rd element instead:
`(y, y_pts, "<=")` instead of `sign="<="`.
- Tuples without a sign default to "=="; the bounded tuple need not be first.
- Validate: at most one tuple may carry a non-equality sign; with 3 or more
tuples all signs must be "==" (the multi-input bounded case is reserved
for a future bivariate / triangulated piecewise API).
- Old `sign=` callers get a clear `TypeError` pointing to the new shape.
Internal flow
- Introduce `_PwlInputs` to carry the categorized inputs (`bounded_*` vs
`pinned_*`) through the dispatch chain. `_build_links`, `_try_lp`,
`_lp_eligibility`, `_add_continuous`, `_add_disjunctive` all consume it
directly — no more positional "first tuple is special" convention.
- User's tuple order is preserved end-to-end.
Tests
- Migrate ~30 callers to per-tuple sign.
- Drop tests of the now-rejected N>=3 + non-equality case
(`TestNVariableInequality`, the two CHP `TestHandComputedAnchors` cases,
`test_nvar_inequality_bounds_first_tuple`).
- Add tests for: removed-`sign=`-keyword migration error, multiple bounded
tuples rejected, N>=3 + non-equality rejected, bounded tuple in the
second position still routes to LP.
Docs
- Rewrite the "sign parameter" section of doc/piecewise-linear-constraints.rst
for per-tuple sign. Update the comparison table, examples, and the
release notes entry.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
for more information, see https://pre-commit.ci
…s invitations Drops vestigial framing from the old API throughout the user docs and example notebooks. The "first-tuple convention" and "N−1 jointly pinned" scaffolding existed only to explain why position 0 was special — with per-tuple sign that explanation isn't needed. Each tuple's role is now visible at the call site. Restrictions (one bounded tuple max; 3+ must be all-equality) are reframed as invitations: "open an issue at https://github.com/PyPSA/linopy/issues if you have a use case." We don't actually know what shape future support takes — better to invite scoping than to commit to a specific "future bivariate / triangulated piecewise API" we haven't designed. - doc/piecewise-linear-constraints.rst: rewrite the restrictions block, the N-variable linking section, and the SOS2 generated-names list to use the new framing. Update See Also link target. - examples/piecewise-inequality-bounds.ipynb: rewrite intro, math, code, and summary cells. Drop the four cells (10–13) that were dedicated to the now-rejected 3-variable inequality case (the 3D ribbon plot and its "first-tuple convention" justification). Notebook executes end-to-end on Gurobi. - examples/piecewise-linear-constraints.ipynb: drop the 3-variable CHP inequality demo (cells 12–13); the joint-equality CHP case is already in section 6. Update the inequality intro for per-tuple sign. - linopy/piecewise.py: rephrase docstring restrictions and the entry-point ValueError to invite an issue rather than promise a specific future API. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
for more information, see https://pre-commit.ci
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.
Refactor of the unreleased
add_piecewise_formulationAPI onfeat/piecewise-api-refactor. Targets the in-progress branch, not master; no external users yet.What changes
sign=keyword onto the tuple it applies to:(y, y_pts, \"<=\")replacessign=\"<=\". Each tuple carries an optional 3rd element (default\"==\"). The bounded tuple no longer has a special position — user tuple order is preserved end-to-end.\"==\". The N-input bounded case is rejected with an error message that invites a GitHub issue rather than promising a specific future API shape._PwlInputsdataclass carries categorized inputs (bounded_*vspinned_*) through the dispatch chain (_build_links,_try_lp,_lp_eligibility,_add_continuous,_add_disjunctive). The "first tuple is special" positional convention is gone — every consumer reads the role explicitly.Before / after
The old
sign=keyword now raises aTypeErrorwith a one-line pointer to the new shape — convenient while updating the in-repo callers, not a migration aid for external users.Future work this enables
The tuple shape is now the right primitive across three distinct piecewise regimes — same
(expr, breaks, [sign])call, no public-API redesign needed for any of them:(y, y_pts), (x, x_pts)± sign(power, p_pts), (fuel, f_pts), (heat, h_pts)z ≥ f(x, y)(z, z_pts, \">=\"), (x, x_pts), (y, y_pts), triangulation=tristriangulation=kwarg + new buildersFor the 2-D case specifically: vertex coordinates stay as plain 1-D
(_vertex,)DataArrays on each tuple — they survive slicing, broadcast, and use the existing coercion/masking machinery. The simplex adjacency rides along as one shared sibling kwarg, not embedded in the breakpoint slices (which would force fragile attrs or a non-DataArraywrapper class). Internally,_PwlInputswould grow a single optional fieldtriangulation: DataArray | None; the dispatcher branches on it; the formulation builders for the triangulated path are new but consume the same input carrier.The N≥3-inequality slot we reserved at the parser is what makes this fit naturally —
(z, z_pts, \">=\"), (x, x_pts), (y, y_pts)reads asz ≥ f(x, y)without contortion, and the N≥3 + non-equality validation can loosen to allow it specifically whentriangulationis set.Other follow-ups this also unlocks
add_piecewise(y, x, x_pts, y_pts, sign=\"<=\")) — routes cleanly because role-categorization is explicit.EvolvingAPIWarning) — the shape we'd commit to is now structurally honest, with no positional convention to teach or maintain in docs.Test plan
pytest test/test_piecewise_constraints.py test/test_piecewise_feasibility.py— 518 passedpytest test/ --ignore=test/remote --ignore=test/test_optimization.py --ignore=test/test_solvers.py— 1578 passedruff check+ruff format --check— cleanmypy linopy/piecewise.py— clean🤖 Generated with Claude Code