refac: introduce consistent convention for linopy operations with subsets and supersets#572
Conversation
Add le(), ge(), eq() methods to LinearExpression and Variable classes, mirroring the pattern of add/sub/mul/div methods. These methods support the join parameter for flexible coordinate alignment when creating constraints.
Consolidate repetitive alignment handling in _add_constant and _apply_constant_op into a single _align_constant method. This eliminates code duplication and makes the alignment behavior (handling join parameter, fill_value, size-aware defaults) testable and maintainable in one place.
numpy_to_dataarray no longer inflates ndim beyond arr.ndim, fixing lower-dim numpy arrays as constraint RHS. Also reject higher-dim constant arrays (numpy/pandas) consistently with DataArray behavior. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@FBumann, if you have some time, could you run this branch on flexopt and check it does not change results? |
|
I'll do it this evening. |
|
@FabianHofmann I really like the effort to making the broadcasting in linopy more predictable. As far as i understand it, the convention is:
My thoughts on the current convention:The default That said, i see the following issues:
m = linopy.Model()
time = pd.RangeIndex(5, name="time")
subset_time = pd.RangeIndex(3, name="time")
factor = xr.DataArray([0, 1, 2, 3, 4, 5], dims=["time"], coords={"time": [0, 1, 2, 3, 4, 5]})
x = m.add_variables(lower=0, coords=[time], name="x")
y = m.add_variables(lower=0, coords=[subset_time], name="y")
print(y + x + factor)
>>>LinearExpression [time: 5]:
---------------------------
[0]: +1 y[0] + 1 x[0]
[1]: +1 y[1] + 1 x[1] + 1
[2]: +1 y[2] + 1 x[2] + 2
[3]: +1 x[3] + 3
[4]: +1 x[4] + 4
print(y + factor + x )
>>>LinearExpression [time: 5]:
---------------------------
[0]: +1 y[0] + 1 x[0]
[1]: +1 y[1] + 1 x[1] + 1
[2]: +1 y[2] + 1 x[2] + 2
[3]: +1 x[3]
[4]: +1 x[4]I'll try to come up with something that resolves this. |
|
@FabianHofmann After some thought i find the matter more complicated than expected. |
|
@FabianHofmann I will not be able to finalize my review on this. Ill be gone for 2 weeks. |
|
@FBumann wonderful comments. It is really good to have another person thinking about this. Have a good break and take your time afterwards. I might want to pull this in earlier but I am really happy to follow your thought on the new convention for the next major release! |
|
For what I've seen this PR is backwards compatibile, so I don't see a reason why not to merge this and think about another convention later |
…ertion from fixture refactor
… as 'no constraint' in RHS - Fill NaN with 0 (add/sub) or fill_value (mul/div) in _add_constant/_apply_constant_op - Fill NaN coefficients with 0 in Variable.to_linexpr - Restore NaN mask in to_constraint() so subset RHS still signals unconstrained positions
…sets and supersets (PyPSA#572) * refac: introduce consistent convention for linopy operations with subsets and supersets * move scalar addition to add_constant * add overwriting logic to add constant * add join parameter to control alignment in operations * Add le, ge, eq methods with join parameter for constraints Add le(), ge(), eq() methods to LinearExpression and Variable classes, mirroring the pattern of add/sub/mul/div methods. These methods support the join parameter for flexible coordinate alignment when creating constraints. * Extract constant alignment logic into _align_constant helper Consolidate repetitive alignment handling in _add_constant and _apply_constant_op into a single _align_constant method. This eliminates code duplication and makes the alignment behavior (handling join parameter, fill_value, size-aware defaults) testable and maintainable in one place. * update notebooks * update release notes * fix types * add regression test * fix numpy array dim mismatch in constraints and add RHS dim tests numpy_to_dataarray no longer inflates ndim beyond arr.ndim, fixing lower-dim numpy arrays as constraint RHS. Also reject higher-dim constant arrays (numpy/pandas) consistently with DataArray behavior. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * remove pandas reindexing warning * Fix mypy errors: type ignores for xr.align/merge, match override signature, add test type hints * remove outdated warning tests * reintroduce expansions of extra rhs dims, fix multiindex alignment * refactor test fixtures and use sign constants * add tests for pandas series subset/superset * test: add TestMissingValues for same-shape constants with NaN entries * Fix broken test imports, stray docstring char, and incorrect test assertion from fixture refactor * Fill NaN with neutral elements in expression arithmetic, preserve NaN as 'no constraint' in RHS - Fill NaN with 0 (add/sub) or fill_value (mul/div) in _add_constant/_apply_constant_op - Fill NaN coefficients with 0 in Variable.to_linexpr - Restore NaN mask in to_constraint() so subset RHS still signals unconstrained positions * Fix CI doctest collection by deferring linopy import in test/conftest.py --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Documents 5 categories of legacy issues with paired legacy/v1 tests: 1. Positional alignment (#586, #550): same-shape operands with different labels silently paired by position, producing wrong results 2. Subset constant associativity (#572): left-join drops coordinates, making (a+c)+b != a+(c+b) 3. User NaN swallowed (#620): NaN in user data silently filled with inconsistent neutral elements (0 for add/mul, 1 for div) 4. Variable vs Expression inconsistency (#569, #571): x*c and (1*x)*c previously gave different results 5. Absent slot propagation (#620): legacy can't distinguish absent variables from zero, fillna() is a no-op Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Related to #550 #571
Changes proposed in this Pull Request
Establish a consistent coordinate alignment when linopy objects interact with DataArrays that have subset or superset coordinates
TODO:
linopy.alignaddition/multiplication/comparison+subset/superset` + order + object typesChecklist
doc.doc/release_notes.rstof the upcoming release is included.