fix(sos): order a set by its declaration, not by its coordinate labels - #893
Conversation
Merging this PR will regress 2 benchmarks
Warning Please fix the performance issues or acknowledge them on CodSpeed. Performance Changes
Tip Investigate this regression by commenting Comparing Footnotes
|
ffef6dc to
fb79d90
Compare
An SOS set's weights are its coordinate labels, so a solver orders the members by label value rather than by declaration. Where the labels ascend the two coincide — the common case, and every piecewise model. Five tests pin that overlap; seven strict xfails mark where they part. Refs PyPSA#892 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
An SOS set now runs in the order its members are declared along sos_dim, the array order linopy reads everywhere else. Ascending numeric coordinates state the same order and are still passed through as the member weights, so a model whose SOS dimension is sorted is unaffected, every piecewise formulation among them. Anything else is weighted by position, which lifts the numeric-coordinate requirement. A sos_type=2 set whose numeric coordinates do not ascend changes meaning and warns. An sos_type=1 set cannot: its feasible region never depended on the order. The reformulation loses its argsort, now always the identity, and selects the segment indicators positionally. Closes PyPSA#892 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fb79d90 to
16612ad
Compare
|
this looks good. I would include this in a bugfix release |
np.diff underflows on unsigned coords, so a non-ascending uint index looked ascending: no warning, and the LP/native weights disagreed with the reformulation order. Excludes bool from the numeric weight dtypes, renames the helpers to coords_ascend/coords_reorder_set, and tidies the release note and ordering docs.
FabianHofmann
left a comment
There was a problem hiding this comment.
I added a commit about for negative values and some smaller refactorings. this is good to go
(your intent here, handwritten)
Note
The following content was generated by AI.
Closes #892.
An SOS set now runs in the order its members are declared along
sos_dim, rather than the order its coordinate labels sort into — the array order linopy reads everywhere else (isel,shift,roll,diff).Why
Making the labels the SOS weights had two consequences:
ValueError: SOS constraint requires numeric coordinates), reachingpiecewisethe day its breakpoints are labelled[low, mid, high];What does not change
Ascending numeric coordinates state the same order and are still passed through as the member weights, so a model whose SOS dimension is sorted is unaffected down to the bytes of its LP file — every piecewise formulation among them, since
_validate_numeric_breakpoint_coordsalready requires strictly increasing breakpoints.Descending coordinates are also meaning-preserving: reversing a set maps its consecutive pairs
{i, i+1}to{n-i, n+1-i}, which are consecutive too, so SOS2's feasible set is unchanged. Only asos_type=2set whose numeric coordinates neither ascend nor descend genuinely regroups; it now warns, and sorting the index restores the previous behaviour. Ansos_type=1set cannot change meaning at all — its feasible region never depended on the order,reformulate_sos1beingx <= M * yandsum(y) <= 1— so it stays silent.Notes for review
reformulate_sos_constraintsloses itsargsort: that permutation is now always the identity, so it is deleted rather than rewritten.reformulate_sos2selects the neighbouring segment indicators withiselinstead ofsel.test_sos2_unsorted_coords_matches_sortedpasses both before and after, its best pair happening to coincide either way. That is why this was never caught, and why the new tests pick coefficients that separate the two orders.Commits
test(sos)— five tests pinning the overlap where labels and declaration agree, plus seven strictxfails marking where they part. Self-verifying at that commit.fix(sos)— the rule, with thexfailmarkers removed.