Repository navigation
spec: rows emptied by absence are dropped silently; revisit strict refusal #993
Description
Activity
- addedmath-specYAML/text math spec: schema, from_spec/add_spec, operators, expr containerYAML/text math spec: schema, from_spec/add_spec, operators, expr container
on Sep 28, 2026 - added a commit that references this issue
on Sep 28, 2026 Note
The following content was generated by AI.
Related hole found while fixing #958 (f186ec8). Since then, arithmetic on present data that turns non-finite is refused (
evaluate.check_finite), so a NaN from0/0orinf - infcan no longer pass as absence.x + sum(a - b, by=...)is caught. But the difference of two grouped sums is not: the model builds silently, with rhs0instead of an error.import numpy as np import pandas as pd import xarray as xr import linopy linopy.options["semantics"] = "v1" spec = """ dimensions: generator: { dtype: str } bus: { dtype: str } relations: gen_bus: { key: generator, values: bus } parameters: a: { dims: [generator] } b: { dims: [generator] } variables: x: dims: [bus] bounds: { lower: 0 } constraints: row: dims: [bus] expression: x + sum(a, by=gen_bus, over=generator, into=bus) - sum(b, by=gen_bus, over=generator, into=bus) >= 1 objective: { sense: minimize, expression: sum(x) } """ generator = pd.Index(["g1", "g2"], name="generator") a = xr.DataArray([np.inf, 1.0], coords=[generator]) gen_bus = xr.DataArray(["n1", "n1"], coords=[generator]) sources = {"generator": generator, "a": a, "b": a, "gen_bus": gen_bus, "bus": pd.Index(["n1"], name="bus")} m = linopy.Model.from_spec(spec, sources) print(m.constraints["row"].rhs.values) # prints [0.]; expected SpecDataError (inf - inf at bus='n1')
Likely cause: the grouped sum's
fillna(0)(or its reindexing) hides the non-finite operand before the subtraction is checked. Unverified.Note
The following content was generated by AI.
The grouped-sum case above is fixed in 81c8eed: data summands a term's constant adds up across a sum or a comparison are now checked too (
x + a - a,x + a >= a,sum - sum,shift - shift). Still not caught: data subtracted after a scaled term, e.g.(x + a) * 2 - 2 * a, since summands are not distributed through*or/.
In the long term I would like to have things strict, but for now this is fine. Tracking this so we can readdress it later.
Note
The following content was generated by AI.
Describe the feature you'd like to see
With the move to mathspec 0.2.0 in #958,
linopy/spec/builder.py::_check_liveno longer refuses a constraint row that absence empties. It drops the row silently. This follows mathspec's documented semantics: absence spread through arithmetic "takes the row with it", and the first row of a bareshift"is not built".Before (0a725f4), such a row raised
SpecDataError("N row(s) hold no variable term"), because a row that silently disappears no longer binds.The current state after #958:
x - shift(x)at the first snapshotVariableas a termabsence: zeroagainst a binding sideStrict is the long-term goal. Open questions:
add_spec(..., strict=True), or strict by default with an explicit edge policy in the spec (shift(..., edge=...))?Reasons it is relaxed for now:
pypsa.yaml, its netcdf round trip and thespec_pypsabenchmark rely on a bareshiftdropping the first snapshot.Minimal example
Tests pinning the current behaviour:
test/test_spec_builder.py::test_absence_takes_the_row_with_itand::test_a_row_the_data_emptied_of_variables_is_refused.