Migrating to the v1 arithmetic convention#
Note
v1 is opt-in in linopy 0.10 and legacy remains the default. linopy 1.0
removes legacy, so 0.10 is the release to migrate in. Nothing changes until
you set linopy.options["semantics"] = "v1". This guide tells you what
to change when you do.
Why v1 exists#
Legacy linopy silently guessed in three situations where a guess can quietly change a model:
a
NaNin user-supplied data was filled — with a value that differed by operator (0for+/*,1for/);a coordinate mismatch on a shared dimension was resolved by position or an inner join, which can drop terms and constraints without notice;
an absent (masked, reindexed, or shifted-in) variable was treated as a variable fixed to zero.
The v1 convention replaces every silent guess with an explicit rule: it aligns strictly by label and raises where legacy would have guessed, so a wrong model surfaces as an error at build time instead of a wrong number at solve time. The per-case bug catalogue is in issue #714.
The rollout#
The transition happens in two steps, so that no model changes behaviour without warning first:
linopy 0.10 — opt-in. v1 is available via
linopy.options["semantics"] = "v1". Legacy is the default. Under legacy, every operation whose result would change under v1 emits alinopy.LinopySemanticsWarningthat names the rule and the fix.linopy 1.0 — legacy removed. v1 is the only convention. There is no opt-out.
Migrate while on 0.10. Every difference between the two conventions is either warned about under legacy or raised under v1, so no model changes silently.
Who this affects#
Downstream library maintainers (PyPSA, PyPSA-Eur, Calliope, …) carry most of the work: opt the library into v1, fix the raises, and release so it no longer warns under legacy.
Direct linopy users do the same for their own call sites.
End users of a downstream library never touch linopy directly. A
LinopySemanticsWarningin your logs is an upstream item your maintainer will handle; you can silence it in the meantime.
How to migrate#
The legacy warning already names the rule and the fix at every site, so the recipe is short:
Surface every site. Run your existing test suite under legacy and turn the warning into an error so nothing is missed:
import warnings from linopy import LinopySemanticsWarning warnings.filterwarnings("error", category=LinopySemanticsWarning)
Fix each site using the table below (the warning text points at the same fix in context).
Opt in and run the suite with
linopy.options["semantics"] = "v1". The v1 raises catch anything the warnings missed.Release. Set the option in your library’s entry point, or simply drop it once the default flips to v1.
What changes, and how to fix it#
Every row is a legacy guess that becomes an explicit rule under v1. The
LinopySemanticsWarning you see under legacy names the same fix.
Situation |
Under v1 |
Fix |
|---|---|---|
|
Raises (linopy will not guess a fill). |
|
Shared dimension with a different label set |
Raises instead of an inner/positional join. |
|
Shared dimension with the same labels in a different order |
Raises ( |
|
A reindexing |
The join still returns the union of the coordinates; the created rows
are filled per side — the linopy operand contributes the zero
expression, the constant operand |
Pass |
An unlabeled operand (numpy array, list, polars |
Pairs with the linopy operand’s dimensions by size; an ambiguous match (a square array, or two dimensions of equal length) or no size match raises rather than guessing. |
Wrap it in a |
A masked / absent variable in arithmetic |
Absence propagates (the slot stays absent) instead of counting as |
Decide the intent: |
Conflicting auxiliary coordinates on a shared dim — including a
leftover scalar coord from positional indexing ( |
Raises instead of silently dropping one. |
|
A first-class |
Rejected; v1 uses a flat dimension with the levels as aux coords, and a per-level input must be mapped onto the flat dimension explicitly (no implicit projection). Affects PyPSA multi-investment models. |
|
A multi-key |
A flat |
Select on the key aux coords; convert an existing result with
|
A |
Raises — the grouper aligns to the grouped dimension by label, never by position. |
|
The full, normative rule list lives in the arithmetic convention.
Silencing the warning#
If the warning comes from a library you do not maintain, silence it until the upstream release lands:
import warnings
import linopy
warnings.filterwarnings("ignore", category=linopy.LinopySemanticsWarning)
Do this only when the warning is upstream — for your own call sites, fix it instead, since the underlying result changes under v1.