Skip to content

Comments

refac: introduce consistent convention for linopy operations with subsets and supersets#572

Open
FabianHofmann wants to merge 14 commits intomasterfrom
harmonize-linopy-operations
Open

refac: introduce consistent convention for linopy operations with subsets and supersets#572
FabianHofmann wants to merge 14 commits intomasterfrom
harmonize-linopy-operations

Conversation

@FabianHofmann
Copy link
Collaborator

@FabianHofmann FabianHofmann commented Feb 9, 2026

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:

  • extend test suit to linopy + linopy objects
  • manifest convention in the docs
  • clarify relation to linopy.align
  • really ensure convention holds true for all combinations of addition/multiplication/comparison + subset/superset` + order + object types
  • come up with a convention for equally shaped objects Fix coordinate misalignment in expression merge #550

Checklist

  • Code changes are sufficiently documented; i.e. new functions contain docstrings and further explanations may be given in doc.
  • Unit tests for new features were added (if applicable).
  • A note for the release notes doc/release_notes.rst of the upcoming release is included.
  • I consent to the release of this PR's code under the MIT license.

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.
@FabianHofmann FabianHofmann marked this pull request as ready for review February 17, 2026 20:51
FabianHofmann and others added 5 commits February 18, 2026 09:53
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>
@FabianHofmann
Copy link
Collaborator Author

@FBumann, if you have some time, could you run this branch on flexopt and check it does not change results?

@FBumann
Copy link
Collaborator

FBumann commented Feb 19, 2026

I'll do it this evening.

@FBumann
Copy link
Collaborator

FBumann commented Feb 19, 2026

@FabianHofmann I really like the effort to making the broadcasting in linopy more predictable.
I think is is great!

As far as i understand it, the convention is:

  • Same shape → "override" (fast positional alignment). More performant.
  • Different shape → "outer" for expression+expression, "left" for expression+constant

My thoughts on the current convention:

The default join=None unfortunately does some magic thats hard to grasp in my opinion.
I assume that this is partly to keep the current behaviour (as i found for flixopt --> all tests pass!)
So i fully understand if we rather go your proposed route.

That said, i see the following issues:

  1. think it would be great if we relied less on the heuristic of same shape --> align positionally. Id rather do an explicit a.add(b, join="override"). This also resolves the confusion with what are the final coords of the expression for me.
  2. left join for expression+constant means that expression + constant + expression might have different results than expression + expression + constant, which is unexpected.
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.

@FBumann
Copy link
Collaborator

FBumann commented Feb 20, 2026

@FabianHofmann After some thought i find the matter more complicated than expected.
xarray themselves use 'inner' for arithmetic operations. So we should probably go for that.
This fixes the ambiguity of arithmetics (x+a) + y == x + (y + a)
I need to think about the other implications

@FBumann
Copy link
Collaborator

FBumann commented Feb 20, 2026

@FabianHofmann I will not be able to finalize my review on this. Ill be gone for 2 weeks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants