You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Brings in the coords-as-truth stack (#732), the alignment.py module split
(#742), has_terms (#743), and the MatrixAccessor caching (#716).
Conflict resolutions (consistent rule: keep this branch's v1/legacy
dispatch structure, use master's conversion calls inside it):
- expressions.py: _add_constant, _apply_constant_op_{v1,legacy}, and
to_constraint use broadcast_to_coords(..., strict=False) instead of
as_dataarray; SUPPORTED_CONSTANT_TYPES -> CONSTANT_TYPES.
- variables.py: to_linexpr converts via broadcast_to_coords(strict=False),
then applies the v1/legacy absence handling.
- __init__.py: align from linopy.alignment + LinopySemanticsWarning export.
Test adaptations for post-#732 APIs and semantics:
- test_legacy_violations: name the MultiIndex coords entry (required
since #732).
- test_linear_expression: the masked-addend tails of test_nterm and
test_variable_names pin legacy absence behavior; split into
@pytest.mark.legacy / @pytest.mark.v1 pairs (section 6 divergence).
Full suite under both semantics: 6446 passed, 514 skipped.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: doc/release_notes.rst
+12Lines changed: 12 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -4,6 +4,9 @@ Release Notes
4
4
Upcoming Version
5
5
----------------
6
6
7
+
* Add documentation about `LinearExpression.where` with `drop=True`. Add `BaseExpression.variable_names` property.
8
+
* Add ``BaseExpression.has_terms`` property: boolean array, true at slots with at least one live term (`#741 <https://github.com/PyPSA/linopy/issues/741>`_).
9
+
7
10
**Features**
8
11
9
12
*Inspect the solver after solving*
@@ -49,20 +52,29 @@ Most users should keep calling ``model.solve(...)``. If you want more control, y
49
52
**Deprecations**
50
53
51
54
* ``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.
55
+
* **Implicit MultiIndex-level projection is deprecated.** Passing an input indexed by a *level* of a stacked-``MultiIndex`` dimension (e.g. per-``period`` bounds onto a ``(period, timestep)`` ``snapshot`` index) emits an ``EvolvingAPIWarning`` — in arithmetic and in ``add_variables`` / ``add_constraints`` — and will raise under the upcoming v1 convention. Project the input onto the dimension explicitly (select with the dimension's level values) to keep current behavior. Affects PyPSA multi-investment models. See Bug Fixes below for details.
52
56
53
57
**Bug Fixes**
54
58
59
+
* ``add_variables`` / ``add_constraints``: extends 0.7.0's coords-as-truth rule to ``lower``, ``upper`` and ``mask`` for every bound type and dim order. Pandas ``Series`` / ``DataFrame`` bounds or masks missing a dimension are broadcast to ``coords`` instead of being silently dropped (`#709 <https://github.com/PyPSA/linopy/issues/709>`__); the variable's dimension order always follows ``coords`` (`#706 <https://github.com/PyPSA/linopy/issues/706>`__); bare-tuple coord entries (``coords=[(0, 1, 2)]``) now behave like lists. Mismatched values or extra dims raise ``ValueError`` with a labelled message; sparse-coord masks (formerly a v0.6.3 ``FutureWarning``, #580) raise ``ValueError``, and masks with dims not in the data raise ``ValueError`` instead of ``AssertionError``.
60
+
* Pandas inputs whose index names *levels* of a stacked-``MultiIndex`` ``coords`` dimension are now projected onto that dimension: a level subset broadcasts across the others, the full set aligns element-wise. This fixes PyPSA multi-investment arithmetic (e.g. an expression over a ``(period, timestep)`` ``snapshot`` MultiIndex times a ``period``-indexed weighting). In ``add_variables`` / ``add_constraints`` the input must provide a value for every level combination of the MultiIndex or a ``ValueError`` is raised (the error lists the missing combinations). **Implicit level projections are deprecated**: they emit an ``EvolvingAPIWarning`` everywhere — in arithmetic *and* in ``add_variables`` / ``add_constraints`` — and will raise under the upcoming v1 convention. Project the input onto the dimension explicitly (select with the dimension's level values) to keep current behavior. Aligning the full level set with full coverage stays silent. Strict validation also rejects a ``MultiIndex`` input with *unnamed* levels whose combinations don't match ``coords`` (previously a silent bypass, as such inputs can't be projected by level name).
61
+
* ``add_piecewise_formulation`` now produces a reproducible dimension order in the broadcast breakpoint array. The previous set-based expansion gave a hash-randomized order that varied between processes.
55
62
* SOS constraints on masked variables no longer cause solver-specific failures (Gurobi ``IndexError``, Xpress ``?404 Invalid column number``, LP parse errors, silent set corruption). ``Model.solve()`` and ``Model.to_file()`` now raise a clear ``NotImplementedError`` referring users to `#688 <https://github.com/PyPSA/linopy/issues/688>`__; pass ``reformulate_sos=True`` as a workaround.
56
63
* ``Model.solve(..., reformulate_sos=True)`` now actually reformulates SOS constraints even when the solver supports them natively. Previously it was silently ignored with a warning.
64
+
* Fix Mosek interface to inspect both the basic and IPM solutions and pick the one with the better status, so that an optimal crossover solution is not discarded when IPM terminates with a (near-)Farkas certificate.
57
65
58
66
**Breaking Changes**
59
67
68
+
* ``add_variables`` / ``add_constraints``: the v0.6.3 ``mask`` deprecations (#580) are now hard ``ValueError``\ s; an unnamed ``pd.MultiIndex`` in sequence-form ``coords`` raises ``TypeError`` unless paired with ``dims=[i]``. See Bug Fixes above.
69
+
* Sequence-form ``coords`` entries can no longer be ``xarray.DataArray`` objects — they raise ``TypeError``. Pass the underlying index instead: ``variable.indexes[dim]`` (a ``pd.Index``).
60
70
* ``available_solvers`` now lists all *installed* solvers, even ones without a working license. If you used it to decide "can I actually solve with X?", switch to ``linopy.licensed_solvers`` or ``SolverClass.license_status()``.
61
71
* ``Model.solver_model`` and ``Model.solver_name`` are now read-only properties that delegate to ``model.solver``. You can't reassign them (only ``= None`` is allowed, which closes the solver), and ``solver_name`` is ``None`` before the first solve.
62
72
* ``result.solution.primal`` and ``result.solution.dual`` are now ``numpy`` arrays indexed by linopy's integer labels (with ``NaN`` for slots without a value), instead of pandas Series keyed by variable/constraint name. If you accessed them by name, use ``model.variables[name].solution`` (or ``model.constraints[name].dual``) instead.
73
+
* Drop Python 3.10 support. Minimum supported version is now Python 3.11.
63
74
64
75
**Internal**
65
76
77
+
* New module ``linopy.alignment`` owns conversion, broadcasting, and alignment of user input against coordinates (moved out of ``linopy.common``): ``as_dataarray`` (convert only), ``broadcast_to_coords`` (convert and broadcast against ``coords``; ``strict=True`` by default raises on any mismatch, naming ``label`` in the error), ``validate_alignment``, and ``align``.
66
78
* Each ``Solver`` subclass now overrides at most three hooks: ``_build_direct`` (build the native model), ``_run_direct`` (run it), and ``_run_file`` (run the solver on an LP/MPS file). File-only solvers (CBC, GLPK, CPLEX, SCIP, Knitro, COPT, MindOpt) only override ``_run_file``.
67
79
* New ``ConstraintLabelIndex`` cached on ``Model.constraints`` (mirrors the existing ``Variables.label_index``); ``ConstraintBase`` gains ``active_labels()`` and a ``range`` property; ``CSRConstraint`` exposes ``coords``.
68
80
* ``linopy.common`` gains ``values_to_lookup_array``; the legacy pandas-based helpers ``series_to_lookup_array`` and ``lookup_vals`` are removed.
"In this example you can see that many of the elements of the LinearExpression are None. If you want to remove all the None terms, you can use `.where(.., drop=True)`"
351
+
]
352
+
},
353
+
{
354
+
"cell_type": "code",
355
+
"execution_count": null,
356
+
"id": "32",
357
+
"metadata": {},
358
+
"outputs": [],
359
+
"source": [
360
+
"z = z.where(mask_b, drop=True)\n",
361
+
"z"
362
+
]
363
+
},
364
+
{
365
+
"cell_type": "markdown",
366
+
"id": "33",
367
+
"metadata": {},
368
+
"source": [
369
+
"That looks nicer!<br>"
370
+
]
371
+
},
372
+
{
373
+
"cell_type": "markdown",
374
+
"id": "34",
375
+
"metadata": {},
376
+
"source": [
377
+
"You may notice that the variable `x` is not used at all. The expression still contains two terms (one of them is unused) but it only has one variable `y`"
378
+
]
379
+
},
380
+
{
381
+
"cell_type": "code",
382
+
"execution_count": null,
383
+
"id": "35",
384
+
"metadata": {},
385
+
"outputs": [],
386
+
"source": [
387
+
"z.nterm"
388
+
]
389
+
},
390
+
{
391
+
"cell_type": "code",
392
+
"execution_count": null,
393
+
"id": "36",
394
+
"metadata": {},
395
+
"outputs": [],
396
+
"source": [
397
+
"z.variable_names"
398
+
]
399
+
},
400
+
{
401
+
"cell_type": "markdown",
402
+
"id": "37",
403
+
"metadata": {},
404
+
"source": [
405
+
"You can get rid of the unused term with `.simplify()`"
406
+
]
407
+
},
408
+
{
409
+
"cell_type": "code",
410
+
"execution_count": null,
411
+
"id": "38",
412
+
"metadata": {},
413
+
"outputs": [],
414
+
"source": [
415
+
"z = z.simplify()\n",
416
+
"z.nterm"
417
+
]
418
+
},
419
+
{
420
+
"attachments": {},
421
+
"cell_type": "markdown",
422
+
"id": "39",
423
+
"metadata": {},
328
424
"source": [
329
425
"## Using `.shift` to shift the Variable along one dimension\n",
330
426
"\n",
@@ -336,7 +432,7 @@
336
432
{
337
433
"cell_type": "code",
338
434
"execution_count": null,
339
-
"id": "30",
435
+
"id": "40",
340
436
"metadata": {},
341
437
"outputs": [],
342
438
"source": [
@@ -346,7 +442,7 @@
346
442
{
347
443
"attachments": {},
348
444
"cell_type": "markdown",
349
-
"id": "31",
445
+
"id": "41",
350
446
"metadata": {},
351
447
"source": [
352
448
"## Using `.groupby` to group by a key and apply operations on the groups\n",
@@ -359,7 +455,7 @@
359
455
{
360
456
"cell_type": "code",
361
457
"execution_count": null,
362
-
"id": "32",
458
+
"id": "42",
363
459
"metadata": {},
364
460
"outputs": [],
365
461
"source": [
@@ -370,7 +466,7 @@
370
466
{
371
467
"attachments": {},
372
468
"cell_type": "markdown",
373
-
"id": "33",
469
+
"id": "43",
374
470
"metadata": {},
375
471
"source": [
376
472
"## Using `.rolling` to perform a rolling operation\n",
0 commit comments