Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/release_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ Files from earlier versions load as before.
* Adding or removing variables after ``freeze=True`` no longer breaks ``model.matrices`` with ``inconsistent shapes``. (`#926 <https://github.com/PyPSA/linopy/issues/926>`__)
* A ``CSRConstraint`` from a CSR-backed expression keeps its auxiliary coordinates through ``to_dense()``, ``freeze()`` and netcdf. (`#941 <https://github.com/PyPSA/linopy/issues/941>`__)
* Unsupported ``CSRConstraint`` operations (``loc``, ``update``, ``from_rule``, setting ``coeffs``/``vars``/``sign``) raise an ``AttributeError`` that points to ``.mutable()``. (`#963 <https://github.com/PyPSA/linopy/issues/963>`__)
* Freezing a constraint with a ``MultiIndex`` dimension no longer raises ``TypeError: Must pass list-like as `names```.
* Repeated matrix builds on an unchanged model reuse the cached column mapping again, so persistent snapshot diffs skip untouched frozen constraints. (`#933 <https://github.com/PyPSA/linopy/issues/933>`__)

*Expressions*
Expand Down
13 changes: 11 additions & 2 deletions linopy/csr.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ def from_coords(cls, coords: Iterable[pd.Index]) -> Grid:
def from_dataset(cls, ds: Dataset | DataArray, dims: Iterable[str]) -> Grid:
"""Build from the indexes and auxiliary coordinates ``ds`` carries on ``dims``."""
dims = tuple(dims)
indexes = {d: ds.get_index(d).rename(d) for d in dims}
indexes = {d: _named(ds.get_index(d), d) for d in dims}
return cls(indexes, _aux_coords(ds, set(dims)))

@classmethod
Expand Down Expand Up @@ -373,7 +373,7 @@ def from_grouper(
)
for d in coord_dims:
if d != member_dim:
indexes[d] = ds.get_index(d).rename(d)
indexes[d] = _named(ds.get_index(d), d)
aux |= _aux_coords(ds, set(coord_dims) - {member_dim})
grid = Grid({d: indexes[d] for d in grid_dims}, aux)
if isinstance(source, Dataset):
Expand Down Expand Up @@ -837,6 +837,15 @@ def _readonly(values: np.ndarray) -> np.ndarray:
return values


def _named(index: pd.Index, dim: str) -> pd.Index:
"""``index`` named after ``dim``, keeping the level names of a MultiIndex."""
if isinstance(index, pd.MultiIndex):
index = index.copy()
index.name = dim
return index
return index.rename(dim)


def _aux_coords(ds: Dataset | DataArray, dims: set[str]) -> AuxCoords:
"""Scalar and one-dimensional auxiliary coordinates of ``ds`` lying on ``dims``."""
aux: AuxCoords = {}
Expand Down
4 changes: 3 additions & 1 deletion linopy/expressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -1275,7 +1275,9 @@ def _add_constant_legacy(
if is_nan_scalar(other):
check_user_nan()
warn_legacy_absence(self.const)
return self.assign(const=self.const.fillna(0) + other)
return self.assign(
const=self.const.fillna(0) + cast("float | np.number", other)
)
self_const, da, needs_reindex = self._broadcast_and_align(
other, fill_value, join
)
Expand Down
18 changes: 18 additions & 0 deletions test/test_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,24 @@ def test_model_to_netcdf_with_multiindex(
assert_model_equal(m, p)


def test_model_to_netcdf_with_multiindex_frozen_constraint(
model_with_multiindex: Model, tmp_path: Path
) -> None:
from linopy.constraints import CSRConstraint

m = model_with_multiindex
x, y = m.variables["x-var"], m.variables["y-var"]
con = m.add_constraints(x + y, LESS_EQUAL, 10, name="frozen", freeze=True)
assert isinstance(con, CSRConstraint)

fn = tmp_path / "test.nc"
m.to_netcdf(fn)
p = read_netcdf(fn)

assert isinstance(p.constraints["frozen"], CSRConstraint)
assert_model_equal(m, p)


# Regression for https://github.com/PyPSA/linopy/issues/525.
def test_model_to_netcdf_with_multiindex_scipy_engine(
model_with_multiindex: Model, tmp_path: Path
Expand Down
Loading