Skip to content

v1: documented absent-slot fix .fillna(value) still warns under legacy (no cross-semantics form) #847

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 the PR #717 branch.

Summary

Under legacy semantics, the documented resolution for absent slots — .fillna(value) — still emits LinopySemanticsWarning. As a result there is no single expression that is both v1-correct and legacy-warning-free for the shift()/where() absent-slot pattern, which forces downstream code to branch on linopy.options["semantics"].

The warning text itself tells users to resolve absence with .fillna(value):

if you meant absent at this slot, mark it on the variable instead (mask=, .where(cond), .reindex(...), .shift(...)) … resolve with .fillna(value)

but applying exactly that resolution does not clear the warning under legacy.

Reproduction

linopy 0.8.0.post1.dev123+g5a131a628 (branch feat/arithmetic-convention), Python 3.12:

import warnings, pandas as pd
import linopy as ln
from linopy.config import LinopySemanticsWarning

m = ln.Model()
names = pd.Index(["a", "b", "c"], name="name")
sns   = pd.Index([0, 1, 2], name="snapshot")
x = m.add_variables(coords=[sns, names], name="x")

for sem in ["legacy", "v1"]:
    ln.options["semantics"] = sem
    with warnings.catch_warnings(record=True) as w:
        warnings.simplefilter("always")
        x - x.shift(snapshot=1).fillna(0)   # the documented absent-slot resolution
        warned = any(issubclass(a.category, LinopySemanticsWarning) for a in w)
        print(sem, "->", "WARN" if warned else "ok")
legacy -> WARN
v1     -> ok

Both branches produce the intended numeric result (0 at the first snapshot); only legacy warns.

Why it matters

Downstream projects with strict warning filters (e.g. PyPSA runs pytest with error::FutureWarning, and LinopySemanticsWarning subclasses FutureWarning) turn this into a hard error. Because the prescribed fix doesn't silence it, the only way to be green on both semantics is to gate the expression on the active semantics — exactly the kind of version-branching the migration is meant to avoid. This came up repeatedly in PyPSA's unit-commitment (status - status.shift(1)) and storage SOC (previous_e / previous_soc) constraints.

Suggestion

Calling the documented resolution (.fillna(value) on an object that has absent slots) should clear the legacy LinopySemanticsWarning for that operand — i.e. once absence has been explicitly resolved, the subsequent arithmetic is unambiguous under both semantics and should not warn. That would make the shift/where + fillna pattern a single cross-compatible form.

Related: the return-type half of this (Variable.fillna(scalar) differing across releases) is filed separately.

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