Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions .github/workflows/test-notebooks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ jobs:
echo "Skipping $name (requires credentials or special setup)"
continue
;;
building-models-from-specs.ipynb)
echo "Skipping $name (requires math-spec, not yet on PyPI)"
continue
;;
esac

echo "::group::Running $name"
Expand Down
1 change: 1 addition & 0 deletions benchmarks/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@
qp,
sos,
sparse_network,
spec_pypsa,
storage,
)
55 changes: 55 additions & 0 deletions benchmarks/models/spec_pypsa.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"""
Model built from math-spec's ``pypsa.yaml`` example (requires math-spec).

The subject is :meth:`linopy.Model.from_spec`: lowering a spec of PyPSA's full
statement, binding synthetic data to it and building every variable and
constraint it declares. The example lives outside the wheel, so its directory
comes from ``MATH_SPEC_EXAMPLES`` and the case skips without it. A sweep
value is the number of labels per dimension; 40 of them is about 20k
variables.
"""

from __future__ import annotations

import os
from pathlib import Path
from typing import TYPE_CHECKING

from benchmarks.registry import BUILD, FROM_NETCDF, TO_NETCDF, BenchSpec, register

if TYPE_CHECKING:
import linopy

SIZES = (5, 40)

EXAMPLES = os.environ.get("MATH_SPEC_EXAMPLES")
EXAMPLE = Path(EXAMPLES, "pypsa.yaml") if EXAMPLES else None


def build_spec_pypsa(n: int) -> linopy.Model:
"""Lower ``pypsa.yaml`` and build it with ``n`` labels per dimension."""
import pytest

if EXAMPLE is None or not EXAMPLE.exists():
pytest.skip("set MATH_SPEC_EXAMPLES to a math-spec examples directory")
import math_spec

import linopy
from linopy.spec.testing import synthetic_sources

path = str(EXAMPLE)
sources = synthetic_sources(math_spec.to_program(path), n)
with linopy.options as options:
options["semantics"] = "v1"
return linopy.Model.from_spec(path, sources)


SPEC = register(
BenchSpec(
name="spec_pypsa",
build=build_spec_pypsa,
sweep=SIZES,
phases=frozenset({BUILD, TO_NETCDF, FROM_NETCDF}),
requires=("math_spec",),
)
)
9 changes: 9 additions & 0 deletions doc/release_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ Upcoming Version
* Every operation whose result changes under v1 emits a ``LinopySemanticsWarning`` under legacy, naming the fix — so a model can be migrated incrementally before opting in. The full rules are specified in :doc:`the arithmetic convention <design/convention>`.


*Build a model from a math-spec program*

* ``Model.from_spec`` / ``model.add_spec`` build a model from a `math-spec <https://pypi.org/project/math-spec/>`__ YAML program bound to data, and ``model.spec`` reads it back. Requires the ``math-spec`` package and v1 semantics.

* ``model.spec.expressions[name]`` returns a ``NamedExpression`` with three views of a named expression: ``.node`` (the lowered formula), ``.expression`` (the unsolved linopy expression — a ``LinearExpression``, bare ``Variable``, array or scalar) and ``.solution`` (the expression folded over the solved model). ``model.spec.evaluate(name, sources)`` returns the same object with its parameters bound afresh.

* ``model.spec.to_latex`` / ``.to_markdown`` / ``.to_typst`` typeset the whole model; a ``ModelSpec`` and a ``NamedExpression`` render as Markdown in a notebook.


*Numerical scaling*

* Variables, constraints and the objective accept a ``scaling`` factor that rewrites the problem into better-behaved units for the solver, without changing the answer. Variable scaling is column-like, constraint and objective scaling are row-like, and primal values, duals and the objective are transformed back to the original units after solving. See the :doc:`numerical-scaling` tutorial and the *Numerical scaling* section of the :doc:`user-guide`.
Expand Down
Loading
Loading