Skip to content

v1: Variable.fillna(scalar) returns LinearExpression on v1 but raises on released 0.8.0 #848

Description

@FabianHofmann

🤖 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:

  1. Backport Variable.fillna(<scalar>) → LinearExpression to the 0.8.x line (accepting the scalar rather than raising), so the same expression runs everywhere; or
  2. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions