🤖 This issue was written by Claude (Claude Code), based on hands-on findings while migrating PyPSA to the v1 arithmetic semantics. Reproductions were verified against both released 0.8.0 and the PR #717 branch.
Summary
Variable.fillna(<scalar>) has different behavior across linopy releases, with no single form that works on both:
- On released
0.8.0: variable.fillna(0) raises ValueError: other must be a Variable, ScalarVariable, dict or Dataset, got <class 'int'>.
- On the v1 branch (
feat/arithmetic-convention): variable.fillna(0) returns a LinearExpression (a scalar fill no longer preserves variable identity).
This makes it impossible to write the recommended v1-clean absent-slot resolution in downstream code that must also run on the currently released linopy (>=0.8.0).
Reproduction
Released 0.8.0:
import linopy as ln, pandas as pd
m = ln.Model()
x = m.add_variables(coords=[pd.Index([0,1,2], name="snapshot"),
pd.Index(["a","b"], name="name")], name="x")
x.shift(snapshot=1).fillna(0)
# ValueError: other must be a Variable, ScalarVariable, dict or Dataset, got <class 'int'>
v1 branch (0.8.0.post1.dev123+g5a131a628): the same call returns a LinearExpression.
Why it matters
While migrating PyPSA I rewrote a shifted-variable expression to status.shift(1).fillna(0) (the v1-recommended resolution). It worked on the v1 branch but broke 26 tests on released 0.8.0 with the ValueError above, because Variable.fillna(scalar) isn't supported there. The DataArray-level .fillna(0) is fine on both; only the Variable/absent-slot form diverges.
Origin note: Variable.fillna itself was added in #144; the v1 change is the new scalar → LinearExpression return path on top of it.
Suggestion
Provide a form that is valid and identical on both released 0.8.x and v1, one of:
- Backport
Variable.fillna(<scalar>) → LinearExpression to the 0.8.x line (accepting the scalar rather than raising), so the same expression runs everywhere; or
- add a small free helper, e.g.
linopy.fillna(obj, value) (analogous to the existing linopy.align(...)), that dispatches correctly on both semantics/releases.
Either lets downstream authors write the absent-slot resolution once during the transition window instead of version-gating it.
Summary
Variable.fillna(<scalar>)has different behavior across linopy releases, with no single form that works on both:0.8.0:variable.fillna(0)raisesValueError: other must be a Variable, ScalarVariable, dict or Dataset, got <class 'int'>.feat/arithmetic-convention):variable.fillna(0)returns aLinearExpression(a scalar fill no longer preserves variable identity).This makes it impossible to write the recommended v1-clean absent-slot resolution in downstream code that must also run on the currently released linopy (
>=0.8.0).Reproduction
Released
0.8.0:v1 branch (
0.8.0.post1.dev123+g5a131a628): the same call returns aLinearExpression.Why it matters
While migrating PyPSA I rewrote a shifted-variable expression to
status.shift(1).fillna(0)(the v1-recommended resolution). It worked on the v1 branch but broke 26 tests on released 0.8.0 with theValueErrorabove, becauseVariable.fillna(scalar)isn't supported there. The DataArray-level.fillna(0)is fine on both; only theVariable/absent-slot form diverges.Origin note:
Variable.fillnaitself was added in #144; the v1 change is the newscalar → LinearExpressionreturn path on top of it.Suggestion
Provide a form that is valid and identical on both released 0.8.x and v1, one of:
Variable.fillna(<scalar>) → LinearExpressionto the 0.8.x line (accepting the scalar rather than raising), so the same expression runs everywhere; orlinopy.fillna(obj, value)(analogous to the existinglinopy.align(...)), that dispatches correctly on both semantics/releases.Either lets downstream authors write the absent-slot resolution once during the transition window instead of version-gating it.