From 1970f6e6ee80868858bb7daf647cda2e14a5f1f9 Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Thu, 16 Apr 2026 17:14:21 +0200 Subject: [PATCH 1/4] fix: compatibility with xarray 2026.4.0 xarray 2026.4.0 no longer allows passing a Dataset to the Dataset() constructor. Remove redundant Dataset() wrappers around methods that already return a Dataset (transpose, assign_coords), and add an isinstance check in the parameters setter where the input may be either a Dataset or a Mapping. Co-Authored-By: Claude Opus 4.6 (1M context) --- linopy/expressions.py | 6 +++--- linopy/model.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/linopy/expressions.py b/linopy/expressions.py index ca491c3e..7bbc969f 100644 --- a/linopy/expressions.py +++ b/linopy/expressions.py @@ -284,7 +284,7 @@ def sum(self, use_fallback: bool = False, **kwargs: Any) -> LinearExpression: index.names = [str(col) for col in orig_group.columns] index.name = GROUP_DIM new_coords = Coordinates.from_pandas_multiindex(index, GROUP_DIM) - ds = xr.Dataset(ds.assign_coords(new_coords)) + ds = ds.assign_coords(new_coords) ds = ds.rename({GROUP_DIM: final_group_name}) return LinearExpression(ds, self.model) @@ -392,7 +392,7 @@ def __init__(self, data: Dataset | Any | None, model: Model) -> None: data = assign_multiindex_safe(data, **coeffs_vars_dict) # transpose with new Dataset to really ensure correct order - data = Dataset(data.transpose(..., TERM_DIM)) + data = data.transpose(..., TERM_DIM) # ensure helper dimensions are not set as coordinates if drop_dims := set(HELPER_DIMS).intersection(data.coords): @@ -2098,7 +2098,7 @@ def __init__(self, data: Dataset | None, model: Model) -> None: raise ValueError(f"Size of dimension {FACTOR_DIM} must be 2.") # transpose data to have _term as last dimension and _factor as second last - data = xr.Dataset(data.transpose(..., FACTOR_DIM, TERM_DIM)) + data = data.transpose(..., FACTOR_DIM, TERM_DIM) self._data = data @property diff --git a/linopy/model.py b/linopy/model.py index 2a635680..57acecc2 100644 --- a/linopy/model.py +++ b/linopy/model.py @@ -359,7 +359,7 @@ def parameters(self, value: Dataset | Mapping) -> None: """ Set the parameters of the model. """ - self._parameters = Dataset(value) + self._parameters = value if isinstance(value, Dataset) else Dataset(value) @property def solution(self) -> Dataset: From 543118670519a63eea90da64099139a91982f7d7 Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Thu, 16 Apr 2026 18:27:53 +0200 Subject: [PATCH 2/4] fix: exclude highspy 1.14.0 due to suboptimal MIP solutions highspy 1.14.0 returns suboptimal solutions for modified MIP models, causing test_modified_model to fail across all platforms. Closes #651 Co-Authored-By: Claude Opus 4.6 (1M context) --- pyproject.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b8672acc..33bca8b4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,7 +62,7 @@ docs = [ "gurobipy>=13.0.0", "ipykernel==6.29.5", "matplotlib==3.9.1", - "highspy>=1.7.1", + "highspy>=1.7.1,!=1.14.0", ] dev = [ "pytest", @@ -74,7 +74,7 @@ dev = [ "types-paramiko", "types-requests", "gurobipy", - "highspy", + "highspy!=1.14.0", ] benchmarks = [ "pytest-benchmark", @@ -82,8 +82,8 @@ benchmarks = [ ] solvers = [ "gurobipy", - "highspy>=1.5.0; python_version < '3.12'", - "highspy>=1.7.1; python_version >= '3.12'", + "highspy>=1.5.0,!=1.14.0; python_version < '3.12'", + "highspy>=1.7.1,!=1.14.0; python_version >= '3.12'", "cplex; platform_system != 'Darwin' and python_version < '3.12'", "mosek", "mindoptpy; python_version < '3.12'", From d9efe1461994bd9e550d5aff154a4d6db575f245 Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Thu, 16 Apr 2026 18:28:28 +0200 Subject: [PATCH 3/4] Revert "fix: exclude highspy 1.14.0 due to suboptimal MIP solutions" This reverts commit 543118670519a63eea90da64099139a91982f7d7. --- pyproject.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 33bca8b4..b8672acc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,7 +62,7 @@ docs = [ "gurobipy>=13.0.0", "ipykernel==6.29.5", "matplotlib==3.9.1", - "highspy>=1.7.1,!=1.14.0", + "highspy>=1.7.1", ] dev = [ "pytest", @@ -74,7 +74,7 @@ dev = [ "types-paramiko", "types-requests", "gurobipy", - "highspy!=1.14.0", + "highspy", ] benchmarks = [ "pytest-benchmark", @@ -82,8 +82,8 @@ benchmarks = [ ] solvers = [ "gurobipy", - "highspy>=1.5.0,!=1.14.0; python_version < '3.12'", - "highspy>=1.7.1,!=1.14.0; python_version >= '3.12'", + "highspy>=1.5.0; python_version < '3.12'", + "highspy>=1.7.1; python_version >= '3.12'", "cplex; platform_system != 'Darwin' and python_version < '3.12'", "mosek", "mindoptpy; python_version < '3.12'", From 4436de2a7a4b25ce8669ea5a4138b76ea034f456 Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Thu, 16 Apr 2026 18:36:00 +0200 Subject: [PATCH 4/4] fix: resolve mypy error in merge() type annotation The `cls` parameter was typed as `type[GenericExpression]` but assigned concrete subclasses. Use `type[GenericExpression] | None` with a targeted type: ignore on the narrowing assignment, and add an assert to satisfy mypy at the call site. Closes #652 Co-Authored-By: Claude Opus 4.6 (1M context) --- linopy/expressions.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/linopy/expressions.py b/linopy/expressions.py index 7bbc969f..33f6d774 100644 --- a/linopy/expressions.py +++ b/linopy/expressions.py @@ -2340,7 +2340,7 @@ def merge( LinearExpression | QuadraticExpression | variables.Variable | Dataset ], dim: str = TERM_DIM, - cls: type[GenericExpression] = None, # type: ignore + cls: type[GenericExpression] | None = None, join: str | None = None, **kwargs: Any, ) -> GenericExpression: @@ -2384,7 +2384,7 @@ def merge( has_quad_expression = any(type(e) is QuadraticExpression for e in exprs) has_linear_expression = any(type(e) is LinearExpression for e in exprs) if cls is None: - cls = QuadraticExpression if has_quad_expression else LinearExpression + cls = QuadraticExpression if has_quad_expression else LinearExpression # type: ignore[assignment] if cls is QuadraticExpression and dim == TERM_DIM and has_linear_expression: raise ValueError( @@ -2445,6 +2445,7 @@ def merge( for d in set(HELPER_DIMS) & set(ds.coords): ds = ds.reset_index(d, drop=True) + assert cls is not None return cls(ds, model)