Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
27a3129
feat(constraints): add _coef_dirty flag + rhs setter short-circuit
FabianHofmann May 19, 2026
663ab92
feat(persistent): add ModelSnapshot, CoefPattern, StructuralKey
FabianHofmann May 19, 2026
c1da075
feat(persistent): add ModelDiff and compute_diff
FabianHofmann May 19, 2026
572b426
feat(solvers): add persistent-update orchestration to Solver
FabianHofmann May 19, 2026
61b3647
test(persistent): smoke test Solver orchestrator with fake backend
FabianHofmann May 19, 2026
ff1ae15
feat(solvers): short-circuit rebuild when backend lacks persistent-up…
FabianHofmann May 19, 2026
f67cec6
feat(solvers): Gurobi apply_update for persistent solves
FabianHofmann May 19, 2026
8026293
feat(solvers): HiGHS apply_update for persistent solves
FabianHofmann May 19, 2026
b6601e3
test(persistent): cross-model, pickle, threading, failure-path coverage
FabianHofmann May 19, 2026
6922c7d
refactor(persistent): row-block numpy snapshot/diff
FabianHofmann May 20, 2026
0c30688
feat(persistent): opt-in coord-equality via ignore_dims
FabianHofmann May 20, 2026
8dbb8be
feat(persistent): lazy-build Solver, ModelDiff constructors, disallow…
FabianHofmann May 20, 2026
595bab0
feat(persistent): opt-in update tracking, snapshot-free ModelDiff.fro…
FabianHofmann May 21, 2026
6707917
feat(persistent): wire ModelDiff.from_models for track_updates=False
FabianHofmann May 21, 2026
2ae0bef
refactor(persistent): cleanups, VarKind enum, fold _clear_coef_dirty
FabianHofmann May 21, 2026
33fc991
refactor(persistent): CSR-backed ContainerConBuffers
FabianHofmann May 21, 2026
3bffb6e
refactor(persistent): flat-native ModelDiff storage
FabianHofmann May 21, 2026
4995e8b
feat(persistent): apply_update for Xpress and Mosek
FabianHofmann May 21, 2026
5c0a369
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 21, 2026
b38f4f3
Merge branch 'master' into feat/solver-update
FabianHofmann May 21, 2026
9fd88de
fix(persistent): serialize concurrent solves; satisfy mypy
FabianHofmann May 22, 2026
089cf2e
harden coords comparison
FabianHofmann May 22, 2026
08732a9
Variable.update() / Constraint.update() as canonical mutation API (#727)
FBumann May 27, 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: 18 additions & 0 deletions doc/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,14 @@ Attributes
Modification
------------

``Variable.update`` is the canonical mutation API. The legacy ``lower`` /
``upper`` setters still forward to ``update`` but emit a
``DeprecationWarning`` and will be removed in a future release.

.. autosummary::
:toctree: generated/

variables.Variable.update
variables.Variable.fix
variables.Variable.unfix
variables.Variable.relax
Expand Down Expand Up @@ -330,6 +335,19 @@ Structure
constraints.Constraint.coeffs
constraints.Constraint.vars

Modification
------------

``Constraint.update`` is the canonical mutation API. The legacy ``lhs`` /
``sign`` / ``rhs`` / ``coeffs`` / ``vars`` setters still forward to
``update`` but emit a ``DeprecationWarning`` and will be removed in a
future release.

.. autosummary::
:toctree: generated/

constraints.Constraint.update

Post-solve access
-----------------

Expand Down
8 changes: 8 additions & 0 deletions doc/release_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ Most users should keep calling ``model.solve(...)``. If you want more control, y
* Opt in globally via ``Model(freeze_constraints=True)`` or per-call via ``model.add_constraints(..., freeze=True)``.
* Lossless conversion both ways with ``Constraint.freeze()`` / ``CSRConstraint.mutable()``.

*In-place solver updates (persistent re-solve)*

* A built solver can now be re-solved against a mutated ``Model`` without a full rebuild. Construct with ``Solver.from_name(..., track_updates=True)`` and re-call ``solver.solve(model)`` after edits — the diff against the previous build is applied in place when the backend supports it, falling back to a rebuild otherwise. Supported on HiGHS, Gurobi, Xpress, and Mosek (``io_api="direct"``).
* Pass ``disallow_rebuild=True`` to ``solve(model, ...)`` to guarantee an in-place update or raise ``RebuildRequiredError``. Inspect ``solver._last_rebuild_reason`` (a ``RebuildReason``) to understand why a rebuild was triggered.
* New ``linopy.persistent`` module exposes ``ModelSnapshot``, ``ModelDiff``, and ``RebuildReason`` for users who want to introspect or build the diff themselves.

**Performance**

* ~10× faster direct solver communication (``io_api="direct"``), thanks to the new CSR-based matrix construction. Conversion helpers like ``to_highspy`` benefit too.
Expand All @@ -49,6 +55,8 @@ Most users should keep calling ``model.solve(...)``. If you want more control, y
**Deprecations**

* ``Solver.solve_problem``, ``Solver.solve_problem_from_model``, and ``Solver.solve_problem_from_file`` still work but emit a ``DeprecationWarning``. Use ``Solver.from_name(...).solve()`` (or simply ``model.solve(...)``) instead. They will be removed in a future release.
* Mutation via assignment to ``Variable.lower`` / ``Variable.upper`` / ``Constraint.coeffs`` / ``Constraint.vars`` / ``Constraint.lhs`` / ``Constraint.sign`` / ``Constraint.rhs`` is deprecated and emits a ``DeprecationWarning``. Use ``Variable.update(...)`` / ``Constraint.update(...)`` instead — the canonical mutation API with one validation path and one place that flips the persistent-solver dirty flag. Read access to these properties is unchanged. The setters will be removed in a future release.
* Passing a raw ``DataArray`` of integer labels to ``Constraint.vars = ...`` setter is deprecated and emits a ``FutureWarning``. Pass a ``Variable`` to ``Constraint.update()`` instead — it is the supported input. The ``DataArray`` path will be removed in a future release.

**Bug Fixes**

Expand Down
6 changes: 3 additions & 3 deletions examples/creating-constraints.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -348,16 +348,16 @@
"\n",
"`CSRConstraint` deliberately exposes a narrower API than the xarray-backed `Constraint`:\n",
"\n",
"- **No in-place mutation.** Setters such as `con.coeffs = ...`, `con.vars = ...`, `con.sign = ...`, `con.rhs = ...`, and `con.lhs = ...` are only available on `Constraint`.\n",
"- **No in-place mutation.** `Constraint.update(...)` is only available on `Constraint`. (The legacy setters — `con.coeffs = ...`, `con.vars = ...`, `con.sign = ...`, `con.rhs = ...`, `con.lhs = ...` — still forward to `update` on `Constraint` but emit a `DeprecationWarning` and will be removed in a future release.)\n",
"- **No label-based indexing.** `con.loc[...]` is only available on `Constraint`.\n",
"- **Accessing `.coeffs` / `.vars` triggers reconstruction.** On a `CSRConstraint` these properties rebuild the full xarray `Dataset` on demand and emit a `PerformanceWarning`. For solver-oriented workflows prefer `con.to_matrix()` or work with the CSR data directly.\n",
"\n",
"If you need any of the above, call `.mutable()` first to get a `Constraint`:\n",
"\n",
"```python\n",
"con = m.constraints[\"my_constraint\"].mutable()\n",
"con.loc[{\"time\": 0}] # label-based indexing now available\n",
"con.rhs = 5 # mutation now available\n",
"con.loc[{\"time\": 0}] # label-based indexing now available\n",
"con.update(rhs=5) # mutation now available\n",
"```"
]
},
Expand Down
37 changes: 26 additions & 11 deletions examples/manipulating-models.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@
"metadata": {},
"outputs": [],
"source": [
"x.lower = 1"
"x.update(lower=1)"
]
},
{
Expand All @@ -83,7 +83,10 @@
"metadata": {},
"source": [
".. note::\n",
" The same could have been achieved by calling `m.variables.x.lower = 1`\n",
" Assignment via the ``x.lower = 1`` setter still works but is\n",
" deprecated and will be removed in a future release. Use\n",
" ``Variable.update`` instead — it is the canonical mutation API\n",
" with a single validation path.\n",
"\n",
"Let's solve it again!"
]
Expand Down Expand Up @@ -127,7 +130,7 @@
"metadata": {},
"outputs": [],
"source": [
"x.lower = xr.DataArray(range(10, 0, -1), coords=(time,))"
"x.update(lower=xr.DataArray(range(10, 0, -1), coords=(time,)))"
]
},
{
Expand Down Expand Up @@ -157,9 +160,12 @@
"source": [
"## Varying Constraints\n",
"\n",
"A similar functionality is implemented for constraints. Here we can modify the left-hand-side, the sign and the right-hand-side.\n",
"A similar functionality is implemented for constraints. We use\n",
"``Constraint.update`` to change the left-hand-side, the sign,\n",
"and the right-hand-side.\n",
"\n",
"Assume we want to relax the right-hand-side of the first constraint `con1` to `8 * factor`. This would translate to:"
"Assume we want to relax the right-hand-side of the first constraint\n",
"``con1`` to ``8 * factor``. This translates to:"
]
},
{
Expand All @@ -169,7 +175,7 @@
"metadata": {},
"outputs": [],
"source": [
"con1.rhs = 8 * factor"
"con1.update(rhs=8 * factor)"
]
},
{
Expand All @@ -178,7 +184,10 @@
"metadata": {},
"source": [
".. note::\n",
" The same could have been achieved by calling `m.constraints.con1.rhs = 8 * factor`\n",
" Assignment via the ``con1.rhs = 8 * factor`` setter still works\n",
" but is deprecated and will be removed in a future release. Use\n",
" ``Constraint.update`` instead — it is the canonical mutation API\n",
" with a single validation path.\n",
"\n",
"Let's solve it again!"
]
Expand Down Expand Up @@ -212,7 +221,7 @@
"metadata": {},
"outputs": [],
"source": [
"con1.lhs = 3 * x + 8 * y"
"con1.update(lhs=3 * x + 8 * y)"
]
},
{
Expand All @@ -221,9 +230,15 @@
"metadata": {},
"source": [
"**Note:**\n",
"The same could have been achieved by calling \n",
"```python \n",
"m.constraints['con1'].lhs = 3 * x + 8 * y\n",
"Assignment via the ``con1.lhs = 3 * x + 8 * y`` setter still works\n",
"but is deprecated and will be removed in a future release. Use\n",
"``Constraint.update`` instead — it is the canonical mutation API\n",
"with a single validation path.\n",
"\n",
"``Constraint.update`` also accepts a full constraint expression in one call:\n",
"\n",
"```python\n",
"con1.update(3 * x + 8 * y <= 8 * factor) # replaces lhs / sign / rhs at once\n",
"```"
]
},
Expand Down
Loading
Loading