Issue Description
Follow-up to #450 / #614 / #706, covering pandas bounds.
When lower/upper bounds passed to add_variables are missing a dimension that is present in coords, the bound should be broadcast to the full dimensionality from coords (this is what #614 established for DataArray bounds).
For pandas Series/DataFrame bounds this does not happen. If a pandas bound is missing a dimension, the missing dimension is silently dropped and the resulting variable has the wrong shape — not merely the wrong dimension order (#706), but a missing dimension entirely.
As with #706, the behavior depends on the type of the other bound: if at least one bound is a scalar, the scalar carries the full coords dimensionality and broadcasts the pandas bound up, so it accidentally works. If both bounds are pandas objects missing the same dimension, that dimension is lost.
Reproducible Example
import pandas as pd
import linopy
x = pd.Index(["a", "b", "c"], name="x")
y = pd.Index(["X", "Y"], name="y")
# pandas Series bound, indexed only by 'x' (missing 'y')
s = pd.Series([1.0, 2.0, 3.0], index=x)
m = linopy.Model()
# scalar lower -> dims follow coords: ('x', 'y')
foo = m.add_variables(lower=0, upper=s, coords=[x, y], name="foo")
print("foo:", foo.dims) # ('x', 'y')
# both bounds are Series missing 'y' -> 'y' is dropped entirely
bar = m.add_variables(lower=-s, upper=s, coords=[x, y], name="bar")
print("bar:", bar.dims) # ('x',) <-- wrong, 'y' is missing
Output:
foo: ('x', 'y')
bar: ('x',)
Expected Behavior
Both foo and bar should have dimensions ('x', 'y') — pandas bounds missing a dimension should be broadcast to the full dimensionality given by coords, consistent with DataArray bounds.
Root Cause
Two pieces conspire:
linopy/common.py, pandas_to_dataarray builds the DataArray dimensions only from the pandas object's own axes and ignores the coords argument entirely (DataArray(arr, coords=None, dims=dims)).
linopy/model.py, _validate_dataarray_bounds — which performs the coords-driven expansion of missing dimensions — returns immediately for any non-DataArray input (if not isinstance(arr, DataArray): return arr).
So pandas Series/DataFrame bounds never get expanded against coords. When one bound is a scalar, as_dataarray(scalar, coords) recovers the full dimensionality and broadcasting masks the bug — hence the type-dependent inconsistency.
Behavior summary when a coords dimension is missing from the bound:
| Bound type |
Result |
DataArray |
expanded (#614), order fixed (#706) |
pandas Series/DataFrame |
dimension silently dropped |
numpy array |
raises ValueError (positional, no dim names) |
| scalar |
carries full dims via coords |
A fix would let _validate_dataarray_bounds also handle pandas bounds — e.g. convert them via pandas_to_dataarray up front (pandas axes are named, so the dim mapping is unambiguous), then run the existing validate/expand path. numpy arrays are positional and cannot infer dim names, so leaving them to raise is reasonable.
Installed Versions
linopy master, commit be6d3a3.
Issue Description
Follow-up to #450 / #614 / #706, covering pandas bounds.
When
lower/upperbounds passed toadd_variablesare missing a dimension that is present incoords, the bound should be broadcast to the full dimensionality fromcoords(this is what #614 established forDataArraybounds).For pandas
Series/DataFramebounds this does not happen. If a pandas bound is missing a dimension, the missing dimension is silently dropped and the resulting variable has the wrong shape — not merely the wrong dimension order (#706), but a missing dimension entirely.As with #706, the behavior depends on the type of the other bound: if at least one bound is a scalar, the scalar carries the full
coordsdimensionality and broadcasts the pandas bound up, so it accidentally works. If both bounds are pandas objects missing the same dimension, that dimension is lost.Reproducible Example
Output:
Expected Behavior
Both
fooandbarshould have dimensions('x', 'y')— pandas bounds missing a dimension should be broadcast to the full dimensionality given bycoords, consistent withDataArraybounds.Root Cause
Two pieces conspire:
linopy/common.py,pandas_to_dataarraybuilds the DataArray dimensions only from the pandas object's own axes and ignores thecoordsargument entirely (DataArray(arr, coords=None, dims=dims)).linopy/model.py,_validate_dataarray_bounds— which performs thecoords-driven expansion of missing dimensions — returns immediately for any non-DataArrayinput (if not isinstance(arr, DataArray): return arr).So pandas
Series/DataFramebounds never get expanded againstcoords. When one bound is a scalar,as_dataarray(scalar, coords)recovers the full dimensionality and broadcasting masks the bug — hence the type-dependent inconsistency.Behavior summary when a
coordsdimension is missing from the bound:DataArraySeries/DataFramenumpyarrayValueError(positional, no dim names)coordsA fix would let
_validate_dataarray_boundsalso handle pandas bounds — e.g. convert them viapandas_to_dataarrayup front (pandas axes are named, so the dim mapping is unambiguous), then run the existing validate/expand path.numpyarrays are positional and cannot infer dim names, so leaving them to raise is reasonable.Installed Versions
linopy
master, commit be6d3a3.