Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 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
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
18 changes: 9 additions & 9 deletions benchmarks/test_matrices.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@

def _access_matrices(m):
"""Access all matrix properties to force computation."""
m.matrices.clean_cached_properties()
_ = m.matrices.A
_ = m.matrices.b
_ = m.matrices.c
_ = m.matrices.lb
_ = m.matrices.ub
_ = m.matrices.sense
_ = m.matrices.vlabels
_ = m.matrices.clabels
matrices = m.matrices
_ = matrices.A
_ = matrices.b
_ = matrices.c
_ = matrices.lb
_ = matrices.ub
_ = matrices.sense
_ = matrices.vlabels
_ = matrices.clabels


@pytest.mark.parametrize("n", BASIC_SIZES, ids=[f"n={n}" for n in BASIC_SIZES])
Expand Down
43 changes: 43 additions & 0 deletions benchmarks/test_pypsa_carbon_management.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import pypsa
import pytest

import linopy as lp


@pytest.fixture(scope="module")
def network():
return pypsa.examples.carbon_management()


def test_create_model_frozen(benchmark, network):
benchmark(network.optimize.create_model, freeze_constraints=True)


def test_create_model_mutable(benchmark, network):
benchmark(network.optimize.create_model, freeze_constraints=False)


@pytest.fixture(scope="module")
def model_frozen(network):
return network.optimize.create_model(freeze_constraints=True)


@pytest.fixture(scope="module")
def model_mutable(network):
return network.optimize.create_model(freeze_constraints=False)


def test_to_highspy_frozen(benchmark, model_frozen):
benchmark(lp.io.to_highspy, model_frozen)


def test_to_highspy_mutable(benchmark, model_mutable):
benchmark(lp.io.to_highspy, model_mutable)


def test_to_highspy_mutable_no_names(benchmark, model_mutable):
benchmark(lp.io.to_highspy, model_mutable, set_names=False)


def test_to_highspy_frozen_no_names(benchmark, model_frozen):
benchmark(lp.io.to_highspy, model_frozen, set_names=False)
21 changes: 21 additions & 0 deletions doc/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,27 @@ Constraint
constraints.Constraint.sign
constraints.Constraint.rhs
constraints.Constraint.flat
constraints.Constraint.freeze
constraints.Constraint.mutable


CSRConstraint
-------------

``CSRConstraint`` is a memory-efficient, immutable constraint representation backed by a scipy CSR sparse matrix. See the :doc:`creating-constraints` guide for usage.

.. autosummary::
:toctree: generated/

constraints.CSRConstraint
constraints.CSRConstraint.coeffs
constraints.CSRConstraint.vars
constraints.CSRConstraint.sign
constraints.CSRConstraint.rhs
constraints.CSRConstraint.ncons
constraints.CSRConstraint.nterm
constraints.CSRConstraint.freeze
constraints.CSRConstraint.mutable


Constraints
Expand Down
6 changes: 6 additions & 0 deletions doc/release_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ Upcoming Version
* Add ``format_labels()`` on ``Constraints``/``Variables`` and ``format_infeasibilities()`` on ``Model`` that return strings instead of printing to stdout, allowing usage with logging, storage, or custom output handling. Deprecate ``print_labels()`` and ``print_infeasibilities()``.
* Add ``fix()``, ``unfix()``, and ``fixed`` to ``Variable`` and ``Variables`` for fixing variables to values via equality constraints. Supports automatic rounding for integer/binary variables.
* Add ``relax()``, ``unrelax()``, and ``relaxed`` to ``Variable`` and ``Variables`` for LP relaxation of integer/binary variables. Supports partial relaxation via filtered views (e.g. ``m.variables.integers.relax()``). Semi-continuous variables raise ``NotImplementedError``.
* Increase speed of direct solver communication (~10x) in conversion functions like `to_highspy` through faster matrix creation (see below), leading to significant overall speed-up when setting `io_api="direct"`.
* Add ``CSRConstraint``, a memory-efficient immutable constraint representation backed by scipy CSR sparse matrices. Provides up to 90% memory savings for constraints with many terms and 30–120x faster matrix generation for direct solver APIs.
- Add ``freeze_constraints`` parameter to ``Model`` for globally storing constraints in CSR format on ``add_constraints``.
- Add ``freeze`` parameter to ``Model.add_constraints`` for per-constraint opt-in to CSR storage.
- Add ``freeze()`` and ``mutable()`` methods on ``Constraint`` and ``CSRConstraint`` for lossless conversion between xarray-backed and CSR-backed representations.
* Add ``set_names_in_solver_io`` parameter to ``Model`` to skip setting variable/constraint names in direct solver exports for faster performance.


Version 0.6.6
Expand Down
123 changes: 121 additions & 2 deletions examples/creating-constraints.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -235,8 +235,127 @@
{
"cell_type": "markdown",
"id": "r0wxi7v1m7l",
"source": "## Coordinate Alignment in Constraints\n\nAs an alternative to the ``<=``, ``>=``, ``==`` operators, linopy provides ``.le()``, ``.ge()``, and ``.eq()`` methods on variables and expressions. These methods accept a ``join`` parameter (``\"inner\"``, ``\"outer\"``, ``\"left\"``, ``\"right\"``) for explicit control over how coordinates are aligned when creating constraints. See the :doc:`coordinate-alignment` guide for details.",
"metadata": {}
"metadata": {},
"source": "## Coordinate Alignment in Constraints\n\nAs an alternative to the ``<=``, ``>=``, ``==`` operators, linopy provides ``.le()``, ``.ge()``, and ``.eq()`` methods on variables and expressions. These methods accept a ``join`` parameter (``\"inner\"``, ``\"outer\"``, ``\"left\"``, ``\"right\"``) for explicit control over how coordinates are aligned when creating constraints. See the :doc:`coordinate-alignment` guide for details."
},
{
"cell_type": "markdown",
"id": "csr-backend-intro",
"metadata": {},
"source": [
"## CSR Backend (Advanced)\n",
"\n",
"By default, linopy stores each constraint as an `xarray.Dataset` (`Constraint`). This is flexible and allows full label-based indexing, but can use significant memory when constraints have many terms.\n",
"\n",
"For large models, linopy provides an alternative **CSR backend** via the `CSRConstraint` class. It stores the constraint coefficients as a [scipy CSR sparse matrix](https://docs.scipy.org/doc/scipy/reference/generated/scipy.sparse.csr_array.html) with flat numpy arrays for the right-hand side and signs. This can reduce memory usage by up to **90%** and speeds up matrix generation for direct solver APIs by **30–120x**.\n",
"\n",
"`CSRConstraint` is **immutable** — once frozen, the constraint data cannot be modified in place. You can always convert back to the mutable xarray-backed `Constraint` if needed."
]
},
{
"cell_type": "markdown",
"id": "csr-per-constraint",
"metadata": {},
"source": [
"### Freezing individual constraints\n",
"\n",
"Pass `freeze=True` to `add_constraints` to store a single constraint in CSR format:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "csr-per-constraint-code",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"\n",
"from linopy import Model\n",
"\n",
"m2 = Model()\n",
"y = m2.add_variables(coords=[np.arange(100)], name=\"y\")\n",
"\n",
"m2.add_constraints(y <= 10, name=\"upper\", freeze=True)\n",
"\n",
"print(type(m2.constraints[\"upper\"]))\n",
"m2.constraints[\"upper\"]"
]
},
{
"cell_type": "markdown",
"id": "csr-global",
"metadata": {},
"source": [
"### Freezing all constraints globally\n",
"\n",
"Set `freeze_constraints=True` on the `Model` to automatically freeze every constraint added via `add_constraints`:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "csr-global-code",
"metadata": {},
"outputs": [],
"source": [
"m3 = Model(freeze_constraints=True)\n",
"z = m3.add_variables(coords=[np.arange(50)], name=\"z\")\n",
"m3.add_constraints(z >= 0, name=\"lower\")\n",
"m3.add_constraints(z <= 100, name=\"upper\")\n",
"\n",
"print(type(m3.constraints[\"lower\"]))\n",
"print(type(m3.constraints[\"upper\"]))"
]
},
{
"cell_type": "markdown",
"id": "csr-roundtrip",
"metadata": {},
"source": [
"### Converting between representations\n",
"\n",
"Use `.freeze()` and `.mutable()` to convert between the two representations. The conversion is lossless:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "csr-roundtrip-code",
"metadata": {},
"outputs": [],
"source": [
"frozen = m3.constraints[\"lower\"]\n",
"print(f\"Frozen type: {type(frozen).__name__}\")\n",
"\n",
"thawed = frozen.mutable()\n",
"print(f\"Mutable type: {type(thawed).__name__}\")\n",
"\n",
"refrozen = thawed.freeze()\n",
"print(f\"Re-frozen type: {type(refrozen).__name__}\")"
]
},
{
"cell_type": "markdown",
"id": "csr-when-to-use",
"metadata": {},
"source": [
"### When to use the CSR backend\n",
"\n",
"The CSR backend is most beneficial when:\n",
"\n",
"- Your model has **many constraints with many terms**.\n",
"- **Memory** is a bottleneck.\n",
"- You use a **direct solver API** (e.g. HiGHS, Gurobi Python bindings) rather than file-based I/O.\n",
"\n",
"For small models the overhead is negligible and the default xarray-backed `Constraint` is perfectly fine.\n",
"\n",
"Additionally, if you don't need variable and constraint names in the solver (e.g. for batch solves), you can disable name export for extra speed:\n",
"\n",
"```python\n",
"m = Model(freeze_constraints=True, set_names_in_solver_io=False)\n",
"```"
]
}
],
"metadata": {
Expand Down
14 changes: 11 additions & 3 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,
CSRConstraint,
)
from linopy.expressions import LinearExpression, QuadraticExpression, merge
from linopy.io import read_netcdf
from linopy.model import Model, Variable, Variables, available_solvers
Expand All @@ -29,9 +34,12 @@
pass

__all__ = (
"Constraint",
"CSRConstraint",
"ConstraintBase",
"Constraints",
"Constraint",
"EQUAL",
"PerformanceWarning",
"GREATER_EQUAL",
"LESS_EQUAL",
"LinearExpression",
Expand Down
Loading
Loading