Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
fb9f6ab
perf: add to_matrix_via_csr
coroa Mar 18, 2026
80a8b70
perf: improve per-constraint csr matrix construction
coroa Mar 19, 2026
89115c2
Add conversion functions
coroa Mar 19, 2026
bcd0228
feat: add ability to freeze constraints into csr
coroa Mar 19, 2026
b4dc1ea
Add io.to_netcdf support for frozen Constraint
coroa Mar 19, 2026
304a2e7
fix: re-implement matrices
coroa Mar 19, 2026
0fc2673
Move sum_duplicates
coroa Mar 20, 2026
3c8c5d6
feat: VariableLabelIndex
coroa Mar 21, 2026
0b9de00
fix: until solve
coroa Mar 22, 2026
1122b16
fix: disentangle range and ncons
coroa Mar 23, 2026
19125ac
fix: don't freeze if model is chunked
coroa Mar 23, 2026
58879fc
fix typing errors
coroa Mar 24, 2026
7fe0392
fix: bring back forward-refs
coroa Mar 24, 2026
92752e2
fix issues in tests
coroa Mar 24, 2026
83dd58a
fix: add doc strings to VariableLabelIndex
coroa Mar 24, 2026
e9e7870
Merge upstream/master and fix copy() for new Constraint types
coroa Mar 24, 2026
2bec451
test: relax dtype assertions for Windows np.int32 compatibility
coroa Mar 24, 2026
eca2945
Merge remote-tracking branch 'upstream/master' into perf/matrix-acces…
coroa Mar 24, 2026
3b2a415
fix: review fixes for #630 (matrix accessor rewrite) (#632)
FabianHofmann Mar 25, 2026
189f0c5
rename Constraint to CSRConstraint
FabianHofmann Mar 26, 2026
a256212
rename MutableConstraint to Constraint (original name)
FabianHofmann Mar 26, 2026
3ead682
fix: xpress crash with zero constraints and remove_variables not remo…
FabianHofmann Mar 26, 2026
6dc0368
Merge branch 'master' into perf/matrix-accessor-rewrite
FabianHofmann Mar 26, 2026
86884c0
Fix MultiIndex deprecation warning in CSRConstraint by using assign_m…
FabianHofmann Mar 26, 2026
3a68362
Add freeze_constraints option, default freeze to None (resolves from …
FabianHofmann Mar 26, 2026
f3c9516
Merge branch 'master' into perf/matrix-accessor-rewrite
FabianHofmann Mar 26, 2026
9b09bda
bench: add pypsa carbon_management benchmark for direct solver path
FabianHofmann Mar 26, 2026
7696d3e
Add set_names parameter to skip solver name-setting in direct IO, use…
FabianHofmann Mar 26, 2026
d356b33
perf: use polars to list logic in print_variables and print_constraints
FabianHofmann Mar 27, 2026
05e72e4
Use non-deprecated formatting APIs in tests
FabianHofmann Mar 27, 2026
9761ae3
Tighten direct-solver naming tests and repr formatting
FabianHofmann Mar 27, 2026
93f45b9
fix mypy
FabianHofmann Mar 27, 2026
6421628
Fix mypy failures in constraint and IO tests
FabianHofmann Mar 27, 2026
9e94bfa
Move freeze and direct IO naming settings to Model
FabianHofmann Mar 27, 2026
bd10393
perf: speed up mutable direct solver export
FabianHofmann Mar 27, 2026
2ab5ed4
docs: add CSRConstraint documentation to release notes, API reference…
FabianHofmann Mar 30, 2026
5e26346
perf: direct CSR-to-LP writer for frozen constraints (#631)
FBumann Mar 30, 2026
ed1a517
Merge branch 'master' into perf/matrix-accessor-rewrite
FabianHofmann Mar 30, 2026
aa71276
merge: move bench_pypsa_carbon_management into benchmarks/ suite, fix…
FabianHofmann Mar 30, 2026
1a88b38
fix: align CSRConstraint.iterate_slices return type with base class f…
FabianHofmann Mar 30, 2026
4b10d0a
fix: make assert_linequal compare semantic equality of expressions
FBumann Apr 1, 2026
8000762
Merge branch 'master' into perf/matrix-accessor-rewrite
FabianHofmann Apr 20, 2026
23f63b9
fix types from merge conflict
FabianHofmann Apr 20, 2026
7ef1d77
docs: note CSRConstraint API differences from Constraint
FabianHofmann Apr 20, 2026
0feb6da
Merge branch 'master' into perf/matrix-accessor-rewrite
FabianHofmann Apr 20, 2026
9be6826
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 20, 2026
c709cee
Merge branch 'master' into perf/matrix-accessor-rewrite
FabianHofmann May 7, 2026
ba44376
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 7, 2026
1aef091
refac(CSRConstraint): simplify iterate_slices with _equal_nnz_slices …
coroa May 8, 2026
dd86868
deprecate(Constraint): add DeprecationWarning to .flat property
coroa May 8, 2026
672d0b8
refac(ConstraintBase): make ncons/lhs/to_matrix abstract; move to Con…
coroa May 8, 2026
fa04a15
Merge branch 'master' into perf/matrix-accessor-rewrite
FabianHofmann May 11, 2026
27514fe
fix(Model): add __weakref__ slot and drop stale matrices.clean_cached…
FabianHofmann May 11, 2026
7740410
refac(CSRConstraint): direct rhs setter, read-only lhs setter; drop x…
FabianHofmann May 11, 2026
7a5172f
fix(types): drop duplicate MaskLike/PathLike; annotate sign_expr for …
FabianHofmann May 11, 2026
d036ad9
refac(CSRConstraint): make rhs setter read-only; call .mutable() to m…
FabianHofmann May 11, 2026
3e8f64d
Merge branch 'master' into perf/matrix-accessor-rewrite
FabianHofmann May 11, 2026
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
12 changes: 10 additions & 2 deletions linopy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,13 @@
import linopy.monkey_patch_xarray # noqa: F401
from linopy.common import align
from linopy.config import options
from linopy.constants import EQUAL, GREATER_EQUAL, LESS_EQUAL
from linopy.constraints import Constraint, Constraints
from linopy.constants import EQUAL, GREATER_EQUAL, LESS_EQUAL, PerformanceWarning
from linopy.constraints import (
Constraint,
ConstraintBase,
Constraints,
MutableConstraint,
)
from linopy.expressions import LinearExpression, QuadraticExpression, merge
from linopy.io import read_netcdf
from linopy.model import Model, Variable, Variables, available_solvers
Expand All @@ -30,8 +35,11 @@

__all__ = (
"Constraint",
"ConstraintBase",
"Constraints",
"MutableConstraint",
"EQUAL",
"PerformanceWarning",
"GREATER_EQUAL",
"LESS_EQUAL",
"LinearExpression",
Expand Down
109 changes: 103 additions & 6 deletions linopy/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import operator
import os
from collections.abc import Callable, Generator, Hashable, Iterable, Sequence
from functools import partial, reduce, wraps
from functools import cached_property, partial, reduce, wraps
from pathlib import Path
from typing import TYPE_CHECKING, Any, Generic, TypeVar, overload
from warnings import warn
Expand Down Expand Up @@ -40,7 +40,7 @@
)

if TYPE_CHECKING:
from linopy.constraints import Constraint
from linopy.constraints import ConstraintBase
from linopy.expressions import LinearExpression, QuadraticExpression
from linopy.variables import Variable

Expand Down Expand Up @@ -554,7 +554,7 @@ def fill_missing_coords(
return ds


T = TypeVar("T", Dataset, "Variable", "LinearExpression", "Constraint")
T = TypeVar("T", Dataset, "Variable", "LinearExpression", "ConstraintBase")


@overload
Expand Down Expand Up @@ -583,10 +583,10 @@ def iterate_slices(

@overload
def iterate_slices(
ds: Constraint,
ds: ConstraintBase,
slice_size: int | None = 10_000,
slice_dims: list | None = None,
) -> Generator[Constraint, None, None]: ...
) -> Generator[ConstraintBase, None, None]: ...


def iterate_slices(
Expand Down Expand Up @@ -939,6 +939,49 @@ def find_single(value: int) -> tuple[str, dict] | tuple[None, None]:
raise ValueError("Array's with more than two dimensions is not supported")


class VariableLabelIndex:
"""
Index for O(1) mapping between variable labels and dense positions.

Both arrays are computed lazily and cached:
- ``vlabels``: active variable labels in encounter order, shape (n_active_vars,)
- ``label_to_pos``: derived from vlabels; size _xCounter, maps label -> position (-1 if masked)

Invalidated by clearing the instance ``__dict__`` when variables are added or removed.
"""

def __init__(self, variables: Any) -> None:
self._variables = variables

@cached_property
def vlabels(self) -> np.ndarray:
Comment thread
coroa marked this conversation as resolved.
label_lists = []
for _, var in self._variables.items():
labels = var.labels.values.ravel()
mask = labels != -1
label_lists.append(labels[mask])
return (
np.concatenate(label_lists) if label_lists else np.array([], dtype=np.intp)
)

@cached_property
def label_to_pos(self) -> np.ndarray:
Comment thread
coroa marked this conversation as resolved.
vlabels = self.vlabels
n = self._variables.model._xCounter
label_to_pos = np.full(n, -1, dtype=np.intp)
label_to_pos[vlabels] = np.arange(len(vlabels), dtype=np.intp)
return label_to_pos

@property
def n_active_vars(self) -> int:
return len(self.vlabels)

def invalidate(self) -> None:
"""Clear cached arrays so they are recomputed on next access."""
self.__dict__.pop("vlabels", None)
self.__dict__.pop("label_to_pos", None)


def get_label_position(
obj: Any,
values: int | np.ndarray,
Expand Down Expand Up @@ -1306,7 +1349,7 @@ def align(
"Variable",
"LinearExpression",
"QuadraticExpression",
"Constraint",
"ConstraintBase",
)


Expand Down Expand Up @@ -1358,6 +1401,60 @@ def __call__(self) -> bool:
return self.value


def coords_to_dataset_vars(coords: list[pd.Index]) -> dict[str, DataArray]:
"""
Serialize a list of pd.Index (including MultiIndex) to a DataArray dict.

Suitable for embedding coordinate metadata as plain data variables in a
Dataset that has its own unrelated dimensions (e.g. CSR netcdf format).
Reconstruct with :func:`coords_from_dataset`.
"""
data_vars: dict[str, DataArray] = {}
for c in coords:
if isinstance(c, pd.MultiIndex):
for level_name, level_values in zip(c.names, c.levels):
data_vars[f"_coord_{c.name}_level_{level_name}"] = DataArray(
np.array(level_values),
dims=[f"_coorddim_{c.name}_level_{level_name}"],
)
data_vars[f"_coord_{c.name}_codes"] = DataArray(
np.array(c.codes).T,
dims=[f"_coorddim_{c.name}", f"_coorddim_{c.name}_nlevels"],
)
else:
data_vars[f"_coord_{c.name}"] = DataArray(
np.array(c), dims=[f"_coorddim_{c.name}"]
)
return data_vars


def coords_from_dataset(ds: Dataset, coord_dims: list[str]) -> list[pd.Index]:
"""
Deserialize a list of pd.Index (including MultiIndex) from a Dataset.

Reconstructs coordinates previously serialized by :func:`coords_to_dataset_vars`.
"""
coords = []
for d in coord_dims:
if f"_coord_{d}_codes" in ds:
codes_2d = ds[f"_coord_{d}_codes"].values.T
level_names = [
k[len(f"_coord_{d}_level_") :]
for k in ds
if k.startswith(f"_coord_{d}_level_")
]
arrays = [
ds[f"_coord_{d}_level_{ln}"].values[codes_2d[i]]
for i, ln in enumerate(level_names)
]
mi = pd.MultiIndex.from_arrays(arrays, names=level_names)
mi.name = d
coords.append(mi)
else:
coords.append(pd.Index(ds[f"_coord_{d}"].values, name=d))
return coords


def is_constant(x: SideLike) -> bool:
"""
Check if the given object is a constant type or an expression type without
Expand Down
5 changes: 5 additions & 0 deletions linopy/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@
GREATER_EQUAL = ">="
LESS_EQUAL = "<="


class PerformanceWarning(UserWarning):
"""Warning raised when an operation triggers expensive Dataset reconstruction."""


long_EQUAL = "=="
short_GREATER_EQUAL = ">"
short_LESS_EQUAL = "<"
Expand Down
Loading
Loading