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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions