From bc7754248fd7c62fbcb549109320224fe6137c95 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 9 Sep 2026 13:57:58 +0000 Subject: [PATCH] feat(language): a variable, a constraint and a cased expression declare their shape as dims, as a parameter does Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01QrrLMWgN7VSZ4Xxf27t9pz --- README.md | 8 +- docs/about/limits.md | 4 +- docs/examples/commitment.md | 14 +- docs/examples/dispatch.md | 6 +- docs/examples/operators.md | 68 ++--- docs/examples/pypsa.md | 214 +++++++-------- docs/examples/pypsa_linearized_uc.md | 46 ++-- docs/examples/pypsa_losses.md | 30 +-- docs/examples/pypsa_multi_period.md | 20 +- docs/examples/pypsa_quadratic.md | 10 +- docs/examples/pypsa_stochastic.md | 22 +- docs/howto/regimes.md | 16 +- docs/reference/language/absence.md | 20 +- docs/reference/language/declarations.md | 24 +- docs/reference/language/dimensions.md | 2 +- docs/reference/language/errors.md | 2 +- docs/reference/language/expressions.md | 31 +-- docs/reference/language/index.md | 28 +- docs/reference/language/operators.md | 32 +-- docs/reference/language/piecewise.md | 8 +- docs/reference/language/reading.md | 8 +- docs/reference/language/reported.md | 6 +- docs/reference/notation.md | 84 +++--- examples/commitment.yaml | 14 +- examples/dispatch.yaml | 4 +- examples/operators/at.yaml | 4 +- examples/operators/dual.yaml | 4 +- examples/operators/shift.yaml | 4 +- examples/operators/shift_by_parameter.yaml | 4 +- examples/operators/shift_edge.yaml | 4 +- examples/operators/shift_partitioned.yaml | 4 +- examples/operators/shift_wrap.yaml | 4 +- examples/operators/sum.yaml | 4 +- examples/operators/sum_all.yaml | 4 +- examples/operators/sum_back.yaml | 6 +- examples/operators/sum_back_by_parameter.yaml | 6 +- examples/operators/sum_back_partitioned.yaml | 6 +- examples/operators/sum_back_wrap.yaml | 6 +- examples/operators/sum_by.yaml | 4 +- examples/operators/sum_by_lookups.yaml | 4 +- examples/piecewise.yaml | 6 +- examples/piecewise_lp.yaml | 6 +- examples/ports/transport_pwl.yaml | 8 +- examples/pypsa.yaml | 250 +++++++++--------- examples/pypsa_linearized_uc.yaml | 56 ++-- examples/pypsa_losses.yaml | 40 +-- examples/pypsa_multi_period.yaml | 26 +- examples/pypsa_quadratic.yaml | 14 +- examples/pypsa_stochastic.yaml | 34 +-- examples/sos.yaml | 6 +- schema/math-spec.schema.json | 58 ++-- src/math_spec/dimensions.py | 30 +-- src/math_spec/lowering.py | 4 +- src/math_spec/model.py | 36 +-- src/math_spec/piecewise.py | 8 +- src/math_spec/program.py | 6 +- src/math_spec/resolution.py | 4 +- src/math_spec/typesetting/walk.py | 24 +- tests/fixtures.py | 6 +- tests/fixtures/every_program_node.yaml | 20 +- tests/test_advice.py | 6 +- tests/test_boundedness.py | 6 +- tests/test_degree.py | 6 +- tests/test_dimensions.py | 30 +-- tests/test_exclusivity.py | 8 +- tests/test_expansion.py | 2 +- tests/test_lowering.py | 28 +- tests/test_piecewise.py | 20 +- tests/test_separability.py | 28 +- tests/test_validation.py | 116 ++++---- tests/test_yaml_loading.py | 16 +- tests/typesetting/golden/model.yaml | 84 +++--- tests/typesetting/test_cases.py | 8 +- tests/typesetting/test_cli.py | 2 +- tests/typesetting/test_declaration.py | 6 +- tests/typesetting/test_golden.py | 4 +- tests/typesetting/test_walk.py | 46 ++-- 77 files changed, 923 insertions(+), 924 deletions(-) diff --git a/README.md b/README.md index 7a887fec..8fc64850 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ is the [test](docs/about/what-counts-as-language.md) for what belongs here. Three properties follow: - **Nothing is guessed.** A misspelled name, a `where` string on an undeclared - parameter, a constraint whose dimensions do not match its `foreach`: each fails + parameter, a constraint whose dimensions do not match its `dims`: each fails when the file loads, with a message that names the fix. A repository of models checks in CI with no data ([errors](docs/reference/language/errors.md)). - **The operators are a fixed set.** `sum`, `sum_back`, `at` and `shift`. A file @@ -86,13 +86,13 @@ parameters: variables: p: description: output of a generator in a snapshot - foreach: [snapshot, generator] + dims: [snapshot, generator] where: "p_max > 0" bounds: { lower: 0, upper: p_max } constraints: power_balance: - foreach: [snapshot] + dims: [snapshot] expression: sum(p, over=generator) == load objective: @@ -208,7 +208,7 @@ git reference until then; see [RELEASING.md](RELEASING.md). Every file under `src/` was written in [lpspec](https://github.com/fluxopt/lpspec) and extracted here, so that the language and the syntax tree a tool reads it through are a dependency rather than one engine's internals. The keys themselves, -which are YAML math, a block per component, `foreach:` and a `where:` string, +which are YAML math, a block per component, `dims:` and a `where:` string, come from [Calliope](https://github.com/calliope-project/calliope). [linopy](https://github.com/PyPSA/linopy) supplies the vocabulary that `sum(over=)` and the dimension rules are named against. Issue numbers in these diff --git a/docs/about/limits.md b/docs/about/limits.md index 3604f7fe..59ecbe90 100644 --- a/docs/about/limits.md +++ b/docs/about/limits.md @@ -81,7 +81,7 @@ the same model written out by hand. | **this project puts the work elsewhere** | data preparation such as resampling; helpers for one domain; Python that decides which declarations exist | it could; this project does not want it to | Three things never appear inside one model: an `if`, a loop, and a set of -declarations that depends on the data. `foreach: [snapshot]` does not know how +declarations that depends on the data. `dims: [snapshot]` does not know how many snapshots there are, and it does not need to. A dimension computed before the model loads is fine: a cycle basis for Kirchhoff's voltage law is a graph algorithm run in data preparation, and its result arrives as a parameter. What no @@ -155,7 +155,7 @@ That another tool has a feature is not by itself a reason to add it. | A vocabulary for tracked metrics: `impacts:`, `effects:`, a `costs` axis | a named expression already does this | an `impact` dimension and one named expression. Cap it with a constraint, whose dual is the shadow price; weight it in the objective; read it back after the solve ([#124](https://github.com/fluxopt/lpspec/issues/124)) | | `**` with a variable in the base or the exponent | the exponent would decide the degree, and `to_spec` reads no data. `p ** n` is linear at `n = 1`, quadratic at `n = 2`, and refused at `n = 3` | `x * x` for a square. `**` over parameters and numbers is allowed ([#1175](https://github.com/fluxopt/lpspec/issues/1175)) | | Normalisation, `x / sum(x)` | dividing by a variable is not a polynomial, and no solver takes it | write the ratio as a constraint, or fix the denominator | -| An `if`, a loop, or declarations that depend on the data | `to_spec` could no longer read the file without the data | `where:` masks and `foreach:` dimensions. A tool may loop over models | +| An `if`, a loop, or declarations that depend on the data | `to_spec` could no longer read the file without the data | `where:` masks and `dims:` dimensions. A tool may loop over models | | A Python API for building models | the model is the file you review and diff | YAML, or a `dict` with the same keys ([below](#composition-component-libraries)) | ## Composition (component libraries) diff --git a/docs/examples/commitment.md b/docs/examples/commitment.md index 797c081f..5906e2a4 100644 --- a/docs/examples/commitment.md +++ b/docs/examples/commitment.md @@ -42,17 +42,17 @@ parameters: variables: p: description: output of a generator in a snapshot - foreach: [snapshot, generator] + dims: [snapshot, generator] bounds: { lower: 0, upper: p_max } status: description: whether the unit is running in a snapshot - foreach: [snapshot, generator] + dims: [snapshot, generator] domain: binary expressions: previous_status: description: the commitment state a unit carries into a snapshot - foreach: [snapshot, generator] + dims: [snapshot, generator] cases: always_on: when: "not committable" @@ -64,21 +64,21 @@ expressions: constraints: power_balance: - foreach: [snapshot] + dims: [snapshot] expression: sum(p, over=generator) == load upper: description: a unit that is not running produces nothing - foreach: [snapshot, generator] + dims: [snapshot, generator] expression: p <= status * p_max lower: description: and one that is running produces at least its floor - foreach: [snapshot, generator] + dims: [snapshot, generator] expression: p >= status * p_min ramp_up: description: >- one inequality for both regimes — a unit already running is held to `ramp_limit`, a unit starting up to `start_up_limit`. - foreach: [snapshot, generator] + dims: [snapshot, generator] expression: >- p - shift(p, over=snapshot, offset=1, edge=0) <= ramp_limit * previous_status + start_up_limit * (1 - previous_status) diff --git a/docs/examples/dispatch.md b/docs/examples/dispatch.md index 80172f79..0ba0f360 100644 --- a/docs/examples/dispatch.md +++ b/docs/examples/dispatch.md @@ -13,7 +13,7 @@ varies when it needs a base to change one thing in. The `where:` on `p` deletes the rows where a generator has no capacity, so [absence](../reference/language/absence.md) is declared in the file rather than checked at run time. `sum(p, over=generator)` names the dimension it reduces, so -the constraint's `foreach` is what remains. +the constraint's `dims` is what remains. ```yaml @@ -31,13 +31,13 @@ parameters: variables: p: description: output of a generator in a snapshot - foreach: [snapshot, generator] + dims: [snapshot, generator] where: "p_max > 0" bounds: { lower: 0, upper: p_max } constraints: power_balance: - foreach: [snapshot] + dims: [snapshot] expression: sum(p, over=generator) == load objective: diff --git a/docs/examples/operators.md b/docs/examples/operators.md index e24c6ca8..29b5abe5 100644 --- a/docs/examples/operators.md +++ b/docs/examples/operators.md @@ -31,12 +31,12 @@ parameters: variables: p: - foreach: [snapshot, generator] + dims: [snapshot, generator] bounds: { lower: 0 } constraints: fleet_budget: - foreach: [] + dims: [] expression: sum(p) <= budget objective: { sense: minimize, expression: sum(p) } @@ -60,12 +60,12 @@ parameters: variables: p: - foreach: [snapshot, generator] + dims: [snapshot, generator] bounds: { lower: 0 } constraints: fleet_total: - foreach: [snapshot] + dims: [snapshot] expression: sum(p, over=generator) <= limit objective: { sense: minimize, expression: sum(p) } @@ -96,12 +96,12 @@ parameters: variables: p: - foreach: [snapshot, generator] + dims: [snapshot, generator] bounds: { lower: 0 } constraints: bus_total: - foreach: [snapshot, bus] + dims: [snapshot, bus] expression: sum(p, by=gen_bus) <= limit objective: { sense: minimize, expression: sum(p) } @@ -134,12 +134,12 @@ parameters: variables: p: - foreach: [snapshot, generator] + dims: [snapshot, generator] bounds: { lower: 0 } constraints: bus_technology_total: - foreach: [snapshot, bus, technology] + dims: [snapshot, bus, technology] expression: sum(p, by=[gen_bus, gen_tech]) <= limit objective: { sense: minimize, expression: sum(p) } @@ -168,12 +168,12 @@ parameters: variables: p: - foreach: [snapshot] + dims: [snapshot] bounds: { lower: 0 } constraints: within_cap: - foreach: [snapshot] + dims: [snapshot] expression: p <= at(cap, by=period_of) objective: { sense: minimize, expression: sum(p) } @@ -195,12 +195,12 @@ dimensions: variables: p: - foreach: [snapshot] + dims: [snapshot] bounds: { lower: 0 } constraints: no_faster_than_before: - foreach: [snapshot] + dims: [snapshot] expression: p <= shift(p, over=snapshot, offset=1) objective: { sense: minimize, expression: sum(p) } @@ -222,12 +222,12 @@ dimensions: variables: p: - foreach: [snapshot] + dims: [snapshot] bounds: { lower: 0 } constraints: no_faster_than_before: - foreach: [snapshot] + dims: [snapshot] expression: p <= shift(p, over=snapshot, offset=1, edge='wrap') objective: { sense: minimize, expression: sum(p) } @@ -249,12 +249,12 @@ dimensions: variables: p: - foreach: [snapshot] + dims: [snapshot] bounds: { lower: 0 } constraints: no_faster_than_before: - foreach: [snapshot] + dims: [snapshot] expression: p <= shift(p, over=snapshot, offset=1, edge=0) objective: { sense: minimize, expression: sum(p) } @@ -282,12 +282,12 @@ parameters: variables: order: - foreach: [technology, month] + dims: [technology, month] bounds: { lower: 0 } constraints: arrives_after_its_lead: - foreach: [technology, month] + dims: [technology, month] expression: shift(order, over=month, offset=lead, edge=0) >= demand objective: { sense: minimize, expression: sum(order) } @@ -313,12 +313,12 @@ lookups: variables: p: - foreach: [snapshot] + dims: [snapshot] bounds: { lower: 0 } constraints: no_faster_than_before_in_season: - foreach: [snapshot] + dims: [snapshot] expression: p <= shift(p, over=snapshot, offset=1, edge='wrap', by=season_of) objective: { sense: minimize, expression: sum(p) } @@ -344,15 +344,15 @@ parameters: variables: started: - foreach: [unit, hour] + dims: [unit, hour] domain: binary on: - foreach: [unit, hour] + dims: [unit, hour] domain: binary constraints: stays_up_its_own_time: - foreach: [unit, hour] + dims: [unit, hour] expression: sum_back(started, over=hour, within=3) <= on objective: { sense: minimize, expression: sum(on) } @@ -378,15 +378,15 @@ parameters: variables: started: - foreach: [unit, hour] + dims: [unit, hour] domain: binary on: - foreach: [unit, hour] + dims: [unit, hour] domain: binary constraints: stays_up_its_own_time: - foreach: [unit, hour] + dims: [unit, hour] expression: sum_back(started, over=hour, within=min_up) <= on objective: { sense: minimize, expression: sum(on) } @@ -412,15 +412,15 @@ parameters: variables: started: - foreach: [unit, hour] + dims: [unit, hour] domain: binary on: - foreach: [unit, hour] + dims: [unit, hour] domain: binary constraints: stays_up_its_own_time: - foreach: [unit, hour] + dims: [unit, hour] expression: sum_back(started, over=hour, within=min_up, edge='wrap') <= on objective: { sense: minimize, expression: sum(on) } @@ -448,15 +448,15 @@ lookups: variables: started: - foreach: [unit, hour] + dims: [unit, hour] domain: binary on: - foreach: [unit, hour] + dims: [unit, hour] domain: binary constraints: stays_up_inside_its_day: - foreach: [unit, hour] + dims: [unit, hour] expression: sum_back(started, over=hour, within=3, by=day_of) <= on objective: { sense: minimize, expression: sum(on) } @@ -479,12 +479,12 @@ parameters: variables: p: - foreach: [snapshot] + dims: [snapshot] bounds: { lower: 0 } constraints: balance: - foreach: [snapshot] + dims: [snapshot] expression: p >= load expressions: diff --git a/docs/examples/pypsa.md b/docs/examples/pypsa.md index a4f10697..2c4a6d93 100644 --- a/docs/examples/pypsa.md +++ b/docs/examples/pypsa.md @@ -1451,7 +1451,7 @@ objective: ```yaml Generator_fix_p_lower: description: "`Generator-fix-p-lower` — a fixed generator outputs at least its minimum" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: not Generator_p_nom_extendable AND not Generator_committable expression: Generator_p >= Generator_p_min_pu * Generator_p_nom ``` @@ -1467,7 +1467,7 @@ p_{t,g} \ge \underline{\mathrm{p}}_{t,g} \cdot \mathrm{p}^{\mathrm{nom}}_{g} \qq ```yaml Generator_fix_p_upper: description: "`Generator-fix-p-upper` — a fixed generator outputs at most what is available" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: not Generator_p_nom_extendable AND not Generator_committable expression: Generator_p <= Generator_p_max_pu * Generator_p_nom ``` @@ -1483,7 +1483,7 @@ p_{t,g} \le \overline{\mathrm{p}}_{t,g} \cdot \mathrm{p}^{\mathrm{nom}}_{g} \qqu ```yaml Link_fix_p_lower: description: "`Link-fix-p-lower` — a fixed link carries at least its minimum, negative for the other way" - foreach: [snapshot, link] + dims: [snapshot, link] where: not Link_p_nom_extendable expression: Link_p >= Link_p_min_pu * Link_p_nom ``` @@ -1499,7 +1499,7 @@ f_{t,l} \ge \underline{\mathrm{f}}_{t,l} \cdot \mathrm{f}^{\mathrm{nom}}_{l} \qq ```yaml Link_fix_p_upper: description: "`Link-fix-p-upper` — a fixed link carries at most its nominal power" - foreach: [snapshot, link] + dims: [snapshot, link] where: not Link_p_nom_extendable expression: Link_p <= Link_p_max_pu * Link_p_nom ``` @@ -1515,7 +1515,7 @@ f_{t,l} \le \overline{\mathrm{f}}_{t,l} \cdot \mathrm{f}^{\mathrm{nom}}_{l} \qqu ```yaml Generator_ext_p_lower: description: "`Generator-ext-p-lower` — an extendable generator outputs at least its minimum of the chosen build" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_p_nom_extendable AND not Generator_committable expression: Generator_p >= Generator_p_min_pu * Generator_p_nom_ext ``` @@ -1531,7 +1531,7 @@ p_{t,g} \ge \underline{\mathrm{p}}_{t,g} \cdot P_{g} \qquad \forall\, t \in \mat ```yaml Generator_ext_p_upper: description: "`Generator-ext-p-upper` — an extendable generator outputs at most what is available of the chosen build" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_p_nom_extendable AND not Generator_committable expression: Generator_p <= Generator_p_max_pu * Generator_p_nom_ext ``` @@ -1547,7 +1547,7 @@ p_{t,g} \le \overline{\mathrm{p}}_{t,g} \cdot P_{g} \qquad \forall\, t \in \math ```yaml Generator_ext_p_nom_lower: description: "`Generator-ext-p_nom-lower` — the chosen build is at least its floor" - foreach: [generator] + dims: [generator] where: Generator_p_nom_extendable expression: Generator_p_nom_ext >= Generator_p_nom_min ``` @@ -1563,7 +1563,7 @@ P_{g} \ge \underline{\mathrm{p}}^{\mathrm{nom}}_{g} \qquad \forall\, g \in \math ```yaml Generator_ext_p_nom_upper: description: "`Generator-ext-p_nom-upper` — the chosen build is at most its cap; a cap of infinity is no row" - foreach: [generator] + dims: [generator] where: Generator_p_nom_extendable AND Generator_p_nom_max expression: Generator_p_nom_ext <= Generator_p_nom_max ``` @@ -1579,7 +1579,7 @@ P_{g} \le \overline{\mathrm{p}}^{\mathrm{nom}}_{g} \qquad \forall\, g \in \mathc ```yaml Generator_p_nom_set: description: "`Generator-p_nom_set` — the chosen build pinned, wherever a value is given" - foreach: [generator] + dims: [generator] where: Generator_p_nom_extendable AND Generator_p_nom_set expression: Generator_p_nom_ext == Generator_p_nom_set ``` @@ -1595,7 +1595,7 @@ P_{g} = \mathrm{p}^{\mathrm{nom,set}}_{g} \qquad \forall\, g \in \mathcal{G} \,: ```yaml Generator_e_sum_min: description: "`Generator-e_sum_min` — energy over the horizon is at least its floor; a floor of minus infinity is no row" - foreach: [generator] + dims: [generator] where: Generator_e_sum_min expression: sum(Generator_p * snapshot_weightings_generators, over=snapshot) >= Generator_e_sum_min ``` @@ -1611,7 +1611,7 @@ Generator_e_sum_min: ```yaml Generator_e_sum_max: description: "`Generator-e_sum_max` — energy over the horizon is at most its budget; a budget of infinity is no row" - foreach: [generator] + dims: [generator] where: Generator_e_sum_max expression: sum(Generator_p * snapshot_weightings_generators, over=snapshot) <= Generator_e_sum_max ``` @@ -1627,7 +1627,7 @@ Generator_e_sum_max: ```yaml Link_ext_p_lower: description: "`Link-ext-p-lower` — an extendable link carries at least its minimum of the chosen build, negative for the other way" - foreach: [snapshot, link] + dims: [snapshot, link] where: Link_p_nom_extendable expression: Link_p >= Link_p_min_pu * Link_p_nom_ext ``` @@ -1643,7 +1643,7 @@ f_{t,l} \ge \underline{\mathrm{f}}_{t,l} \cdot F_{l} \qquad \forall\, t \in \mat ```yaml Link_ext_p_upper: description: "`Link-ext-p-upper` — an extendable link carries at most the chosen build" - foreach: [snapshot, link] + dims: [snapshot, link] where: Link_p_nom_extendable expression: Link_p <= Link_p_max_pu * Link_p_nom_ext ``` @@ -1659,7 +1659,7 @@ f_{t,l} \le \overline{\mathrm{f}}_{t,l} \cdot F_{l} \qquad \forall\, t \in \math ```yaml Link_ext_p_nom_lower: description: "`Link-ext-p_nom-lower` — the chosen build is at least its floor" - foreach: [link] + dims: [link] where: Link_p_nom_extendable expression: Link_p_nom_ext >= Link_p_nom_min ``` @@ -1675,7 +1675,7 @@ F_{l} \ge \underline{\mathrm{f}}^{\mathrm{nom}}_{l} \qquad \forall\, l \in \math ```yaml Link_ext_p_nom_upper: description: "`Link-ext-p_nom-upper` — the chosen build is at most its cap; a cap of infinity is no row" - foreach: [link] + dims: [link] where: Link_p_nom_extendable AND Link_p_nom_max expression: Link_p_nom_ext <= Link_p_nom_max ``` @@ -1691,7 +1691,7 @@ F_{l} \le \overline{\mathrm{f}}^{\mathrm{nom}}_{l} \qquad \forall\, l \in \mathc ```yaml Link_p_nom_set: description: "`Link-p_nom_set` — the chosen build pinned, wherever a value is given" - foreach: [link] + dims: [link] where: Link_p_nom_extendable AND Link_p_nom_set expression: Link_p_nom_ext == Link_p_nom_set ``` @@ -1707,7 +1707,7 @@ F_{l} = \mathrm{f}^{\mathrm{nom,set}}_{l} \qquad \forall\, l \in \mathcal{L} \,: ```yaml StorageUnit_fix_p_dispatch_lower: description: "`StorageUnit-fix-p_dispatch-lower` — dispatch is non-negative" - foreach: [snapshot, storage_unit] + dims: [snapshot, storage_unit] where: not StorageUnit_p_nom_extendable expression: StorageUnit_p_dispatch >= 0 ``` @@ -1723,7 +1723,7 @@ h^{+}_{t,s} \ge 0 \qquad \forall\, t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \ ```yaml StorageUnit_fix_p_dispatch_upper: description: "`StorageUnit-fix-p_dispatch-upper` — a fixed unit dispatches at most its nominal power" - foreach: [snapshot, storage_unit] + dims: [snapshot, storage_unit] where: not StorageUnit_p_nom_extendable expression: StorageUnit_p_dispatch <= StorageUnit_p_max_pu * StorageUnit_p_nom ``` @@ -1739,7 +1739,7 @@ h^{+}_{t,s} \le \overline{\mathrm{h}}_{t,s} \cdot \mathrm{h}^{\mathrm{nom}}_{s} ```yaml StorageUnit_fix_p_store_lower: description: "`StorageUnit-fix-p_store-lower` — storing is non-negative" - foreach: [snapshot, storage_unit] + dims: [snapshot, storage_unit] where: not StorageUnit_p_nom_extendable expression: StorageUnit_p_store >= 0 ``` @@ -1757,7 +1757,7 @@ StorageUnit_fix_p_store_upper: description: >- `StorageUnit-fix-p_store-upper` — a fixed unit stores at most its nominal power, the minimum-per-unit column carrying that cap negated - foreach: [snapshot, storage_unit] + dims: [snapshot, storage_unit] where: not StorageUnit_p_nom_extendable expression: StorageUnit_p_store <= -StorageUnit_p_min_pu * StorageUnit_p_nom ``` @@ -1773,7 +1773,7 @@ h^{-}_{t,s} \le -\underline{\mathrm{h}}_{t,s} \cdot \mathrm{h}^{\mathrm{nom}}_{s ```yaml StorageUnit_fix_state_of_charge_lower: description: "`StorageUnit-fix-state_of_charge-lower` — charge is non-negative" - foreach: [snapshot, storage_unit] + dims: [snapshot, storage_unit] where: not StorageUnit_p_nom_extendable expression: StorageUnit_state_of_charge >= 0 ``` @@ -1789,7 +1789,7 @@ StorageUnit_fix_state_of_charge_lower: ```yaml StorageUnit_fix_state_of_charge_upper: description: "`StorageUnit-fix-state_of_charge-upper` — a fixed unit holds at most its hours at nominal power" - foreach: [snapshot, storage_unit] + dims: [snapshot, storage_unit] where: not StorageUnit_p_nom_extendable expression: StorageUnit_state_of_charge <= StorageUnit_max_hours * StorageUnit_p_nom ``` @@ -1805,7 +1805,7 @@ StorageUnit_fix_state_of_charge_upper: ```yaml Generator_com_p_lower: description: "`Generator-com-p-lower` — a committed unit outputs at least its minimum; off, at least nothing" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable AND not Generator_p_nom_extendable expression: Generator_p >= Generator_p_min_pu * Generator_p_nom * Generator_status ``` @@ -1821,7 +1821,7 @@ p_{t,g} \ge \underline{\mathrm{p}}_{t,g} \cdot \mathrm{p}^{\mathrm{nom}}_{g} \cd ```yaml Generator_com_p_upper: description: "`Generator-com-p-upper` — a committed unit outputs at most what is available; off, at most nothing" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable AND not Generator_p_nom_extendable expression: Generator_p <= Generator_p_max_pu * Generator_p_nom * Generator_status ``` @@ -1837,7 +1837,7 @@ p_{t,g} \le \overline{\mathrm{p}}_{t,g} \cdot \mathrm{p}^{\mathrm{nom}}_{g} \cdo ```yaml Generator_com_transition_start_up: description: "`Generator-com-transition-start-up` — turning on is a start, counted against the state the unit carried into the snapshot" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable expression: Generator_start_up >= Generator_status - Generator_previous_status ``` @@ -1853,7 +1853,7 @@ Generator_com_transition_start_up: ```yaml Generator_com_transition_shut_down: description: "`Generator-com-transition-shut-down` — turning off is a stop, counted against the state the unit carried into the snapshot" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable expression: Generator_shut_down >= Generator_previous_status - Generator_status ``` @@ -1872,7 +1872,7 @@ Generator_com_up_time: `Generator-com-up-time` — a unit started within its own minimum up time is still on. The first snapshot's share of the window is the brought-in up time's, which the must-stay-up mask carries - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable AND Generator_min_up_time > 0 AND position(snapshot) > 0 expression: sum_back(Generator_start_up, over=snapshot, within=Generator_min_up_time) <= Generator_status ``` @@ -1888,7 +1888,7 @@ Generator_com_up_time: ```yaml Generator_com_down_time: description: "`Generator-com-down-time` — a unit stopped within its own minimum down time is still off" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable AND Generator_min_down_time > 0 AND position(snapshot) > 0 expression: sum_back(Generator_shut_down, over=snapshot, within=Generator_min_down_time) <= 1 - Generator_status ``` @@ -1904,7 +1904,7 @@ Generator_com_down_time: ```yaml Generator_com_status_must_stay_up: description: "`Generator-com-status-min_up_time_must_stay_up` — a unit still serving the up time it brought in stays on" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable AND Generator_must_stay_up expression: Generator_status == 1 ``` @@ -1923,7 +1923,7 @@ Generator_p_ramp_limit_up_run_big_m: `Generator-p-ramp_limit_up-run-bigM` — a committed extendable unit raises output no faster than its limit of the chosen build; the big M releases the row in the snapshot it turns on - foreach: [snapshot, generator] + dims: [snapshot, generator] where: >- Generator_committable AND Generator_p_nom_extendable AND Generator_ramp_limit_up AND (position(snapshot) > 0 OR Generator_status_initial == 0) @@ -1947,7 +1947,7 @@ Generator_p_ramp_limit_up_start_big_m: `Generator-p-ramp_limit_up-start-bigM` — in the snapshot it turns on, a committed extendable unit ramps no further than its start-up ramp of the chosen build; the big M releases the row everywhere else - foreach: [snapshot, generator] + dims: [snapshot, generator] where: >- Generator_committable AND Generator_p_nom_extendable AND Generator_ramp_limit_up AND (position(snapshot) > 0 OR Generator_status_initial == 0) @@ -1971,7 +1971,7 @@ Generator_p_ramp_limit_down_run_big_m: `Generator-p-ramp_limit_down-run-bigM` — a committed extendable unit lowers output no faster than its limit of the chosen build; the big M releases the row in the snapshot it turns off - foreach: [snapshot, generator] + dims: [snapshot, generator] where: >- Generator_committable AND Generator_p_nom_extendable AND Generator_ramp_limit_down AND (position(snapshot) > 0 OR Generator_status_initial == 0) @@ -1995,7 +1995,7 @@ Generator_p_ramp_limit_down_shut_big_m: `Generator-p-ramp_limit_down-shut-bigM` — in the snapshot it turns off, a committed extendable unit ramps no further than its shut-down ramp of the chosen build; the big M releases the row everywhere else - foreach: [snapshot, generator] + dims: [snapshot, generator] where: >- Generator_committable AND Generator_p_nom_extendable AND Generator_ramp_limit_down AND (position(snapshot) > 0 OR Generator_status_initial == 0) @@ -2016,7 +2016,7 @@ Generator_p_ramp_limit_down_shut_big_m: ```yaml Generator_p_nom_modularity: description: "`Generator-p_nom_modularity` — the chosen build is a whole number of modules" - foreach: [generator] + dims: [generator] where: Generator_p_nom_extendable AND Generator_p_nom_mod > 0 expression: Generator_p_nom_ext == Generator_p_nom_mod * Generator_n_mod ``` @@ -2034,7 +2034,7 @@ Generator_com_ext_p_upper_cap: description: >- `Generator-com-ext-p-upper-cap` — a committed extendable unit outputs at most what is available of the chosen build, whatever its status - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable AND Generator_p_nom_extendable AND NOT (Generator_p_nom_mod > 0) expression: Generator_p <= Generator_p_max_pu * Generator_p_nom_ext ``` @@ -2050,7 +2050,7 @@ p_{t,g} \le \overline{\mathrm{p}}_{t,g} \cdot P_{g} \qquad \forall\, t \in \math ```yaml Generator_com_ext_p_upper_big_m: description: "`Generator-com-ext-p-upper-bigM` — off, a unit outputs nothing; on, the big M is no bound" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable AND Generator_p_nom_extendable AND NOT (Generator_p_nom_mod > 0) expression: Generator_p <= Generator_big_m * Generator_status ``` @@ -2068,7 +2068,7 @@ Generator_com_ext_p_lower: description: >- `Generator-com-ext-p-lower` — a committed extendable unit outputs at least its minimum of the chosen build; off, the big M releases the row - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable AND Generator_p_nom_extendable AND NOT (Generator_p_nom_mod > 0) expression: >- Generator_p >= @@ -2090,7 +2090,7 @@ Generator_com_ext_p_lower_nonneg: `Generator-com-ext-p-lower-nonneg` — where no minimum-per-unit is negative, output is also plainly non-negative, a row the big-M lower cannot assert while the unit is off - foreach: [snapshot, generator] + dims: [snapshot, generator] where: >- Generator_committable AND Generator_p_nom_extendable AND Generator_p_min_pu_nonneg AND NOT (Generator_p_nom_mod > 0) @@ -2110,7 +2110,7 @@ Generator_com_mod_p_lower: description: >- `Generator-com-mod-p-lower` — a committed modular unit outputs at least its minimum of one module, whether the build is fixed or a decision - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable AND Generator_p_nom_mod > 0 expression: Generator_p >= Generator_p_min_pu * Generator_p_nom_mod * Generator_status ``` @@ -2128,7 +2128,7 @@ Generator_com_mod_p_upper: description: >- `Generator-com-mod-p-upper` — a committed modular unit outputs at most one module's share, whether the build is fixed or a decision - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable AND Generator_p_nom_mod > 0 expression: Generator_p <= Generator_p_max_pu * Generator_p_nom_mod * Generator_status ``` @@ -2147,7 +2147,7 @@ Generator_status_p_fixed_upper: `Generator-status-p-fixed-upper` — a status is at most the modules in place, an explicit row as PyPSA writes it: one where the build is not modular, and the fixed build's whole count of modules where it is - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable AND NOT (Generator_p_nom_extendable AND Generator_p_nom_mod > 0) expression: Generator_status <= Generator_modules_installed ``` @@ -2166,7 +2166,7 @@ Generator_start_up_p_fixed_upper: `Generator-start_up-p-fixed-upper` — a start is at most the modules in place, an explicit row as PyPSA writes it: one where the build is not modular, and the fixed build's whole count of modules where it is - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable AND NOT (Generator_p_nom_extendable AND Generator_p_nom_mod > 0) expression: Generator_start_up <= Generator_modules_installed ``` @@ -2185,7 +2185,7 @@ Generator_shut_down_p_fixed_upper: `Generator-shut_down-p-fixed-upper` — a stop is at most the modules in place, an explicit row as PyPSA writes it: one where the build is not modular, and the fixed build's whole count of modules where it is - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable AND NOT (Generator_p_nom_extendable AND Generator_p_nom_mod > 0) expression: Generator_shut_down <= Generator_modules_installed ``` @@ -2201,7 +2201,7 @@ Generator_shut_down_p_fixed_upper: ```yaml Generator_status_p_nom_variable_upper: description: "`Generator-status-p_nom-variable-upper` — a modular unit is on only where a module is built" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable AND Generator_p_nom_extendable AND Generator_p_nom_mod > 0 expression: Generator_status <= Generator_n_mod ``` @@ -2217,7 +2217,7 @@ u_{t,g} \le N_{g} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \ ```yaml Generator_start_up_p_nom_variable_upper: description: "`Generator-start_up-p_nom-variable-upper` — a modular unit starts only where a module is built" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable AND Generator_p_nom_extendable AND Generator_p_nom_mod > 0 expression: Generator_start_up <= Generator_n_mod ``` @@ -2233,7 +2233,7 @@ Generator_start_up_p_nom_variable_upper: ```yaml Generator_shut_down_p_nom_variable_upper: description: "`Generator-shut_down-p_nom-variable-upper` — a modular unit stops only where a module is built" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable AND Generator_p_nom_extendable AND Generator_p_nom_mod > 0 expression: Generator_shut_down <= Generator_n_mod ``` @@ -2249,7 +2249,7 @@ Generator_shut_down_p_nom_variable_upper: ```yaml Line_fix_s_lower: description: "`Line-fix-s-lower` — a fixed line carries at least the negative of its rating" - foreach: [snapshot, line] + dims: [snapshot, line] where: not Line_s_nom_extendable expression: Line_s >= -Line_s_max_pu * Line_s_nom ``` @@ -2265,7 +2265,7 @@ s_{t,k} \ge -\overline{\mathrm{s}}_{t,k} \cdot \mathrm{s}^{\mathrm{nom}}_{k} \qq ```yaml Line_fix_s_upper: description: "`Line-fix-s-upper` — a fixed line carries at most its rating" - foreach: [snapshot, line] + dims: [snapshot, line] where: not Line_s_nom_extendable expression: Line_s <= Line_s_max_pu * Line_s_nom ``` @@ -2281,7 +2281,7 @@ s_{t,k} \le \overline{\mathrm{s}}_{t,k} \cdot \mathrm{s}^{\mathrm{nom}}_{k} \qqu ```yaml Line_ext_s_lower: description: "`Line-ext-s-lower` — an extendable line carries at least the negative of its rating of the chosen build" - foreach: [snapshot, line] + dims: [snapshot, line] where: Line_s_nom_extendable expression: Line_s >= -Line_s_max_pu * Line_s_nom_ext ``` @@ -2297,7 +2297,7 @@ s_{t,k} \ge -\overline{\mathrm{s}}_{t,k} \cdot S_{k} \qquad \forall\, t \in \mat ```yaml Line_ext_s_upper: description: "`Line-ext-s-upper` — an extendable line carries at most its rating of the chosen build" - foreach: [snapshot, line] + dims: [snapshot, line] where: Line_s_nom_extendable expression: Line_s <= Line_s_max_pu * Line_s_nom_ext ``` @@ -2313,7 +2313,7 @@ s_{t,k} \le \overline{\mathrm{s}}_{t,k} \cdot S_{k} \qquad \forall\, t \in \math ```yaml Line_ext_s_nom_lower: description: "`Line-ext-s_nom-lower` — the chosen build is at least its floor" - foreach: [line] + dims: [line] where: Line_s_nom_extendable expression: Line_s_nom_ext >= Line_s_nom_min ``` @@ -2329,7 +2329,7 @@ S_{k} \ge \underline{\mathrm{s}}^{\mathrm{nom}}_{k} \qquad \forall\, k \in \math ```yaml Line_ext_s_nom_upper: description: "`Line-ext-s_nom-upper` — the chosen build is at most its cap; a cap of infinity is no row" - foreach: [line] + dims: [line] where: Line_s_nom_extendable AND Line_s_nom_max expression: Line_s_nom_ext <= Line_s_nom_max ``` @@ -2345,7 +2345,7 @@ S_{k} \le \overline{\mathrm{s}}^{\mathrm{nom}}_{k} \qquad \forall\, k \in \mathc ```yaml Line_s_nom_set: description: "`Line-s_nom_set` — the chosen build pinned, wherever a value is given" - foreach: [line] + dims: [line] where: Line_s_nom_extendable AND Line_s_nom_set expression: Line_s_nom_ext == Line_s_nom_set ``` @@ -2361,7 +2361,7 @@ S_{k} = \mathrm{s}^{\mathrm{nom,set}}_{k} \qquad \forall\, k \in \mathcal{K} \,: ```yaml Line_s_set: description: "`Line-s_set` — flow pinned to the given schedule, wherever one is given" - foreach: [snapshot, line] + dims: [snapshot, line] where: Line_s_set expression: Line_s == Line_s_set ``` @@ -2380,7 +2380,7 @@ Kirchhoff_Voltage_Law: `Kirchhoff-Voltage-Law` — around every independent cycle the impedance-weighted flows sum to nothing, which is what makes the linear power flow physical rather than transport - foreach: [snapshot, cycle] + dims: [snapshot, cycle] expression: sum(Line_s * Line_cycle_weight, over=line) == 0 ``` @@ -2400,7 +2400,7 @@ Generator_p_ramp_limit_up: start-up ramp in the snapshot it turns on. A unit that came into the horizon running brought an unknown output, so it carries no row at the first snapshot — nor does any unit a big M releases instead - foreach: [snapshot, generator] + dims: [snapshot, generator] where: >- Generator_ramp_limit_up AND NOT (Generator_committable AND Generator_p_nom_extendable) @@ -2424,7 +2424,7 @@ Generator_p_ramp_limit_down: shut-down ramp in the snapshot it turns off. A unit that came into the horizon running brought an unknown output, so it carries no row at the first snapshot — nor does any unit a big M releases instead - foreach: [snapshot, generator] + dims: [snapshot, generator] where: >- Generator_ramp_limit_down AND NOT (Generator_committable AND Generator_p_nom_extendable) @@ -2446,7 +2446,7 @@ Link_p_ramp_limit_up: `Link-p-ramp_limit_up` — a link raises flow no faster than its limit of the build. The translated term vacates the first snapshot, where a plain optimize builds no row either - foreach: [snapshot, link] + dims: [snapshot, link] where: Link_ramp_limit_up expression: Link_p - shift(Link_p, over=snapshot, offset=1) <= Link_ramp_limit_up * Link_p_nom_effective ``` @@ -2462,7 +2462,7 @@ f_{t,l} - f_{t - 1,l} \le \mathrm{ru}^{f}_{l} \cdot \widetilde{\mathrm{f}}^{\mat ```yaml Link_p_ramp_limit_down: description: "`Link-p-ramp_limit_down` — a link lowers flow no faster than its limit of the build" - foreach: [snapshot, link] + dims: [snapshot, link] where: Link_ramp_limit_down expression: shift(Link_p, over=snapshot, offset=1) - Link_p <= Link_ramp_limit_down * Link_p_nom_effective ``` @@ -2478,7 +2478,7 @@ f_{t - 1,l} - f_{t,l} \le \mathrm{rd}^{f}_{l} \cdot \widetilde{\mathrm{f}}^{\mat ```yaml StorageUnit_ext_p_dispatch_lower: description: "`StorageUnit-ext-p_dispatch-lower` — dispatch is non-negative" - foreach: [snapshot, storage_unit] + dims: [snapshot, storage_unit] where: StorageUnit_p_nom_extendable expression: StorageUnit_p_dispatch >= 0 ``` @@ -2494,7 +2494,7 @@ h^{+}_{t,s} \ge 0 \qquad \forall\, t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \ ```yaml StorageUnit_ext_p_dispatch_upper: description: "`StorageUnit-ext-p_dispatch-upper` — an extendable unit dispatches at most the chosen build" - foreach: [snapshot, storage_unit] + dims: [snapshot, storage_unit] where: StorageUnit_p_nom_extendable expression: StorageUnit_p_dispatch <= StorageUnit_p_max_pu * StorageUnit_p_nom_ext ``` @@ -2510,7 +2510,7 @@ h^{+}_{t,s} \le \overline{\mathrm{h}}_{t,s} \cdot H_{s} \qquad \forall\, t \in \ ```yaml StorageUnit_ext_p_store_lower: description: "`StorageUnit-ext-p_store-lower` — storing is non-negative" - foreach: [snapshot, storage_unit] + dims: [snapshot, storage_unit] where: StorageUnit_p_nom_extendable expression: StorageUnit_p_store >= 0 ``` @@ -2528,7 +2528,7 @@ StorageUnit_ext_p_store_upper: description: >- `StorageUnit-ext-p_store-upper` — an extendable unit stores at most the chosen build, the minimum-per-unit column carrying that cap negated - foreach: [snapshot, storage_unit] + dims: [snapshot, storage_unit] where: StorageUnit_p_nom_extendable expression: StorageUnit_p_store <= -StorageUnit_p_min_pu * StorageUnit_p_nom_ext ``` @@ -2544,7 +2544,7 @@ h^{-}_{t,s} \le -\underline{\mathrm{h}}_{t,s} \cdot H_{s} \qquad \forall\, t \in ```yaml StorageUnit_ext_state_of_charge_lower: description: "`StorageUnit-ext-state_of_charge-lower` — charge is non-negative" - foreach: [snapshot, storage_unit] + dims: [snapshot, storage_unit] where: StorageUnit_p_nom_extendable expression: StorageUnit_state_of_charge >= 0 ``` @@ -2560,7 +2560,7 @@ StorageUnit_ext_state_of_charge_lower: ```yaml StorageUnit_ext_state_of_charge_upper: description: "`StorageUnit-ext-state_of_charge-upper` — an extendable unit holds at most its hours at the chosen build" - foreach: [snapshot, storage_unit] + dims: [snapshot, storage_unit] where: StorageUnit_p_nom_extendable expression: StorageUnit_state_of_charge <= StorageUnit_max_hours * StorageUnit_p_nom_ext ``` @@ -2576,7 +2576,7 @@ StorageUnit_ext_state_of_charge_upper: ```yaml StorageUnit_ext_p_nom_lower: description: "`StorageUnit-ext-p_nom-lower` — the chosen build is at least its floor" - foreach: [storage_unit] + dims: [storage_unit] where: StorageUnit_p_nom_extendable expression: StorageUnit_p_nom_ext >= StorageUnit_p_nom_min ``` @@ -2592,7 +2592,7 @@ H_{s} \ge \underline{\mathrm{h}}^{\mathrm{nom}}_{s} \qquad \forall\, s \in \math ```yaml StorageUnit_ext_p_nom_upper: description: "`StorageUnit-ext-p_nom-upper` — the chosen build is at most its cap; a cap of infinity is no row" - foreach: [storage_unit] + dims: [storage_unit] where: StorageUnit_p_nom_extendable AND StorageUnit_p_nom_max expression: StorageUnit_p_nom_ext <= StorageUnit_p_nom_max ``` @@ -2608,7 +2608,7 @@ H_{s} \le \overline{\mathrm{h}}^{\mathrm{nom}}_{s} \qquad \forall\, s \in \mathc ```yaml StorageUnit_p_nom_set: description: "`StorageUnit-p_nom_set` — the chosen build pinned, wherever a value is given" - foreach: [storage_unit] + dims: [storage_unit] where: StorageUnit_p_nom_extendable AND StorageUnit_p_nom_set expression: StorageUnit_p_nom_ext == StorageUnit_p_nom_set ``` @@ -2627,7 +2627,7 @@ StorageUnit_energy_balance: `StorageUnit-energy_balance` — the charge carried in, plus what is stored after its efficiency, less what dispatch draws down before its own, plus inflow not spilled - foreach: [snapshot, storage_unit] + dims: [snapshot, storage_unit] expression: >- StorageUnit_state_of_charge == StorageUnit_charge_carried_in @@ -2647,7 +2647,7 @@ StorageUnit_energy_balance: ```yaml Store_fix_e_lower: description: "`Store-fix-e-lower` — a fixed store holds at least its floor" - foreach: [snapshot, store] + dims: [snapshot, store] where: not Store_e_nom_extendable expression: Store_e >= Store_e_min_pu * Store_e_nom ``` @@ -2663,7 +2663,7 @@ e_{t,v} \ge \underline{\mathrm{e}}_{t,v} \cdot \mathrm{e}^{\mathrm{nom}}_{v} \qq ```yaml Store_fix_e_upper: description: "`Store-fix-e-upper` — a fixed store holds at most its nominal capacity" - foreach: [snapshot, store] + dims: [snapshot, store] where: not Store_e_nom_extendable expression: Store_e <= Store_e_max_pu * Store_e_nom ``` @@ -2679,7 +2679,7 @@ e_{t,v} \le \overline{\mathrm{e}}_{t,v} \cdot \mathrm{e}^{\mathrm{nom}}_{v} \qqu ```yaml Store_ext_e_lower: description: "`Store-ext-e-lower` — an extendable store holds at least its floor of the chosen build" - foreach: [snapshot, store] + dims: [snapshot, store] where: Store_e_nom_extendable expression: Store_e >= Store_e_min_pu * Store_e_nom_ext ``` @@ -2695,7 +2695,7 @@ e_{t,v} \ge \underline{\mathrm{e}}_{t,v} \cdot E_{v} \qquad \forall\, t \in \mat ```yaml Store_ext_e_upper: description: "`Store-ext-e-upper` — an extendable store holds at most the chosen build" - foreach: [snapshot, store] + dims: [snapshot, store] where: Store_e_nom_extendable expression: Store_e <= Store_e_max_pu * Store_e_nom_ext ``` @@ -2711,7 +2711,7 @@ e_{t,v} \le \overline{\mathrm{e}}_{t,v} \cdot E_{v} \qquad \forall\, t \in \math ```yaml Store_ext_e_nom_lower: description: "`Store-ext-e_nom-lower` — the chosen build is at least its floor" - foreach: [store] + dims: [store] where: Store_e_nom_extendable expression: Store_e_nom_ext >= Store_e_nom_min ``` @@ -2727,7 +2727,7 @@ E_{v} \ge \underline{\mathrm{e}}^{\mathrm{nom}}_{v} \qquad \forall\, v \in \math ```yaml Store_ext_e_nom_upper: description: "`Store-ext-e_nom-upper` — the chosen build is at most its cap; a cap of infinity is no row" - foreach: [store] + dims: [store] where: Store_e_nom_extendable AND Store_e_nom_max expression: Store_e_nom_ext <= Store_e_nom_max ``` @@ -2743,7 +2743,7 @@ E_{v} \le \overline{\mathrm{e}}^{\mathrm{nom}}_{v} \qquad \forall\, v \in \mathc ```yaml Store_e_nom_set: description: "`Store-e_nom_set` — the chosen build pinned, wherever a value is given" - foreach: [store] + dims: [store] where: Store_e_nom_extendable AND Store_e_nom_set expression: Store_e_nom_ext == Store_e_nom_set ``` @@ -2759,7 +2759,7 @@ E_{v} = \mathrm{e}^{\mathrm{nom,set}}_{v} \qquad \forall\, v \in \mathcal{V} \,: ```yaml Store_energy_balance: description: "`Store-energy_balance` — the energy carried in, less what is delivered to the bus" - foreach: [snapshot, store] + dims: [snapshot, store] expression: >- Store_e == Store_energy_carried_in @@ -2777,7 +2777,7 @@ e_{t,v} = \overleftarrow{e}_{t,v} - q_{t,v} \cdot \mathrm{w}^{\mathrm{sto}}_{t} ```yaml Generator_p_set: description: "`Generator-p_set` — output pinned to the given schedule, wherever one is given" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_p_set expression: Generator_p == Generator_p_set ``` @@ -2793,7 +2793,7 @@ p_{t,g} = \mathrm{p}^{\mathrm{set}}_{t,g} \qquad \forall\, t \in \mathcal{T},\ g ```yaml Link_p_set: description: "`Link-p_set` — flow pinned to the given schedule, wherever one is given" - foreach: [snapshot, link] + dims: [snapshot, link] where: Link_p_set expression: Link_p == Link_p_set ``` @@ -2809,7 +2809,7 @@ f_{t,l} = \mathrm{f}^{\mathrm{set}}_{t,l} \qquad \forall\, t \in \mathcal{T},\ l ```yaml StorageUnit_p_set: description: "`StorageUnit-p_set` — net dispatch pinned to the given schedule, wherever one is given" - foreach: [snapshot, storage_unit] + dims: [snapshot, storage_unit] where: StorageUnit_p_set expression: StorageUnit_p_dispatch - StorageUnit_p_store == StorageUnit_p_set ``` @@ -2825,7 +2825,7 @@ h^{+}_{t,s} - h^{-}_{t,s} = \mathrm{h}^{\mathrm{set}}_{t,s} \qquad \forall\, t \ ```yaml StorageUnit_state_of_charge_set: description: "`StorageUnit-state_of_charge_set` — charge pinned to the given schedule, wherever one is given" - foreach: [snapshot, storage_unit] + dims: [snapshot, storage_unit] where: StorageUnit_state_of_charge_set expression: StorageUnit_state_of_charge == StorageUnit_state_of_charge_set ``` @@ -2841,7 +2841,7 @@ StorageUnit_state_of_charge_set: ```yaml Store_e_set: description: "`Store-e_set` — energy pinned to the given schedule, wherever one is given" - foreach: [snapshot, store] + dims: [snapshot, store] where: Store_e_set expression: Store_e == Store_e_set ``` @@ -2857,7 +2857,7 @@ e_{t,v} = \mathrm{e}^{\mathrm{set}}_{t,v} \qquad \forall\, t \in \mathcal{T},\ v ```yaml GlobalConstraint_primary_energy_ub: description: "`primary_energy` — its total, at most its constant" - foreach: [global_constraint] + dims: [global_constraint] where: GlobalConstraint_type == 'primary_energy' AND GlobalConstraint_sense == '<=' expression: primary_energy <= GlobalConstraint_constant ``` @@ -2873,7 +2873,7 @@ GlobalConstraint_primary_energy_ub: ```yaml GlobalConstraint_primary_energy_lb: description: "`primary_energy` — its total, at least its constant" - foreach: [global_constraint] + dims: [global_constraint] where: GlobalConstraint_type == 'primary_energy' AND GlobalConstraint_sense == '>=' expression: primary_energy >= GlobalConstraint_constant ``` @@ -2889,7 +2889,7 @@ GlobalConstraint_primary_energy_lb: ```yaml GlobalConstraint_primary_energy_eq: description: "`primary_energy` — its total, at its constant" - foreach: [global_constraint] + dims: [global_constraint] where: GlobalConstraint_type == 'primary_energy' AND GlobalConstraint_sense == '==' expression: primary_energy == GlobalConstraint_constant ``` @@ -2905,7 +2905,7 @@ GlobalConstraint_primary_energy_eq: ```yaml GlobalConstraint_operational_limit_ub: description: "`operational_limit` — its total, at most its constant" - foreach: [global_constraint] + dims: [global_constraint] where: GlobalConstraint_type == 'operational_limit' AND GlobalConstraint_sense == '<=' expression: operational_limit <= GlobalConstraint_constant ``` @@ -2921,7 +2921,7 @@ GlobalConstraint_operational_limit_ub: ```yaml GlobalConstraint_operational_limit_lb: description: "`operational_limit` — its total, at least its constant" - foreach: [global_constraint] + dims: [global_constraint] where: GlobalConstraint_type == 'operational_limit' AND GlobalConstraint_sense == '>=' expression: operational_limit >= GlobalConstraint_constant ``` @@ -2937,7 +2937,7 @@ GlobalConstraint_operational_limit_lb: ```yaml GlobalConstraint_operational_limit_eq: description: "`operational_limit` — its total, at its constant" - foreach: [global_constraint] + dims: [global_constraint] where: GlobalConstraint_type == 'operational_limit' AND GlobalConstraint_sense == '==' expression: operational_limit == GlobalConstraint_constant ``` @@ -2953,7 +2953,7 @@ GlobalConstraint_operational_limit_eq: ```yaml GlobalConstraint_transmission_volume_expansion_limit_ub: description: "`transmission_volume_expansion_limit` — its total, at most its constant" - foreach: [global_constraint] + dims: [global_constraint] where: GlobalConstraint_type == 'transmission_volume_expansion_limit' AND GlobalConstraint_sense == '<=' expression: transmission_volume_expansion <= GlobalConstraint_constant ``` @@ -2969,7 +2969,7 @@ GlobalConstraint_transmission_volume_expansion_limit_ub: ```yaml GlobalConstraint_transmission_volume_expansion_limit_lb: description: "`transmission_volume_expansion_limit` — its total, at least its constant" - foreach: [global_constraint] + dims: [global_constraint] where: GlobalConstraint_type == 'transmission_volume_expansion_limit' AND GlobalConstraint_sense == '>=' expression: transmission_volume_expansion >= GlobalConstraint_constant ``` @@ -2985,7 +2985,7 @@ GlobalConstraint_transmission_volume_expansion_limit_lb: ```yaml GlobalConstraint_transmission_volume_expansion_limit_eq: description: "`transmission_volume_expansion_limit` — its total, at its constant" - foreach: [global_constraint] + dims: [global_constraint] where: GlobalConstraint_type == 'transmission_volume_expansion_limit' AND GlobalConstraint_sense == '==' expression: transmission_volume_expansion == GlobalConstraint_constant ``` @@ -3001,7 +3001,7 @@ GlobalConstraint_transmission_volume_expansion_limit_eq: ```yaml GlobalConstraint_transmission_expansion_cost_limit_ub: description: "`transmission_expansion_cost_limit` — its total, at most its constant" - foreach: [global_constraint] + dims: [global_constraint] where: GlobalConstraint_type == 'transmission_expansion_cost_limit' AND GlobalConstraint_sense == '<=' expression: transmission_expansion_cost <= GlobalConstraint_constant ``` @@ -3017,7 +3017,7 @@ GlobalConstraint_transmission_expansion_cost_limit_ub: ```yaml GlobalConstraint_transmission_expansion_cost_limit_lb: description: "`transmission_expansion_cost_limit` — its total, at least its constant" - foreach: [global_constraint] + dims: [global_constraint] where: GlobalConstraint_type == 'transmission_expansion_cost_limit' AND GlobalConstraint_sense == '>=' expression: transmission_expansion_cost >= GlobalConstraint_constant ``` @@ -3033,7 +3033,7 @@ GlobalConstraint_transmission_expansion_cost_limit_lb: ```yaml GlobalConstraint_transmission_expansion_cost_limit_eq: description: "`transmission_expansion_cost_limit` — its total, at its constant" - foreach: [global_constraint] + dims: [global_constraint] where: GlobalConstraint_type == 'transmission_expansion_cost_limit' AND GlobalConstraint_sense == '==' expression: transmission_expansion_cost == GlobalConstraint_constant ``` @@ -3049,7 +3049,7 @@ GlobalConstraint_transmission_expansion_cost_limit_eq: ```yaml GlobalConstraint_tech_capacity_expansion_limit_ub: description: "`tech_capacity_expansion_limit` — its total, at most its constant" - foreach: [global_constraint] + dims: [global_constraint] where: GlobalConstraint_type == 'tech_capacity_expansion_limit' AND GlobalConstraint_sense == '<=' expression: tech_capacity_expansion <= GlobalConstraint_constant ``` @@ -3065,7 +3065,7 @@ GlobalConstraint_tech_capacity_expansion_limit_ub: ```yaml GlobalConstraint_tech_capacity_expansion_limit_lb: description: "`tech_capacity_expansion_limit` — its total, at least its constant" - foreach: [global_constraint] + dims: [global_constraint] where: GlobalConstraint_type == 'tech_capacity_expansion_limit' AND GlobalConstraint_sense == '>=' expression: tech_capacity_expansion >= GlobalConstraint_constant ``` @@ -3081,7 +3081,7 @@ GlobalConstraint_tech_capacity_expansion_limit_lb: ```yaml GlobalConstraint_tech_capacity_expansion_limit_eq: description: "`tech_capacity_expansion_limit` — its total, at its constant" - foreach: [global_constraint] + dims: [global_constraint] where: GlobalConstraint_type == 'tech_capacity_expansion_limit' AND GlobalConstraint_sense == '==' expression: tech_capacity_expansion == GlobalConstraint_constant ``` @@ -3103,7 +3103,7 @@ Bus_nodal_balance: load there. A bus nothing is attached to has no row; PyPSA refuses one that carries load, and this file does not yet. - foreach: [snapshot, bus] + dims: [snapshot, bus] expression: >- sum(Generator_p, by=Generator_bus) + sum(StorageUnit_p_dispatch - StorageUnit_p_store, by=StorageUnit_bus) @@ -3126,7 +3126,7 @@ Generator_previous_status: description: >- the commitment state a generator carries into a snapshot — the state it brought into the horizon at the first, the previous snapshot's after that - foreach: [snapshot, generator] + dims: [snapshot, generator] cases: opening: { when: "position(snapshot) == 0", expression: Generator_status_initial } otherwise: shift(Generator_status, over=snapshot, offset=1) @@ -3144,7 +3144,7 @@ Generator_previous_p: the output a generator carries into a snapshot — nothing at the start of the horizon, which is why a unit that came in running carries no ramp row there - foreach: [snapshot, generator] + dims: [snapshot, generator] cases: opening: { when: "position(snapshot) == 0", expression: 0 } otherwise: shift(Generator_p, over=snapshot, offset=1) @@ -3159,7 +3159,7 @@ Generator_previous_p: ```yaml Generator_p_nom_effective: description: the build a generator's limits are taken against — the chosen one where it is extendable, the given one otherwise - foreach: [generator] + dims: [generator] cases: extendable: { when: Generator_p_nom_extendable, expression: Generator_p_nom_ext } otherwise: Generator_p_nom @@ -3177,7 +3177,7 @@ Generator_ramp_up_allowance: how far a generator may raise output between two snapshots — its ramp limit of the build while it stays on, plus its start-up ramp in the snapshot it turns on - foreach: [snapshot, generator] + dims: [snapshot, generator] cases: committed: when: Generator_committable @@ -3200,7 +3200,7 @@ Generator_ramp_down_allowance: how far a generator may lower output between two snapshots — its ramp limit of the build while it stays on, plus its shut-down ramp in the snapshot it turns off - foreach: [snapshot, generator] + dims: [snapshot, generator] cases: committed: when: Generator_committable @@ -3220,7 +3220,7 @@ Generator_ramp_down_allowance: ```yaml Link_p_nom_effective: description: the build a link's limits are taken against — the chosen one where it is extendable, the given one otherwise - foreach: [link] + dims: [link] cases: extendable: { when: Link_p_nom_extendable, expression: Link_p_nom_ext } otherwise: Link_p_nom @@ -3239,7 +3239,7 @@ StorageUnit_charge_carried_in: standing loss where it is cyclic, the given initial charge at the start of the horizon, which no standing loss has touched yet, and the previous snapshot's less standing loss otherwise - foreach: [snapshot, storage_unit] + dims: [snapshot, storage_unit] cases: cyclic: when: StorageUnit_cyclic_state_of_charge @@ -3263,7 +3263,7 @@ Store_energy_carried_in: standing loss where it is cyclic, the given initial energy at the start of the horizon, which no standing loss has touched yet, and the previous snapshot's less standing loss otherwise - foreach: [snapshot, store] + dims: [snapshot, store] cases: cyclic: when: Store_e_cyclic @@ -3289,7 +3289,7 @@ Link_output_arrival: is not the flow still in transit at the first snapshots is lost. A port that does not delay (`delay` zero) delivers its flow unshifted, cyclic or not - foreach: [snapshot, link_output] + dims: [snapshot, link_output] cases: wrapping: when: Link_output_cyclic_delay diff --git a/docs/examples/pypsa_linearized_uc.md b/docs/examples/pypsa_linearized_uc.md index 23d7e62e..94d9978e 100644 --- a/docs/examples/pypsa_linearized_uc.md +++ b/docs/examples/pypsa_linearized_uc.md @@ -179,7 +179,7 @@ objective: ```yaml Generator_fix_p_lower: description: "`Generator-fix-p-lower` — a generator outputs at least its minimum" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: not Generator_committable expression: Generator_p >= Generator_p_min_pu * Generator_p_nom ``` @@ -195,7 +195,7 @@ p_{t,g} \ge \underline{\mathrm{p}}_{t,g} \cdot \mathrm{p}^{\mathrm{nom}}_{g} \qq ```yaml Generator_fix_p_upper: description: "`Generator-fix-p-upper` — a generator outputs at most what is available" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: not Generator_committable expression: Generator_p <= Generator_p_max_pu * Generator_p_nom ``` @@ -211,7 +211,7 @@ p_{t,g} \le \overline{\mathrm{p}}_{t,g} \cdot \mathrm{p}^{\mathrm{nom}}_{g} \qqu ```yaml Link_fix_p_lower: description: "`Link-fix-p-lower` — a link carries at least its minimum, negative for the other way" - foreach: [snapshot, link] + dims: [snapshot, link] expression: Link_p >= Link_p_min_pu * Link_p_nom ``` @@ -226,7 +226,7 @@ f_{t,l} \ge \underline{\mathrm{f}}_{t,l} \cdot \mathrm{f}^{\mathrm{nom}}_{l} \qq ```yaml Link_fix_p_upper: description: "`Link-fix-p-upper` — a link carries at most its nominal power" - foreach: [snapshot, link] + dims: [snapshot, link] expression: Link_p <= Link_p_max_pu * Link_p_nom ``` @@ -244,7 +244,7 @@ Bus_nodal_balance: `Bus-nodal_balance` — what is generated at a bus, less what the links take away, plus what arrives over them after losses, meets the load there - foreach: [snapshot, bus] + dims: [snapshot, bus] expression: >- sum(Generator_p, by=Generator_bus) - sum(Link_p, by=Link_bus0) @@ -263,7 +263,7 @@ Bus_nodal_balance: ```yaml Generator_com_p_lower: description: "`Generator-com-p-lower` — a committed unit outputs at least its minimum; off, at least nothing" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable expression: Generator_p >= Generator_p_min_pu * Generator_p_nom * Generator_status ``` @@ -279,7 +279,7 @@ p_{t,g} \ge \underline{\mathrm{p}}_{t,g} \cdot \mathrm{p}^{\mathrm{nom}}_{g} \cd ```yaml Generator_com_p_upper: description: "`Generator-com-p-upper` — a committed unit outputs at most what is available; off, at most nothing" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable expression: Generator_p <= Generator_p_max_pu * Generator_p_nom * Generator_status ``` @@ -295,7 +295,7 @@ p_{t,g} \le \overline{\mathrm{p}}_{t,g} \cdot \mathrm{p}^{\mathrm{nom}}_{g} \cdo ```yaml Generator_com_transition_start_up: description: "`Generator-com-transition-start-up` — turning on is a start, counted against the state the unit carried into the snapshot" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable expression: Generator_start_up >= Generator_status - Generator_previous_status ``` @@ -311,7 +311,7 @@ Generator_com_transition_start_up: ```yaml Generator_com_transition_shut_down: description: "`Generator-com-transition-shut-down` — turning off is a stop, counted against the state the unit carried into the snapshot" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable expression: Generator_shut_down >= Generator_previous_status - Generator_status ``` @@ -330,7 +330,7 @@ Generator_com_up_time: `Generator-com-up-time` — a unit started within its own minimum up time is still on. The first snapshot's share of the window is the brought-in up time's, which the must-stay-up mask carries - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable AND Generator_min_up_time > 0 AND position(snapshot) > 0 expression: sum_back(Generator_start_up, over=snapshot, within=Generator_min_up_time) <= Generator_status ``` @@ -346,7 +346,7 @@ Generator_com_up_time: ```yaml Generator_com_down_time: description: "`Generator-com-down-time` — a unit stopped within its own minimum down time is still off" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable AND Generator_min_down_time > 0 AND position(snapshot) > 0 expression: sum_back(Generator_shut_down, over=snapshot, within=Generator_min_down_time) <= 1 - Generator_status ``` @@ -362,7 +362,7 @@ Generator_com_down_time: ```yaml Generator_com_status_must_stay_up: description: "`Generator-com-status-min_up_time_must_stay_up` — a unit still serving the up time it brought in stays on" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable AND Generator_must_stay_up expression: Generator_status == 1 ``` @@ -383,7 +383,7 @@ Generator_p_ramp_limit_up: start-up ramp in the snapshot it turns on. A unit that came into the horizon running brought an unknown output, so it carries no row at the first snapshot - foreach: [snapshot, generator] + dims: [snapshot, generator] where: >- Generator_committable AND Generator_ramp_limit_up AND (position(snapshot) > 0 OR Generator_status_initial == 0) @@ -410,7 +410,7 @@ Generator_p_ramp_limit_down: shut-down ramp in the snapshot it turns off. A unit that came into the horizon running brought an unknown output, so it carries no row at the first snapshot - foreach: [snapshot, generator] + dims: [snapshot, generator] where: >- Generator_committable AND Generator_ramp_limit_down AND (position(snapshot) > 0 OR Generator_status_initial == 0) @@ -432,7 +432,7 @@ Generator_p_ramp_limit_down: ```yaml Generator_status_p_fixed_upper: description: "`Generator-status-p-fixed-upper` — a status is at most one, an explicit row as PyPSA writes it" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable expression: Generator_status <= 1 ``` @@ -448,7 +448,7 @@ u_{t,g} \le 1 \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \math ```yaml Generator_start_up_p_fixed_upper: description: "`Generator-start_up-p-fixed-upper` — a start is at most one, an explicit row as PyPSA writes it" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable expression: Generator_start_up <= 1 ``` @@ -464,7 +464,7 @@ Generator_start_up_p_fixed_upper: ```yaml Generator_shut_down_p_fixed_upper: description: "`Generator-shut_down-p-fixed-upper` — a stop is at most one, an explicit row as PyPSA writes it" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable expression: Generator_shut_down <= 1 ``` @@ -484,7 +484,7 @@ Generator_com_p_before: fits the share of it still on, less the share it is shutting down at the shut-down ramp. The translated term vacates the first snapshot, as PyPSA's `sns[1:]` does - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable AND Generator_partly_tightened expression: >- shift(Generator_p, over=snapshot, offset=1) @@ -504,7 +504,7 @@ p_{t - 1,g} - \mathrm{rd}^{\mathrm{dn}}_{g} \cdot \mathrm{p}^{\mathrm{nom}}_{g} ```yaml Generator_com_p_current: description: "`Generator-com-p-current` — output fits the share on, and the share starting up only up to the start-up ramp" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable AND Generator_partly_tightened AND position(snapshot) > 0 expression: >- Generator_p - Generator_p_max_pu * Generator_p_nom * Generator_status @@ -522,7 +522,7 @@ p_{t,g} - \overline{\mathrm{p}}_{t,g} \cdot \mathrm{p}^{\mathrm{nom}}_{g} \cdot ```yaml Generator_com_partly_start_up: description: "`Generator-com-partly-start-up` — raising output while a share is starting up is bounded by the ramp of the share on and the start-up ramp of the share coming on" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable AND Generator_partly_tightened expression: >- Generator_p - shift(Generator_p, over=snapshot, offset=1) @@ -543,7 +543,7 @@ p_{t,g} - p_{t - 1,g} - \left( \underline{\mathrm{p}}_{t,g} \cdot \mathrm{p}^{\m ```yaml Generator_com_partly_shut_down: description: "`Generator-com-partly-shut-down` — lowering output while a share is shutting down is bounded likewise, by the shut-down ramp" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable AND Generator_partly_tightened expression: >- shift(Generator_p, over=snapshot, offset=1) - Generator_p @@ -564,7 +564,7 @@ Generator_previous_status: description: >- the commitment state a generator carries into a snapshot — the state it brought into the horizon at the first, the previous snapshot's after that - foreach: [snapshot, generator] + dims: [snapshot, generator] cases: opening: { when: "position(snapshot) == 0", expression: Generator_status_initial } otherwise: shift(Generator_status, over=snapshot, offset=1) @@ -582,7 +582,7 @@ Generator_previous_p: the output a generator carries into a snapshot — nothing at the start of the horizon, which is why a unit that came in running carries no ramp row there - foreach: [snapshot, generator] + dims: [snapshot, generator] cases: opening: { when: "position(snapshot) == 0", expression: 0 } otherwise: shift(Generator_p, over=snapshot, offset=1) diff --git a/docs/examples/pypsa_losses.md b/docs/examples/pypsa_losses.md index 90da0828..eab0d2f2 100644 --- a/docs/examples/pypsa_losses.md +++ b/docs/examples/pypsa_losses.md @@ -152,7 +152,7 @@ objective: ```yaml Generator_fix_p_lower: description: "`Generator-fix-p-lower` — a generator outputs at least its minimum" - foreach: [snapshot, generator] + dims: [snapshot, generator] expression: Generator_p >= Generator_p_min_pu * Generator_p_nom ``` @@ -167,7 +167,7 @@ p_{t,g} \ge \underline{\mathrm{p}}_{t,g} \cdot \mathrm{p}^{\mathrm{nom}}_{g} \qq ```yaml Generator_fix_p_upper: description: "`Generator-fix-p-upper` — a generator outputs at most what is available" - foreach: [snapshot, generator] + dims: [snapshot, generator] expression: Generator_p <= Generator_p_max_pu * Generator_p_nom ``` @@ -182,7 +182,7 @@ p_{t,g} \le \overline{\mathrm{p}}_{t,g} \cdot \mathrm{p}^{\mathrm{nom}}_{g} \qqu ```yaml Link_fix_p_lower: description: "`Link-fix-p-lower` — a link carries at least its minimum, negative for the other way" - foreach: [snapshot, link] + dims: [snapshot, link] expression: Link_p >= Link_p_min_pu * Link_p_nom ``` @@ -197,7 +197,7 @@ f_{t,l} \ge \underline{\mathrm{f}}_{t,l} \cdot \mathrm{f}^{\mathrm{nom}}_{l} \qq ```yaml Link_fix_p_upper: description: "`Link-fix-p-upper` — a link carries at most its nominal power" - foreach: [snapshot, link] + dims: [snapshot, link] expression: Link_p <= Link_p_max_pu * Link_p_nom ``` @@ -212,7 +212,7 @@ f_{t,l} \le \overline{\mathrm{f}}_{t,l} \cdot \mathrm{f}^{\mathrm{nom}}_{l} \qqu ```yaml Line_fix_s_lower: description: "`Line-fix-s-lower` — a fixed line carries at least the negative of its rating, the loss counted against it" - foreach: [snapshot, line] + dims: [snapshot, line] where: not Line_s_nom_extendable expression: Line_s - Line_loss >= -Line_s_max_pu * Line_s_nom ``` @@ -228,7 +228,7 @@ s_{t,k} - \ell_{t,k} \ge -\overline{\mathrm{s}}_{t,k} \cdot \mathrm{s}^{\mathrm{ ```yaml Line_fix_s_upper: description: "`Line-fix-s-upper` — a fixed line carries at most its rating, loss included" - foreach: [snapshot, line] + dims: [snapshot, line] where: not Line_s_nom_extendable expression: Line_s + Line_loss <= Line_s_max_pu * Line_s_nom ``` @@ -244,7 +244,7 @@ s_{t,k} + \ell_{t,k} \le \overline{\mathrm{s}}_{t,k} \cdot \mathrm{s}^{\mathrm{n ```yaml Line_ext_s_lower: description: "`Line-ext-s-lower` — an extendable line carries at least the negative of its rating of the chosen build" - foreach: [snapshot, line] + dims: [snapshot, line] where: Line_s_nom_extendable expression: Line_s - Line_loss >= -Line_s_max_pu * Line_s_nom_ext ``` @@ -260,7 +260,7 @@ s_{t,k} - \ell_{t,k} \ge -\overline{\mathrm{s}}_{t,k} \cdot S_{k} \qquad \forall ```yaml Line_ext_s_upper: description: "`Line-ext-s-upper` — an extendable line carries at most its rating of the chosen build" - foreach: [snapshot, line] + dims: [snapshot, line] where: Line_s_nom_extendable expression: Line_s + Line_loss <= Line_s_max_pu * Line_s_nom_ext ``` @@ -276,7 +276,7 @@ s_{t,k} + \ell_{t,k} \le \overline{\mathrm{s}}_{t,k} \cdot S_{k} \qquad \forall\ ```yaml Line_ext_s_nom_lower: description: "`Line-ext-s_nom-lower` — the chosen build is at least its floor" - foreach: [line] + dims: [line] where: Line_s_nom_extendable expression: Line_s_nom_ext >= Line_s_nom_min ``` @@ -292,7 +292,7 @@ S_{k} \ge \underline{\mathrm{s}}^{\mathrm{nom}}_{k} \qquad \forall\, k \in \math ```yaml Line_ext_s_nom_upper: description: "`Line-ext-s_nom-upper` — the chosen build is at most its cap; a cap of infinity is no row" - foreach: [line] + dims: [line] where: Line_s_nom_extendable AND Line_s_nom_max expression: Line_s_nom_ext <= Line_s_nom_max ``` @@ -311,7 +311,7 @@ Kirchhoff_Voltage_Law: `Kirchhoff-Voltage-Law` — around every independent cycle the impedance-weighted flows sum to nothing, which is what makes the linear power flow physical rather than transport - foreach: [snapshot, cycle] + dims: [snapshot, cycle] expression: sum(Line_s * Line_cycle_weight, over=line) == 0 ``` @@ -329,7 +329,7 @@ Bus_nodal_balance: `Bus-nodal_balance` — what is generated at a bus, plus what the links and lines bring, meets the load there, less half of every incident line's loss — PyPSA dissipates a branch's loss half at either end - foreach: [snapshot, bus] + dims: [snapshot, bus] expression: >- sum(Generator_p, by=Generator_bus) - sum(Link_p, by=Link_bus0) @@ -352,7 +352,7 @@ Bus_nodal_balance: ```yaml Line_loss_upper: description: "`Line-loss_upper` — a line dissipates at most the loss at its rating" - foreach: [snapshot, line] + dims: [snapshot, line] expression: Line_loss <= Line_loss_max ``` @@ -370,7 +370,7 @@ Line_loss_tangents_forward: `Line-loss_tangents-{k}-1` — the loss sits above every tangent to its curve for flow one way; PyPSA names one row per segment `k`, this block states them all over the segment dimension - foreach: [snapshot, line, segment] + dims: [snapshot, line, segment] expression: Line_loss + Line_loss_slope * Line_s >= Line_loss_offset ``` @@ -385,7 +385,7 @@ Line_loss_tangents_forward: ```yaml Line_loss_tangents_reverse: description: "`Line-loss_tangents-{k}--1` — the same fan mirrored, the loss depending on the flow's magnitude" - foreach: [snapshot, line, segment] + dims: [snapshot, line, segment] expression: Line_loss - Line_loss_slope * Line_s >= Line_loss_offset ``` diff --git a/docs/examples/pypsa_multi_period.md b/docs/examples/pypsa_multi_period.md index 745c1ec1..e1334e8d 100644 --- a/docs/examples/pypsa_multi_period.md +++ b/docs/examples/pypsa_multi_period.md @@ -188,7 +188,7 @@ objective: ```yaml Generator_fix_p_lower: description: "`Generator-fix-p-lower` — a generator outputs at least its minimum" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: not Generator_p_nom_extendable AND Generator_active expression: Generator_p >= Generator_p_min_pu * Generator_p_nom ``` @@ -204,7 +204,7 @@ p_{t,g} \ge \underline{\mathrm{p}}_{t,g} \cdot \mathrm{p}^{\mathrm{nom}}_{g} \qq ```yaml Generator_fix_p_upper: description: "`Generator-fix-p-upper` — a generator outputs at most what is available" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: not Generator_p_nom_extendable AND Generator_active expression: Generator_p <= Generator_p_max_pu * Generator_p_nom ``` @@ -220,7 +220,7 @@ p_{t,g} \le \overline{\mathrm{p}}_{t,g} \cdot \mathrm{p}^{\mathrm{nom}}_{g} \qqu ```yaml Generator_ext_p_lower: description: "`Generator-ext-p-lower` — an extendable generator outputs at least its minimum of the chosen build" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_p_nom_extendable AND Generator_active expression: Generator_p >= Generator_p_min_pu * Generator_p_nom_ext ``` @@ -236,7 +236,7 @@ p_{t,g} \ge \underline{\mathrm{p}}_{t,g} \cdot P_{g} \qquad \forall\, t \in \mat ```yaml Generator_ext_p_upper: description: "`Generator-ext-p-upper` — an extendable generator outputs at most what is available of the chosen build" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_p_nom_extendable AND Generator_active expression: Generator_p <= Generator_p_max_pu * Generator_p_nom_ext ``` @@ -252,7 +252,7 @@ p_{t,g} \le \overline{\mathrm{p}}_{t,g} \cdot P_{g} \qquad \forall\, t \in \math ```yaml Generator_ext_p_nom_lower: description: "`Generator-ext-p_nom-lower` — the chosen build is at least its floor" - foreach: [generator] + dims: [generator] where: Generator_p_nom_extendable expression: Generator_p_nom_ext >= Generator_p_nom_min ``` @@ -268,7 +268,7 @@ P_{g} \ge \underline{\mathrm{p}}^{\mathrm{nom}}_{g} \qquad \forall\, g \in \math ```yaml Generator_ext_p_nom_upper: description: "`Generator-ext-p_nom-upper` — the chosen build is at most its cap; a cap of infinity is no row" - foreach: [generator] + dims: [generator] where: Generator_p_nom_extendable AND Generator_p_nom_max expression: Generator_p_nom_ext <= Generator_p_nom_max ``` @@ -284,7 +284,7 @@ P_{g} \le \overline{\mathrm{p}}^{\mathrm{nom}}_{g} \qquad \forall\, g \in \mathc ```yaml Link_fix_p_lower: description: "`Link-fix-p-lower` — a link carries at least its minimum, negative for the other way" - foreach: [snapshot, link] + dims: [snapshot, link] expression: Link_p >= Link_p_min_pu * Link_p_nom ``` @@ -299,7 +299,7 @@ f_{t,l} \ge \underline{\mathrm{f}}_{t,l} \cdot \mathrm{f}^{\mathrm{nom}}_{l} \qq ```yaml Link_fix_p_upper: description: "`Link-fix-p-upper` — a link carries at most its nominal power" - foreach: [snapshot, link] + dims: [snapshot, link] expression: Link_p <= Link_p_max_pu * Link_p_nom ``` @@ -317,7 +317,7 @@ Bus_nodal_balance: `Bus-nodal_balance` — what is generated at a bus, less what the links take away, plus what arrives over them after losses, meets the load there - foreach: [snapshot, bus] + dims: [snapshot, bus] expression: >- sum(Generator_p, by=Generator_bus) - sum(Link_p, by=Link_bus0) @@ -340,7 +340,7 @@ Carrier_growth_limit: build in the first period it stands in, is at most its allowance plus a share of what it added the period before; the first period has no predecessor, so `edge=0` leaves it the bare allowance - foreach: [carrier, period] + dims: [carrier, period] where: Carrier_max_growth expression: >- sum(Generator_p_nom_ext * Generator_first_active, by=Generator_carrier) diff --git a/docs/examples/pypsa_quadratic.md b/docs/examples/pypsa_quadratic.md index eb509563..c4455fd5 100644 --- a/docs/examples/pypsa_quadratic.md +++ b/docs/examples/pypsa_quadratic.md @@ -127,7 +127,7 @@ objective: ```yaml Generator_fix_p_lower: description: "`Generator-fix-p-lower` — a generator outputs at least its minimum" - foreach: [snapshot, generator] + dims: [snapshot, generator] expression: Generator_p >= Generator_p_min_pu * Generator_p_nom ``` @@ -142,7 +142,7 @@ p_{t,g} \ge \underline{\mathrm{p}}_{t,g} \cdot \mathrm{p}^{\mathrm{nom}}_{g} \qq ```yaml Generator_fix_p_upper: description: "`Generator-fix-p-upper` — a generator outputs at most what is available" - foreach: [snapshot, generator] + dims: [snapshot, generator] expression: Generator_p <= Generator_p_max_pu * Generator_p_nom ``` @@ -157,7 +157,7 @@ p_{t,g} \le \overline{\mathrm{p}}_{t,g} \cdot \mathrm{p}^{\mathrm{nom}}_{g} \qqu ```yaml Link_fix_p_lower: description: "`Link-fix-p-lower` — a link carries at least its minimum, negative for the other way" - foreach: [snapshot, link] + dims: [snapshot, link] expression: Link_p >= Link_p_min_pu * Link_p_nom ``` @@ -172,7 +172,7 @@ f_{t,l} \ge \underline{\mathrm{f}}_{t,l} \cdot \mathrm{f}^{\mathrm{nom}}_{l} \qq ```yaml Link_fix_p_upper: description: "`Link-fix-p-upper` — a link carries at most its nominal power" - foreach: [snapshot, link] + dims: [snapshot, link] expression: Link_p <= Link_p_max_pu * Link_p_nom ``` @@ -190,7 +190,7 @@ Bus_nodal_balance: `Bus-nodal_balance` — what is generated at a bus, less what the links take away, plus what arrives over them after losses, meets the load there - foreach: [snapshot, bus] + dims: [snapshot, bus] expression: >- sum(Generator_p, by=Generator_bus) - sum(Link_p, by=Link_bus0) diff --git a/docs/examples/pypsa_stochastic.md b/docs/examples/pypsa_stochastic.md index f2aeb90e..acda4b02 100644 --- a/docs/examples/pypsa_stochastic.md +++ b/docs/examples/pypsa_stochastic.md @@ -141,7 +141,7 @@ objective: ```yaml Generator_fix_p_lower: description: "`Generator-fix-p-lower` — a generator outputs at least its minimum" - foreach: [scenario, snapshot, generator] + dims: [scenario, snapshot, generator] where: not Generator_p_nom_extendable expression: Generator_p >= Generator_p_min_pu * Generator_p_nom ``` @@ -157,7 +157,7 @@ p_{s,t,g} \ge \underline{\mathrm{p}}_{t,g} \cdot \mathrm{p}^{\mathrm{nom}}_{g} \ ```yaml Generator_fix_p_upper: description: "`Generator-fix-p-upper` — a generator outputs at most what is available" - foreach: [scenario, snapshot, generator] + dims: [scenario, snapshot, generator] where: not Generator_p_nom_extendable expression: Generator_p <= Generator_p_max_pu * Generator_p_nom ``` @@ -173,7 +173,7 @@ p_{s,t,g} \le \overline{\mathrm{p}}_{s,t,g} \cdot \mathrm{p}^{\mathrm{nom}}_{g} ```yaml Generator_ext_p_lower: description: "`Generator-ext-p-lower` — an extendable generator outputs at least its minimum of the chosen build" - foreach: [scenario, snapshot, generator] + dims: [scenario, snapshot, generator] where: Generator_p_nom_extendable expression: Generator_p >= Generator_p_min_pu * Generator_p_nom_ext ``` @@ -189,7 +189,7 @@ p_{s,t,g} \ge \underline{\mathrm{p}}_{t,g} \cdot P_{g} \qquad \forall\, s \in \m ```yaml Generator_ext_p_upper: description: "`Generator-ext-p-upper` — an extendable generator outputs at most what is available of the chosen build" - foreach: [scenario, snapshot, generator] + dims: [scenario, snapshot, generator] where: Generator_p_nom_extendable expression: Generator_p <= Generator_p_max_pu * Generator_p_nom_ext ``` @@ -205,7 +205,7 @@ p_{s,t,g} \le \overline{\mathrm{p}}_{s,t,g} \cdot P_{g} \qquad \forall\, s \in \ ```yaml Generator_ext_p_nom_lower: description: "`Generator-ext-p_nom-lower` — the chosen build is at least its floor" - foreach: [generator] + dims: [generator] where: Generator_p_nom_extendable expression: Generator_p_nom_ext >= Generator_p_nom_min ``` @@ -221,7 +221,7 @@ P_{g} \ge \underline{\mathrm{p}}^{\mathrm{nom}}_{g} \qquad \forall\, g \in \math ```yaml Generator_ext_p_nom_upper: description: "`Generator-ext-p_nom-upper` — the chosen build is at most its cap; a cap of infinity is no row" - foreach: [generator] + dims: [generator] where: Generator_p_nom_extendable AND Generator_p_nom_max expression: Generator_p_nom_ext <= Generator_p_nom_max ``` @@ -237,7 +237,7 @@ P_{g} \le \overline{\mathrm{p}}^{\mathrm{nom}}_{g} \qquad \forall\, g \in \mathc ```yaml Link_fix_p_lower: description: "`Link-fix-p-lower` — a link carries at least its minimum, negative for the other way" - foreach: [scenario, snapshot, link] + dims: [scenario, snapshot, link] expression: Link_p >= Link_p_min_pu * Link_p_nom ``` @@ -252,7 +252,7 @@ f_{s,t,l} \ge \underline{\mathrm{f}}_{t,l} \cdot \mathrm{f}^{\mathrm{nom}}_{l} \ ```yaml Link_fix_p_upper: description: "`Link-fix-p-upper` — a link carries at most its nominal power" - foreach: [scenario, snapshot, link] + dims: [scenario, snapshot, link] expression: Link_p <= Link_p_max_pu * Link_p_nom ``` @@ -270,7 +270,7 @@ Bus_nodal_balance: `Bus-nodal_balance` — what is generated at a bus, less what the links take away, plus what arrives over them after losses, meets the load there - foreach: [scenario, snapshot, bus] + dims: [scenario, snapshot, bus] expression: >- sum(Generator_p, by=Generator_bus) - sum(Link_p, by=Link_bus0) @@ -289,7 +289,7 @@ Bus_nodal_balance: ```yaml CVaR_excess: description: "`CVaR-excess-{s}` — a scenario's operating cost beyond the tail's start is its excess; PyPSA names one row per scenario" - foreach: [scenario] + dims: [scenario] expression: CVaR_a - scenario_opex + CVaR_theta >= 0 ``` @@ -304,7 +304,7 @@ a_{s} - \mathit{scenario\_opex}_{s} + \theta \ge 0 \qquad \forall\, s \in \mathc ```yaml CVaR_def: description: "`CVaR-def` — the tail's average is at least where it starts plus the expected excess over the tail's probability" - foreach: [] + dims: [] expression: CVaR_theta + CVaR_inv_tail * sum(scenario_weight * CVaR_a, over=scenario) <= CVaR ``` diff --git a/docs/howto/regimes.md b/docs/howto/regimes.md index 932c3555..ffeebbd2 100644 --- a/docs/howto/regimes.md +++ b/docs/howto/regimes.md @@ -32,16 +32,16 @@ the recipe needs no second model file. committable: { dims: [generator], dtype: bool } variables: - p: { foreach: [snapshot, generator], bounds: { lower: 0, upper: p_max } } - on: { foreach: [snapshot, generator], where: committable, domain: binary } + p: { dims: [snapshot, generator], bounds: { lower: 0, upper: p_max } } + on: { dims: [snapshot, generator], where: committable, domain: binary } constraints: floor_committed: - foreach: [snapshot, generator] + dims: [snapshot, generator] where: committable expression: p >= p_min * on ceiling_committed: - foreach: [snapshot, generator] + dims: [snapshot, generator] where: committable expression: p <= p_max * on ``` @@ -63,12 +63,12 @@ the recipe needs no second model file. committable: { dims: [generator], dtype: bool } variables: - p: { foreach: [snapshot, generator], bounds: { lower: 0 } } - on: { foreach: [snapshot, generator], where: committable, domain: binary } + p: { dims: [snapshot, generator], bounds: { lower: 0 } } + on: { dims: [snapshot, generator], where: committable, domain: binary } expressions: available: - foreach: [snapshot, generator] + dims: [snapshot, generator] cases: committed: when: committable @@ -77,7 +77,7 @@ the recipe needs no second model file. constraints: ceiling: - foreach: [snapshot, generator] + dims: [snapshot, generator] expression: p <= available ``` diff --git a/docs/reference/language/absence.md b/docs/reference/language/absence.md index fafdb8d0..84a8f433 100644 --- a/docs/reference/language/absence.md +++ b/docs/reference/language/absence.md @@ -16,7 +16,7 @@ parameters: p_max: { dims: [g] } variables: p: - foreach: [g] + dims: [g] where: "p_max > 0" ``` @@ -50,17 +50,17 @@ operator, it does not. ```yaml variables: - x: { foreach: [g] } - y: { foreach: [g], where: "p_max > 0" } # no y[old] + x: { dims: [g] } + y: { dims: [g], where: "p_max > 0" } # no y[old] constraints: each: - foreach: [g] + dims: [g] expression: x + y >= 1 # rows at wind and gas; no row at old total: - foreach: [] + dims: [] expression: sum(x + y, over=g) >= 1 # x[wind] + y[wind] + x[gas] + y[gas] >= 1 split: - foreach: [] + dims: [] expression: sum(x, over=g) + sum(y, over=g) >= 1 # x[old] is back in ``` @@ -75,7 +75,7 @@ Beside a parameter, the rule reads the other way: ```yaml constraints: cap: - foreach: [g] + dims: [g] expression: x - rel_max * y <= 0 ``` @@ -112,15 +112,15 @@ applies: ```yaml variables: spill: - foreach: [storage] + dims: [storage] where: has_inflow absence: zero # outside the mask spill is 0 and the row stands soc: - foreach: [storage] + dims: [storage] where: has_store # the default, absence: undefined — no row constraints: balance: - foreach: [storage] + dims: [storage] expression: inflow - spill - soc == 0 ``` diff --git a/docs/reference/language/declarations.md b/docs/reference/language/declarations.md index 0a129101..a2fe9259 100644 --- a/docs/reference/language/declarations.md +++ b/docs/reference/language/declarations.md @@ -71,7 +71,7 @@ on the way past. ## `variables` A variable is what the solver decides. There is one column per coordinate of -`foreach`. +`dims`. ```yaml dimensions: @@ -81,7 +81,7 @@ parameters: p_max: { dims: [generator] } variables: p: - foreach: [snapshot, generator] + dims: [snapshot, generator] where: "p_max > 0" bounds: lower: 0 @@ -90,7 +90,7 @@ variables: | Field | | | | ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | -| `foreach` | required. The dimensions it is indexed by | | +| `dims` | required. The dimensions it is indexed by | | | `where` | which coordinates exist ([absence](absence.md)) | default `null` | | `bounds.lower` / `bounds.upper` | a number, or the name of a `float` or `int` parameter. Two numbers that cross are refused at load. A named bound is checked against its data | default `-inf` / `inf` | | `domain` | `continuous`, `integer` or `binary`. `binary` carries fixed 0/1 bounds | default `continuous` | @@ -105,7 +105,7 @@ A bound is a name or a number, never arithmetic. `upper: p_max` is accepted, and `upper: -rating` is refused with a message that says so. Ship the negated column as data. Arithmetic in a bound is [#31](https://github.com/fluxopt/lpspec/issues/31). The dimensions of a bound -parameter must not exceed `foreach`. +parameter must not exceed its `dims`. Equal bounds pin a variable. That is how one declaration covers a quantity that is a decision in one model and data in another: bind `lower` and `upper` to the @@ -126,21 +126,21 @@ dimensions: parameters: load: { dims: [snapshot] } variables: - p: { foreach: [snapshot, generator] } + p: { dims: [snapshot, generator] } constraints: power_balance: - foreach: [snapshot] + dims: [snapshot] expression: sum(p, over=generator) == load ``` | Field | | | | ------------- | --------------------------------------------------- | -------------- | -| `foreach` | required. The rows this rule builds | | +| `dims` | required. The rows this rule builds | | | `expression` | required. It uses exactly one of `<=`, `>=` or `==` | | | `where` | which rows are built ([absence](absence.md)) | default `null` | | `description` | free text | default `null` | -The dimensions of the expression must **equal** `foreach`. See +The dimensions of the expression must **equal** its `dims`. See [how dimensions combine](expressions.md#how-dimensions-combine). Either side of the comparator may carry variables, and one side must. A @@ -149,7 +149,7 @@ is settled before the solve. A single _row_ can still end up with no variable terms, because the data left its terms nowhere to sit. Such a row is not built. See [absence](absence.md#rows-with-no-variable-terms). -`foreach: []` gives one scalar row, for a rule such as a system-wide budget. An +`dims: []` gives one scalar row, for a rule such as a system-wide budget. An empty dimension list means one value for a parameter, one column for a variable and one row for a constraint, so a scalar is never written as a dummy dimension of size 1. A scalar _variable_ may not carry a `where` @@ -162,11 +162,11 @@ Two regimes of one rule are two blocks, each with a name a reader chose: ```yaml storage_balance: - foreach: [snapshot, storage] + dims: [snapshot, storage] expression: soc == shift(soc, over=snapshot, offset=1) * (1 - loss) + charge - discharge storage_balance_initial: - foreach: [snapshot, storage] + dims: [snapshot, storage] where: "position(snapshot) == 0" expression: soc == soc_initial ``` @@ -188,7 +188,7 @@ dimensions: parameters: cost: { dims: [generator] } variables: - p: { foreach: [generator] } + p: { dims: [generator] } objective: sense: minimize expression: sum(p * cost) diff --git a/docs/reference/language/dimensions.md b/docs/reference/language/dimensions.md index 7b4373b7..3021c6a2 100644 --- a/docs/reference/language/dimensions.md +++ b/docs/reference/language/dimensions.md @@ -154,7 +154,7 @@ does with the column, not what the column holds: | is a mask | a `bool` parameter | a bare name in a `where` is its own answer | Two rules follow from the table. If `b` has one value per `a`, then `b` is a -**lookup** over `a`, and not a dimension: a `foreach` product over two +**lookup** over `a`, and not a dimension: a `dims:` product over two dimensions that depend on each other, cut back with a mask, is the shape that `lookups` replaces. diff --git a/docs/reference/language/errors.md b/docs/reference/language/errors.md index 897397f0..12add9e8 100644 --- a/docs/reference/language/errors.md +++ b/docs/reference/language/errors.md @@ -69,7 +69,7 @@ that too depends on the data | `MathSpecError` | The root. Everything below is an instance of it | | `LanguageError` | Something in the model: a construct outside the language, a dimension set that does not compose, or a name that nothing declares | | `SchemaError` | Something in the file: an unknown key, a malformed declaration, or a bad symbol table | -| `DimensionError` | Dimensions that disagree, such as a constraint whose expression does not equal its `foreach` | +| `DimensionError` | Dimensions that disagree, such as a constraint whose expression does not equal its `dims` | | `PiecewiseExpansionError` | A `piecewise:` block that cannot be expanded | Every one of these means the file is wrong, and every one is reproducible from diff --git a/docs/reference/language/expressions.md b/docs/reference/language/expressions.md index 03769fce..320bf4d4 100644 --- a/docs/reference/language/expressions.md +++ b/docs/reference/language/expressions.md @@ -116,14 +116,14 @@ variable. The objective has no name at all. ## How dimensions combine -A parameter declares `dims`, a variable declares `foreach`, and every dimension +A parameter and a variable both declare `dims`, and every dimension argument is name-checked. So **the dimension set of every expression is known before any data binds**: | Node | Dim set | Error | | ------------------------------- | -------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | | number | `{}` | | -| parameter / variable | its `dims` / its `foreach` | | +| parameter / variable | its `dims` | | | `-x`, `+x` | `dims(x)` | | | `a + b`, `a * b`, `a / b` | `dims(a) ∪ dims(b)` | | | `sum(x)` | `{}` | error if `dims(x)` is already empty | @@ -139,8 +139,9 @@ product is allowed wherever the declaration's own dimensions cover the result. Those dimensions are the declaration's **frame**. What is never allowed is a declaration that disagrees with its expression: -- A constraint requires `dims(lhs) ∪ dims(rhs)` to **equal** its `foreach`. A - stray dimension multiplies the rows, and an unused `foreach` dimension repeats +- A constraint requires `dims(lhs) ∪ dims(rhs)` to **equal** its `dims`. A + stray dimension multiplies the rows, and a declared dimension the expression + does not carry repeats one row across them. - An objective must carry **no dimensions**. It is one number, and the sums that reduce it to one number are written in the expression. @@ -227,10 +228,10 @@ dimensions: parameters: soc_initial: { dims: [] } variables: - soc: { foreach: [snapshot], bounds: { lower: 0 } } + soc: { dims: [snapshot], bounds: { lower: 0 } } constraints: soc_start: - foreach: [snapshot] + dims: [snapshot] where: "position(snapshot) == 0" # not: snapshot == 0 expression: soc == soc_initial ``` @@ -255,10 +256,10 @@ lookups: parameters: soc_initial: { dims: [period] } variables: - soc: { foreach: [snapshot], bounds: { lower: 0 } } + soc: { dims: [snapshot], bounds: { lower: 0 } } constraints: soc_start: - foreach: [snapshot] + dims: [snapshot] where: "position(snapshot, by=period_of) == 0" expression: soc == at(soc_initial, by=period_of) ``` @@ -278,7 +279,7 @@ dimensions: parameters: rate: { dims: [generator] } variables: - p: { foreach: [generator] } + p: { dims: [generator] } expressions: total_generation: sum(p, over=generator) emissions: @@ -287,7 +288,7 @@ expressions: ``` Write it as a bare string, or as a mapping when it carries a `description:`. Its -dimensions fall out of its body, so there is no `foreach`. The CO₂ that a +dimensions fall out of its body, so there is no `dims:`. The CO₂ that a constraint bounds and the CO₂ that a summary reports are then one definition, validated once. @@ -315,7 +316,7 @@ three ways. Named here, the inequality is written once: expressions: previous_status: description: the commitment state a unit carries into a snapshot - foreach: [snapshot, generator] + dims: [snapshot, generator] cases: always_on: when: "not committable" @@ -326,7 +327,7 @@ expressions: otherwise: shift(status, over=snapshot, offset=1) constraints: ramp_up: - foreach: [snapshot, generator] + dims: [snapshot, generator] expression: >- p - shift(p, over=snapshot, offset=1, edge=0) <= ramp_limit * previous_status + start_up_limit * (1 - previous_status) @@ -339,7 +340,7 @@ $$\mathit{previous\_status}_{t,g} = \begin{cases} 1 & \text{if } \neg \mathrm{co A named expression carries **exactly one** of `expression:` and `cases:`. A `cases:` block is a map of named cases, each with a `when:` mask and an `expression:`. Beside it, `otherwise:` carries every coordinate the cases leave, -and `foreach:` declares the **frame**: the dimensions every case ranges over. A +and `dims:` declares the **frame**: the dimensions every case ranges over. A point of the frame is a **coordinate**, here one snapshot for one generator. #### The rules that keep the cases apart @@ -372,10 +373,10 @@ point of the frame is a **coordinate**, here one snapshot for one generator. have no value, and absence [spreads](absence.md), so a constraint reading the expression would lose rows it never masked. -- **`foreach:` is required with cases, and refused without them.** A case may be +- **`dims:` is required with cases, and refused without them.** A case may be a single number while its `when:` ranges over dimensions, as `always_on` does, so the frame cannot fall out of the body. Each `when:` and each value must sit - inside the frame. The dimensions of a reference are the declared `foreach`, + inside the frame. The dimensions of a reference are the declared `dims`, and a narrower case broadcasts as a parameter with fewer dimensions does. Claiming a coordinate is not the same as having a value there. The `otherwise:` diff --git a/docs/reference/language/index.md b/docs/reference/language/index.md index 1ff4fb13..2430fbe8 100644 --- a/docs/reference/language/index.md +++ b/docs/reference/language/index.md @@ -23,13 +23,13 @@ parameters: variables: p: - foreach: [snapshot, generator] + dims: [snapshot, generator] where: "p_max > 0" bounds: { lower: 0, upper: p_max } constraints: power_balance: - foreach: [snapshot] + dims: [snapshot] expression: sum(p, over=generator) == load objective: @@ -44,18 +44,18 @@ That file is a complete model. The pages below give the exact rules. `to_spec` checks everything it can without data, and refuses the file with a message that names the fix. These ten rules are what it checks. -| # | Rule | | -| --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- | -| 1 | A file has ten declaration keys, plus `version` and `description`. A key the schema does not know is refused, with the nearest valid key named: `boundz` → `bounds`. | [File shape](file.md) | -| 2 | Everything that can be checked without data is checked when the file loads. | [Errors](errors.md) | -| 3 | Every name is declared once. A parameter and a dimension both called `snapshot` is refused, and the message names both lines. | [Names](expressions.md#name-resolution) | -| 4 | Where a name may stand depends on what it is. A dimension may follow `over=`, and may not be multiplied: `p * snapshot` is refused, because `snapshot` is an axis and not a column of numbers. | [Names](expressions.md#name-resolution) | -| 5 | `a + b` carries the dimensions of `a` and of `b` together. A constraint's expression must carry **exactly** its `foreach`. The objective must carry none. A `where` or a bound may carry fewer dimensions than its declaration, never more. | [How dimensions combine](expressions.md#how-dimensions-combine) | -| 6 | A variable's `where:` deletes the variable at the masked coordinates. There is no column there, not a column fixed at zero. A constraint's `where:` deletes the row. | [Absence](absence.md) | -| 7 | A deleted variable takes its row with it: `x + y >= 1` has no row where `y` is deleted. Inside a `sum` it is one term fewer, and the row stays. So `sum(x + y)` and `sum(x) + sum(y)` are different constraints. | [Absence](absence.md#how-absence-travels) | -| 8 | A parameter row that is missing from the table reads as `0` in arithmetic and as false in a `where`. Where `0` would change the model, as in a divisor or a bound, the missing row is refused instead. | [Absence](absence.md#what-creates-absence) | -| 9 | The objective and the constraints may multiply two variables: `p * p * wear`. A bound and a `piecewise:` link may not. `x / y` needs `y` free of variables, and `a ** b` needs both `a` and `b` free of them. | [Expressions](expressions.md) | -| 10 | The operators are `sum`, `sum_back`, `at`, `shift`, and `dual` in a reported expression. There are no others, and a file cannot add one. Write a composition of them as a macro. | [Operators](operators.md) | +| # | Rule | | +| --- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- | +| 1 | A file has ten declaration keys, plus `version` and `description`. A key the schema does not know is refused, with the nearest valid key named: `boundz` → `bounds`. | [File shape](file.md) | +| 2 | Everything that can be checked without data is checked when the file loads. | [Errors](errors.md) | +| 3 | Every name is declared once. A parameter and a dimension both called `snapshot` is refused, and the message names both lines. | [Names](expressions.md#name-resolution) | +| 4 | Where a name may stand depends on what it is. A dimension may follow `over=`, and may not be multiplied: `p * snapshot` is refused, because `snapshot` is an axis and not a column of numbers. | [Names](expressions.md#name-resolution) | +| 5 | `a + b` carries the dimensions of `a` and of `b` together. A constraint's expression must carry **exactly** its `dims`. The objective must carry none. A `where` or a bound may carry fewer dimensions than its declaration, never more. | [How dimensions combine](expressions.md#how-dimensions-combine) | +| 6 | A variable's `where:` deletes the variable at the masked coordinates. There is no column there, not a column fixed at zero. A constraint's `where:` deletes the row. | [Absence](absence.md) | +| 7 | A deleted variable takes its row with it: `x + y >= 1` has no row where `y` is deleted. Inside a `sum` it is one term fewer, and the row stays. So `sum(x + y)` and `sum(x) + sum(y)` are different constraints. | [Absence](absence.md#how-absence-travels) | +| 8 | A parameter row that is missing from the table reads as `0` in arithmetic and as false in a `where`. Where `0` would change the model, as in a divisor or a bound, the missing row is refused instead. | [Absence](absence.md#what-creates-absence) | +| 9 | The objective and the constraints may multiply two variables: `p * p * wear`. A bound and a `piecewise:` link may not. `x / y` needs `y` free of variables, and `a ** b` needs both `a` and `b` free of them. | [Expressions](expressions.md) | +| 10 | The operators are `sum`, `sum_back`, `at`, `shift`, and `dual` in a reported expression. There are no others, and a file cannot add one. Write a composition of them as a macro. | [Operators](operators.md) | ## The pages diff --git a/docs/reference/language/operators.md b/docs/reference/language/operators.md index 7f3954cf..2ab05482 100644 --- a/docs/reference/language/operators.md +++ b/docs/reference/language/operators.md @@ -59,11 +59,11 @@ lookups: parameters: load: { dims: [bus] } variables: - p: { foreach: [generator] } - f: { foreach: [line] } + p: { dims: [generator] } + f: { dims: [line] } constraints: nodal_balance: - foreach: [bus] + dims: [bus] expression: >- sum(p, by=gen_bus) + sum(f, by=line_to) @@ -114,12 +114,12 @@ parameters: min_up: { dims: [unit], dtype: int } variables: - started: { foreach: [unit, hour], domain: binary } - on: { foreach: [unit, hour], domain: binary } + started: { dims: [unit, hour], domain: binary } + on: { dims: [unit, hour], domain: binary } constraints: stays_up_its_own_time: - foreach: [unit, hour] + dims: [unit, hour] expression: sum_back(started, over=hour, within=min_up) <= on objective: { sense: minimize, expression: sum(on) } @@ -160,12 +160,12 @@ dimensions: parameters: eta: { dims: [storage] } variables: - soc: { foreach: [snapshot, storage] } - charge: { foreach: [snapshot, storage] } - discharge: { foreach: [snapshot, storage] } + soc: { dims: [snapshot, storage] } + charge: { dims: [snapshot, storage] } + discharge: { dims: [snapshot, storage] } constraints: storage_balance: - foreach: [snapshot, storage] + dims: [snapshot, storage] expression: soc == shift(soc, over=snapshot, offset=1, edge='wrap') + charge * eta - discharge ``` @@ -216,10 +216,10 @@ lookups: parameters: inflow: { dims: [snapshot] } variables: - soc: { foreach: [snapshot], bounds: { lower: 0 } } + soc: { dims: [snapshot], bounds: { lower: 0 } } constraints: season_balance: - foreach: [snapshot] + dims: [snapshot] expression: soc == shift(soc, over=snapshot, offset=1, edge='wrap', by=season_of) + inflow objective: { sense: minimize, expression: sum(soc) } ``` @@ -254,11 +254,11 @@ parameters: demand: { dims: [technology, month] } variables: order: - foreach: [technology, month] + dims: [technology, month] bounds: { lower: 0 } constraints: arrives_after_its_lead: - foreach: [technology, month] + dims: [technology, month] expression: shift(order, over=month, offset=lead, edge=0) >= demand objective: { sense: minimize, expression: sum(order) } ``` @@ -300,11 +300,11 @@ parameters: demand: { dims: [snapshot] } variables: order: - foreach: [snapshot] + dims: [snapshot] bounds: { lower: 0 } constraints: arrives_after_its_periods_lead: - foreach: [snapshot] + dims: [snapshot] expression: shift(order, over=snapshot, offset=lead, by=period_of, edge=0) >= demand objective: { sense: minimize, expression: sum(order) } ``` diff --git a/docs/reference/language/piecewise.md b/docs/reference/language/piecewise.md index 25a99efd..f6b01992 100644 --- a/docs/reference/language/piecewise.md +++ b/docs/reference/language/piecewise.md @@ -85,7 +85,7 @@ means: ```yaml running: - foreach: [snapshot, generator] + dims: [snapshot, generator] domain: binary where: committable # only some units have a commitment decision ``` @@ -194,7 +194,7 @@ ties three, write the formulation out: ```yaml variables: weight: # the convex combination, one per converter and period - foreach: [converter, time, bp] + dims: [converter, time, bp] where: bp_present # how far each curve runs bounds: { lower: 0, upper: 1 } @@ -203,10 +203,10 @@ sos: constraints: one_operating_point: - foreach: [converter, time] + dims: [converter, time] expression: sum(weight, over=bp) == 1 on_the_curve: # one row per flow — this is where the count goes - foreach: [flow, time] + dims: [flow, time] expression: rate == sum(at(weight, by=converter_of) * bp_rate, over=bp) ``` diff --git a/docs/reference/language/reading.md b/docs/reference/language/reading.md index 5b688718..3b6abcc6 100644 --- a/docs/reference/language/reading.md +++ b/docs/reference/language/reading.md @@ -36,10 +36,10 @@ parameters: bp_y: { dims: [generator, bp] } variables: p: - foreach: [generator] + dims: [generator] bounds: { lower: 0 } cost: - foreach: [generator] + dims: [generator] bounds: { lower: 0 } piecewise: curve: @@ -50,7 +50,7 @@ piecewise: method: convex constraints: target: - foreach: [] + dims: [] expression: sum(p, over=generator) >= 100 objective: sense: minimize @@ -205,5 +205,5 @@ gets a file for a reviewer to read. `to_yaml()` writes every value and omits every absence. `domain: continuous` is written out, because a reviewer should see the default. A `null`, an infinite -bound and an empty section are left out. `foreach: []` is written, because an +bound and an empty section are left out. `dims: []` is written, because an empty list is a value: it says the declaration is a scalar. diff --git a/docs/reference/language/reported.md b/docs/reference/language/reported.md index 14c974a6..6d6fd1a9 100644 --- a/docs/reference/language/reported.md +++ b/docs/reference/language/reported.md @@ -16,7 +16,7 @@ dimensions: parameters: marginal_cost: { dims: [generator] } variables: - p: { foreach: [snapshot, generator] } + p: { dims: [snapshot, generator] } expressions: system_cost: sum(sum(p * marginal_cost, over=generator), over=snapshot) delivered: sum(sum(p, over=generator), over=snapshot) @@ -84,7 +84,7 @@ quantity, move that quantity into an entry whose shape the math can read. ## Reading a constraint's dual `dual(c)` reads the **row dual** of the constraint `c`: the shadow price a solve -puts on that row, over `c`'s own `foreach`. It is the one built-in that only a +puts on that row, over `c`'s own `dims`. It is the one built-in that only a reported entry may call. `c` [resolves against the constraints alone](expressions.md#name-resolution), @@ -114,7 +114,7 @@ Where the solver returns no dual, the engine reports no value. ## How an engine reads a reported entry -A reported entry has the dimensions of its body, so there is no `foreach` and no +A reported entry has the dimensions of its body, so there is no `dims:` and no `where`. Where a masked row leaves a solved quantity absent, the reported value is absent there too. See [absence](absence.md#reported-values). diff --git a/docs/reference/notation.md b/docs/reference/notation.md index b9cb558f..10d99b65 100644 --- a/docs/reference/notation.md +++ b/docs/reference/notation.md @@ -154,7 +154,7 @@ names the plain expression: its symbol prints here, its definition once below ```yaml budgeted: - foreach: [snapshot] + dims: [snapshot] expression: spend <= budget ``` @@ -168,7 +168,7 @@ names the cased expression: its symbol prints here, its block once below ```yaml starts: - foreach: [snapshot, generator] + dims: [snapshot, generator] expression: p <= startup_cost ``` @@ -182,7 +182,7 @@ sum over a lookup ```yaml balance: - foreach: [snapshot, bus] + dims: [snapshot, bus] expression: sum(p, by=gen_bus) + spill - slack == load ``` @@ -196,7 +196,7 @@ roll (cyclic) and shift (acyclic) in one equation ```yaml ramp: - foreach: [snapshot, generator] + dims: [snapshot, generator] expression: p - shift(p, over=snapshot, offset=1, edge='wrap') <= shift(p, over=snapshot, offset=1) + p_max ``` @@ -210,7 +210,7 @@ the two translations `ramp` leaves out: a fill, and forwards ```yaml edges: - foreach: [snapshot, generator] + dims: [snapshot, generator] expression: >- shift(p, over=snapshot, offset=1, edge=0) <= shift(p, over=snapshot, offset=-1, edge=0) + p_max @@ -226,7 +226,7 @@ the cyclic translation forwards, which is a fourth symbol again ```yaml ahead: - foreach: [snapshot, generator] + dims: [snapshot, generator] expression: p <= shift(p, over=snapshot, offset=-1, edge='wrap') ``` @@ -240,7 +240,7 @@ two steps of one policy are one step; a zero step is none at all ```yaml composed: - foreach: [snapshot, generator] + dims: [snapshot, generator] expression: shift(shift(p, over=snapshot, offset=1), over=snapshot, offset=1) <= shift(p_max, over=generator, offset=0) ``` @@ -254,7 +254,7 @@ a named offset under a numbered one stays two steps, not their sum ```yaml uncomposed: - foreach: [snapshot, generator] + dims: [snapshot, generator] expression: shift(shift(p, over=snapshot, offset=lead, edge=0), over=snapshot, offset=1) <= p_max ``` @@ -268,7 +268,7 @@ two dimensions translated at one leaf, each with its own policy ```yaml crossed: - foreach: [snapshot, generator] + dims: [snapshot, generator] expression: shift(shift(p, over=snapshot, offset=1, edge='wrap'), over=generator, offset=-1) <= p_max ``` @@ -282,7 +282,7 @@ an offset the data carries, so it prints as a symbol rather than a number ```yaml lead_time: - foreach: [snapshot, generator] + dims: [snapshot, generator] expression: shift(p, over=snapshot, offset=lead, edge=0) <= p_max ``` @@ -296,7 +296,7 @@ a translation partitioned by a lookup: the group rides on the operator ```yaml in_season: - foreach: [snapshot, generator] + dims: [snapshot, generator] expression: p <= shift(p, over=snapshot, offset=1, edge='wrap', by=season_of) ``` @@ -310,7 +310,7 @@ the same group, with a fill: each season's opening row is kept and given a zero ```yaml held_in_season: - foreach: [snapshot, generator] + dims: [snapshot, generator] expression: p <= shift(p, over=snapshot, offset=1, edge=0, by=season_of) ``` @@ -324,7 +324,7 @@ a trailing window of fixed width ```yaml window: - foreach: [snapshot, generator] + dims: [snapshot, generator] expression: sum_back(on, over=snapshot, within=3) <= units ``` @@ -338,7 +338,7 @@ the same window, its width in the data and its edge wrapped ```yaml history: - foreach: [snapshot, generator] + dims: [snapshot, generator] expression: sum_back(on, over=snapshot, within=min_up, edge='wrap') <= units ``` @@ -352,7 +352,7 @@ a window partitioned by a lookup: the group rides on the operator ```yaml seasonal_window: - foreach: [snapshot, generator] + dims: [snapshot, generator] expression: sum_back(on, over=snapshot, within=3, by=season_of) <= units ``` @@ -366,7 +366,7 @@ at(), which re-indexes through a lookup instead of an offset ```yaml pullback: - foreach: [snapshot, bus] + dims: [snapshot, bus] expression: spill <= at(zone_cap, by=zone_of) ``` @@ -380,7 +380,7 @@ one grouping through two maps: the domain carries both conditions ```yaml grouped_twice: - foreach: [snapshot, bus, technology] + dims: [snapshot, bus, technology] expression: sum(p, by=[gen_bus, gen_tech]) <= tech_cap ``` @@ -394,7 +394,7 @@ its adjoint, reading one slot through a pair of labels ```yaml pulled_back_twice: - foreach: [generator] + dims: [generator] expression: units <= at(tech_cap, by=[gen_bus, gen_tech]) ``` @@ -408,7 +408,7 @@ division, both unary signs, a sign beside a sign, floats with and without an exp ```yaml arithmetic: - foreach: [snapshot] + dims: [snapshot] expression: >- sum(p / 2 + -cost - -1e-5 * p + 2.5e-7 * cost + 0.5 * p, over=generator) >= -sum(+p, over=generator) * -3 @@ -424,7 +424,7 @@ a sum naming no dim, whose domain is the one place the dims it took are said ```yaml total: - foreach: [] + dims: [] expression: sum(p) <= budget ``` @@ -438,7 +438,7 @@ a parameter over nothing, and a mask that is a bare parameter ```yaml scalar: - foreach: [generator] + dims: [generator] where: "cost" expression: units <= budget ``` @@ -453,7 +453,7 @@ a mask on a variable's existence, and one on a dimension's label ```yaml running: - foreach: [snapshot, bus] + dims: [snapshot, bus] where: "theta AND snapshot >= 3" expression: theta <= load ``` @@ -468,7 +468,7 @@ a position in a dimension, and the same position within a group ```yaml first: - foreach: [snapshot, generator] + dims: [snapshot, generator] where: "position(snapshot) == 0 OR position(snapshot, by=season_of) == 0" expression: on == 1 ``` @@ -483,7 +483,7 @@ the same two counted from the end, which print against a size rather than as the ```yaml last: - foreach: [snapshot, generator] + dims: [snapshot, generator] where: "position(snapshot) == -1 OR position(snapshot, by=season_of) == -1" expression: on == 0 ``` @@ -498,7 +498,7 @@ a lookup compared to a label, to another lookup, and to nothing ```yaml northern: - foreach: [snapshot, bus] + dims: [snapshot, bus] where: "zone_of == 'north' AND zone_of != area_of AND zone_of" expression: slack <= load ``` @@ -513,7 +513,7 @@ a Greek-named parameter, which is given — so the convention wins and it prints ```yaml efficiency: - foreach: [snapshot, generator] + dims: [snapshot, generator] expression: p <= eta * p_max ``` @@ -527,7 +527,7 @@ the infinity literal, which is the one way infinity prints ```yaml ceiling: - foreach: [bus] + dims: [bus] expression: theta <= inf ``` @@ -541,7 +541,7 @@ a mask that is only the constant true, which the language says is no mask at all ```yaml always: - foreach: [snapshot] + dims: [snapshot] where: "true" expression: spill >= 0 ``` @@ -556,7 +556,7 @@ the same constant *inside* a mask, where it is what the file says and prints ```yaml redundant: - foreach: [snapshot] + dims: [snapshot] where: "True AND spill" expression: spill >= 0 ``` @@ -571,7 +571,7 @@ the other constant mask, which says the rows are none and is worth seeing ```yaml never: - foreach: [snapshot] + dims: [snapshot] where: "false" expression: slack >= 0 ``` @@ -625,7 +625,7 @@ a quantity defined by region: no two cases overlap, and `otherwise` is the rest ```yaml startup_cost: - foreach: [snapshot, generator] + dims: [snapshot, generator] cases: opening: { when: "position(snapshot) == 0", expression: cost } winter: { when: "position(snapshot) > 0 and season_of == 'winter'", expression: cost * 2 } @@ -644,7 +644,7 @@ both bounds, and a where with all three connectives ```yaml p: - foreach: [snapshot, generator] + dims: [snapshot, generator] where: "p_max > 0 AND NOT is_flexible OR p_min > 0" bounds: { lower: p_min, upper: p_max } ``` @@ -659,7 +659,7 @@ lower only ```yaml spill: - foreach: [snapshot] + dims: [snapshot] bounds: { lower: 0 } ``` @@ -673,7 +673,7 @@ upper only ```yaml slack: - foreach: [snapshot] + dims: [snapshot] bounds: { upper: 100 } ``` @@ -687,7 +687,7 @@ unbounded ```yaml theta: - foreach: [bus] + dims: [bus] ``` ```math @@ -700,7 +700,7 @@ a binary domain, which is a set rather than a pair of bounds ```yaml on: - foreach: [snapshot, generator] + dims: [snapshot, generator] domain: binary ``` @@ -714,7 +714,7 @@ an integer domain, which is both: bounds, and where the values live ```yaml units: - foreach: [generator] + dims: [generator] domain: integer bounds: { lower: 0, upper: 10 } ``` @@ -729,7 +729,7 @@ integer with neither bound: the domain is the whole line ```yaml spare: - foreach: [generator] + dims: [generator] domain: integer ``` @@ -739,11 +739,11 @@ spare: #### `reserve` -an empty foreach: a scalar declaration, whose line carries no quantifier +an empty dims: a scalar declaration, whose line carries no quantifier ```yaml reserve: - foreach: [] + dims: [] bounds: { lower: 0 } ``` @@ -757,7 +757,7 @@ scalar too, but masked, so the condition stands with no set beside it ```yaml headroom: - foreach: [] + dims: [] where: "budget" bounds: { lower: 0 } ``` @@ -772,7 +772,7 @@ the family a sos runs along ```yaml weight: - foreach: [snapshot, generator] + dims: [snapshot, generator] bounds: { lower: 0, upper: 1 } ``` diff --git a/examples/commitment.yaml b/examples/commitment.yaml index b46931e8..21fb82e8 100644 --- a/examples/commitment.yaml +++ b/examples/commitment.yaml @@ -26,17 +26,17 @@ parameters: variables: p: description: output of a generator in a snapshot - foreach: [snapshot, generator] + dims: [snapshot, generator] bounds: { lower: 0, upper: p_max } status: description: whether the unit is running in a snapshot - foreach: [snapshot, generator] + dims: [snapshot, generator] domain: binary expressions: previous_status: description: the commitment state a unit carries into a snapshot - foreach: [snapshot, generator] + dims: [snapshot, generator] cases: always_on: when: "not committable" @@ -48,21 +48,21 @@ expressions: constraints: power_balance: - foreach: [snapshot] + dims: [snapshot] expression: sum(p, over=generator) == load upper: description: a unit that is not running produces nothing - foreach: [snapshot, generator] + dims: [snapshot, generator] expression: p <= status * p_max lower: description: and one that is running produces at least its floor - foreach: [snapshot, generator] + dims: [snapshot, generator] expression: p >= status * p_min ramp_up: description: >- one inequality for both regimes — a unit already running is held to `ramp_limit`, a unit starting up to `start_up_limit`. - foreach: [snapshot, generator] + dims: [snapshot, generator] expression: >- p - shift(p, over=snapshot, offset=1, edge=0) <= ramp_limit * previous_status + start_up_limit * (1 - previous_status) diff --git a/examples/dispatch.yaml b/examples/dispatch.yaml index c24be113..318e48ff 100644 --- a/examples/dispatch.yaml +++ b/examples/dispatch.yaml @@ -16,13 +16,13 @@ parameters: variables: p: description: output of a generator in a snapshot - foreach: [snapshot, generator] + dims: [snapshot, generator] where: "p_max > 0" bounds: { lower: 0, upper: p_max } constraints: power_balance: - foreach: [snapshot] + dims: [snapshot] expression: sum(p, over=generator) == load objective: diff --git a/examples/operators/at.yaml b/examples/operators/at.yaml index 3ceb94ec..ff381077 100644 --- a/examples/operators/at.yaml +++ b/examples/operators/at.yaml @@ -18,12 +18,12 @@ parameters: variables: p: - foreach: [snapshot] + dims: [snapshot] bounds: { lower: 0 } constraints: within_cap: - foreach: [snapshot] + dims: [snapshot] expression: p <= at(cap, by=period_of) objective: { sense: minimize, expression: sum(p) } diff --git a/examples/operators/dual.yaml b/examples/operators/dual.yaml index 89a82b0b..35f3ed12 100644 --- a/examples/operators/dual.yaml +++ b/examples/operators/dual.yaml @@ -12,12 +12,12 @@ parameters: variables: p: - foreach: [snapshot] + dims: [snapshot] bounds: { lower: 0 } constraints: balance: - foreach: [snapshot] + dims: [snapshot] expression: p >= load expressions: diff --git a/examples/operators/shift.yaml b/examples/operators/shift.yaml index a00d403b..1851a45f 100644 --- a/examples/operators/shift.yaml +++ b/examples/operators/shift.yaml @@ -11,12 +11,12 @@ dimensions: variables: p: - foreach: [snapshot] + dims: [snapshot] bounds: { lower: 0 } constraints: no_faster_than_before: - foreach: [snapshot] + dims: [snapshot] expression: p <= shift(p, over=snapshot, offset=1) objective: { sense: minimize, expression: sum(p) } diff --git a/examples/operators/shift_by_parameter.yaml b/examples/operators/shift_by_parameter.yaml index 059e203d..6913471b 100644 --- a/examples/operators/shift_by_parameter.yaml +++ b/examples/operators/shift_by_parameter.yaml @@ -17,12 +17,12 @@ parameters: variables: order: - foreach: [technology, month] + dims: [technology, month] bounds: { lower: 0 } constraints: arrives_after_its_lead: - foreach: [technology, month] + dims: [technology, month] expression: shift(order, over=month, offset=lead, edge=0) >= demand objective: { sense: minimize, expression: sum(order) } diff --git a/examples/operators/shift_edge.yaml b/examples/operators/shift_edge.yaml index 7e03bba1..d015aa2f 100644 --- a/examples/operators/shift_edge.yaml +++ b/examples/operators/shift_edge.yaml @@ -11,12 +11,12 @@ dimensions: variables: p: - foreach: [snapshot] + dims: [snapshot] bounds: { lower: 0 } constraints: no_faster_than_before: - foreach: [snapshot] + dims: [snapshot] expression: p <= shift(p, over=snapshot, offset=1, edge=0) objective: { sense: minimize, expression: sum(p) } diff --git a/examples/operators/shift_partitioned.yaml b/examples/operators/shift_partitioned.yaml index caf2099e..e0c0cdaa 100644 --- a/examples/operators/shift_partitioned.yaml +++ b/examples/operators/shift_partitioned.yaml @@ -15,12 +15,12 @@ lookups: variables: p: - foreach: [snapshot] + dims: [snapshot] bounds: { lower: 0 } constraints: no_faster_than_before_in_season: - foreach: [snapshot] + dims: [snapshot] expression: p <= shift(p, over=snapshot, offset=1, edge='wrap', by=season_of) objective: { sense: minimize, expression: sum(p) } diff --git a/examples/operators/shift_wrap.yaml b/examples/operators/shift_wrap.yaml index f6dfe86a..4283ccca 100644 --- a/examples/operators/shift_wrap.yaml +++ b/examples/operators/shift_wrap.yaml @@ -11,12 +11,12 @@ dimensions: variables: p: - foreach: [snapshot] + dims: [snapshot] bounds: { lower: 0 } constraints: no_faster_than_before: - foreach: [snapshot] + dims: [snapshot] expression: p <= shift(p, over=snapshot, offset=1, edge='wrap') objective: { sense: minimize, expression: sum(p) } diff --git a/examples/operators/sum.yaml b/examples/operators/sum.yaml index 69ef6172..347d8b9f 100644 --- a/examples/operators/sum.yaml +++ b/examples/operators/sum.yaml @@ -13,12 +13,12 @@ parameters: variables: p: - foreach: [snapshot, generator] + dims: [snapshot, generator] bounds: { lower: 0 } constraints: fleet_total: - foreach: [snapshot] + dims: [snapshot] expression: sum(p, over=generator) <= limit objective: { sense: minimize, expression: sum(p) } diff --git a/examples/operators/sum_all.yaml b/examples/operators/sum_all.yaml index 30f6052f..0d2a691a 100644 --- a/examples/operators/sum_all.yaml +++ b/examples/operators/sum_all.yaml @@ -13,12 +13,12 @@ parameters: variables: p: - foreach: [snapshot, generator] + dims: [snapshot, generator] bounds: { lower: 0 } constraints: fleet_budget: - foreach: [] + dims: [] expression: sum(p) <= budget objective: { sense: minimize, expression: sum(p) } diff --git a/examples/operators/sum_back.yaml b/examples/operators/sum_back.yaml index 5b735e4e..0f31e586 100644 --- a/examples/operators/sum_back.yaml +++ b/examples/operators/sum_back.yaml @@ -15,15 +15,15 @@ parameters: variables: started: - foreach: [unit, hour] + dims: [unit, hour] domain: binary on: - foreach: [unit, hour] + dims: [unit, hour] domain: binary constraints: stays_up_its_own_time: - foreach: [unit, hour] + dims: [unit, hour] expression: sum_back(started, over=hour, within=3) <= on objective: { sense: minimize, expression: sum(on) } diff --git a/examples/operators/sum_back_by_parameter.yaml b/examples/operators/sum_back_by_parameter.yaml index eb950144..05bf5923 100644 --- a/examples/operators/sum_back_by_parameter.yaml +++ b/examples/operators/sum_back_by_parameter.yaml @@ -15,15 +15,15 @@ parameters: variables: started: - foreach: [unit, hour] + dims: [unit, hour] domain: binary on: - foreach: [unit, hour] + dims: [unit, hour] domain: binary constraints: stays_up_its_own_time: - foreach: [unit, hour] + dims: [unit, hour] expression: sum_back(started, over=hour, within=min_up) <= on objective: { sense: minimize, expression: sum(on) } diff --git a/examples/operators/sum_back_partitioned.yaml b/examples/operators/sum_back_partitioned.yaml index fe4972b4..0f748894 100644 --- a/examples/operators/sum_back_partitioned.yaml +++ b/examples/operators/sum_back_partitioned.yaml @@ -17,15 +17,15 @@ lookups: variables: started: - foreach: [unit, hour] + dims: [unit, hour] domain: binary on: - foreach: [unit, hour] + dims: [unit, hour] domain: binary constraints: stays_up_inside_its_day: - foreach: [unit, hour] + dims: [unit, hour] expression: sum_back(started, over=hour, within=3, by=day_of) <= on objective: { sense: minimize, expression: sum(on) } diff --git a/examples/operators/sum_back_wrap.yaml b/examples/operators/sum_back_wrap.yaml index f0677d03..504e0f42 100644 --- a/examples/operators/sum_back_wrap.yaml +++ b/examples/operators/sum_back_wrap.yaml @@ -15,15 +15,15 @@ parameters: variables: started: - foreach: [unit, hour] + dims: [unit, hour] domain: binary on: - foreach: [unit, hour] + dims: [unit, hour] domain: binary constraints: stays_up_its_own_time: - foreach: [unit, hour] + dims: [unit, hour] expression: sum_back(started, over=hour, within=min_up, edge='wrap') <= on objective: { sense: minimize, expression: sum(on) } diff --git a/examples/operators/sum_by.yaml b/examples/operators/sum_by.yaml index 8d0ad97b..a36b221f 100644 --- a/examples/operators/sum_by.yaml +++ b/examples/operators/sum_by.yaml @@ -20,12 +20,12 @@ parameters: variables: p: - foreach: [snapshot, generator] + dims: [snapshot, generator] bounds: { lower: 0 } constraints: bus_total: - foreach: [snapshot, bus] + dims: [snapshot, bus] expression: sum(p, by=gen_bus) <= limit objective: { sense: minimize, expression: sum(p) } diff --git a/examples/operators/sum_by_lookups.yaml b/examples/operators/sum_by_lookups.yaml index 6db2a0f7..f1b47f2f 100644 --- a/examples/operators/sum_by_lookups.yaml +++ b/examples/operators/sum_by_lookups.yaml @@ -22,12 +22,12 @@ parameters: variables: p: - foreach: [snapshot, generator] + dims: [snapshot, generator] bounds: { lower: 0 } constraints: bus_technology_total: - foreach: [snapshot, bus, technology] + dims: [snapshot, bus, technology] expression: sum(p, by=[gen_bus, gen_tech]) <= limit objective: { sense: minimize, expression: sum(p) } diff --git a/examples/piecewise.yaml b/examples/piecewise.yaml index a63168bc..19bb7d0b 100644 --- a/examples/piecewise.yaml +++ b/examples/piecewise.yaml @@ -34,13 +34,13 @@ parameters: variables: p: description: dispatched power - foreach: [snapshot, generator] + dims: [snapshot, generator] bounds: lower: 0 upper: p_max op_cost: description: operating cost, piecewise-linear in dispatch - foreach: [snapshot, generator] + dims: [snapshot, generator] bounds: lower: 0 @@ -57,7 +57,7 @@ piecewise: constraints: balance: - foreach: [snapshot] + dims: [snapshot] expression: sum(p, over=generator) == load objective: diff --git a/examples/piecewise_lp.yaml b/examples/piecewise_lp.yaml index 7eea5c4b..05318da2 100644 --- a/examples/piecewise_lp.yaml +++ b/examples/piecewise_lp.yaml @@ -37,13 +37,13 @@ parameters: variables: p: description: dispatched power - foreach: [snapshot, generator] + dims: [snapshot, generator] bounds: lower: 0 upper: p_max op_cost: description: operating cost, held above every segment of the generator's curve - foreach: [snapshot, generator] + dims: [snapshot, generator] bounds: lower: 0 @@ -62,7 +62,7 @@ piecewise: constraints: balance: - foreach: [snapshot] + dims: [snapshot] expression: sum(p, over=generator) == load objective: diff --git a/examples/ports/transport_pwl.yaml b/examples/ports/transport_pwl.yaml index 144b616a..febb2e56 100644 --- a/examples/ports/transport_pwl.yaml +++ b/examples/ports/transport_pwl.yaml @@ -42,14 +42,14 @@ parameters: variables: shipment: description: cases shipped from a plant to a market - foreach: [plant, market] + dims: [plant, market] bounds: lower: 0 scaled: description: >- what the objective is charged on — the square root of the shipment, read off the curve rather than computed - foreach: [plant, market] + dims: [plant, market] bounds: lower: 0 @@ -70,10 +70,10 @@ piecewise: constraints: within_capacity: - foreach: [plant] + dims: [plant] expression: sum(shipment, over=market) <= capacity meet_demand: - foreach: [market] + dims: [market] expression: sum(shipment, over=plant) >= demand objective: diff --git a/examples/pypsa.yaml b/examples/pypsa.yaml index 006372e0..0f15cde4 100644 --- a/examples/pypsa.yaml +++ b/examples/pypsa.yaml @@ -463,28 +463,28 @@ lookups: variables: Generator_p: description: "`Generator-p` — output of a generator in a snapshot" - foreach: [snapshot, generator] + dims: [snapshot, generator] Link_p: description: >- `Link-p` — PyPSA's `p0`, the flow measured at the `Link_bus0` end: a positive value withdraws there and injects at every bus the link's output ports deliver to - foreach: [snapshot, link] + dims: [snapshot, link] StorageUnit_p_dispatch: description: "`StorageUnit-p_dispatch` — power delivered to the bus" - foreach: [snapshot, storage_unit] + dims: [snapshot, storage_unit] StorageUnit_p_store: description: "`StorageUnit-p_store` — power drawn from the bus into charge" - foreach: [snapshot, storage_unit] + dims: [snapshot, storage_unit] StorageUnit_state_of_charge: description: "`StorageUnit-state_of_charge` — energy held at the end of a snapshot" - foreach: [snapshot, storage_unit] + dims: [snapshot, storage_unit] StorageUnit_spill: description: >- `StorageUnit-spill` — inflow passed on unused. Zero where there is no inflow, so the balance keeps its row there; the bounds are PyPSA's, on the variable rather than as rows - foreach: [snapshot, storage_unit] + dims: [snapshot, storage_unit] where: "StorageUnit_inflow > 0" absence: zero bounds: @@ -492,13 +492,13 @@ variables: upper: StorageUnit_inflow Store_e: description: "`Store-e` — energy held at the end of a snapshot" - foreach: [snapshot, store] + dims: [snapshot, store] Store_p: description: "`Store-p` — power delivered to the bus; charging is negative" - foreach: [snapshot, store] + dims: [snapshot, store] Generator_n_mod: description: "`Generator-n_mod` — how many modules of an extendable modular build" - foreach: [generator] + dims: [generator] where: Generator_p_nom_extendable AND Generator_p_nom_mod > 0 domain: integer bounds: @@ -508,21 +508,21 @@ variables: `Generator-status` — how much of a committable unit is on: an integer the rows below cap at one, or at the module count where the build is modular - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable domain: integer bounds: lower: 0 Generator_start_up: description: "`Generator-start_up` — how much of a committable unit turns on this snapshot, capped as the status is" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable domain: integer bounds: lower: 0 Generator_shut_down: description: "`Generator-shut_down` — how much of a committable unit turns off this snapshot, capped as the status is" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable domain: integer bounds: @@ -531,36 +531,36 @@ variables: description: >- `Line-s` — PyPSA's `p0`, the flow measured at the `Line_bus0` end: a positive value withdraws there and injects at `Line_bus1`, lossless - foreach: [snapshot, line] + dims: [snapshot, line] Line_s_nom_ext: description: >- `Line-s_nom` — nominal apparent power where it is a decision; the parameter of the same PyPSA name carries the fixed regime - foreach: [line] + dims: [line] where: Line_s_nom_extendable Generator_p_nom_ext: description: >- `Generator-p_nom` — nominal power where it is a decision; the parameter of the same PyPSA name carries the fixed regime - foreach: [generator] + dims: [generator] where: Generator_p_nom_extendable Link_p_nom_ext: description: >- `Link-p_nom` — nominal power where it is a decision; the parameter of the same PyPSA name carries the fixed regime - foreach: [link] + dims: [link] where: Link_p_nom_extendable StorageUnit_p_nom_ext: description: >- `StorageUnit-p_nom` — nominal power where it is a decision; the parameter of the same PyPSA name carries the fixed regime - foreach: [storage_unit] + dims: [storage_unit] where: StorageUnit_p_nom_extendable Store_e_nom_ext: description: >- `Store-e_nom` — nominal capacity where it is a decision; the parameter of the same PyPSA name carries the fixed regime - foreach: [store] + dims: [store] where: Store_e_nom_extendable expressions: @@ -568,7 +568,7 @@ expressions: description: >- the commitment state a generator carries into a snapshot — the state it brought into the horizon at the first, the previous snapshot's after that - foreach: [snapshot, generator] + dims: [snapshot, generator] cases: opening: { when: "position(snapshot) == 0", expression: Generator_status_initial } otherwise: shift(Generator_status, over=snapshot, offset=1) @@ -577,13 +577,13 @@ expressions: the output a generator carries into a snapshot — nothing at the start of the horizon, which is why a unit that came in running carries no ramp row there - foreach: [snapshot, generator] + dims: [snapshot, generator] cases: opening: { when: "position(snapshot) == 0", expression: 0 } otherwise: shift(Generator_p, over=snapshot, offset=1) Generator_p_nom_effective: description: the build a generator's limits are taken against — the chosen one where it is extendable, the given one otherwise - foreach: [generator] + dims: [generator] cases: extendable: { when: Generator_p_nom_extendable, expression: Generator_p_nom_ext } otherwise: Generator_p_nom @@ -592,7 +592,7 @@ expressions: how far a generator may raise output between two snapshots — its ramp limit of the build while it stays on, plus its start-up ramp in the snapshot it turns on - foreach: [snapshot, generator] + dims: [snapshot, generator] cases: committed: when: Generator_committable @@ -606,7 +606,7 @@ expressions: how far a generator may lower output between two snapshots — its ramp limit of the build while it stays on, plus its shut-down ramp in the snapshot it turns off - foreach: [snapshot, generator] + dims: [snapshot, generator] cases: committed: when: Generator_committable @@ -617,7 +617,7 @@ expressions: otherwise: Generator_ramp_limit_down * Generator_p_nom_effective Link_p_nom_effective: description: the build a link's limits are taken against — the chosen one where it is extendable, the given one otherwise - foreach: [link] + dims: [link] cases: extendable: { when: Link_p_nom_extendable, expression: Link_p_nom_ext } otherwise: Link_p_nom @@ -627,7 +627,7 @@ expressions: standing loss where it is cyclic, the given initial charge at the start of the horizon, which no standing loss has touched yet, and the previous snapshot's less standing loss otherwise - foreach: [snapshot, storage_unit] + dims: [snapshot, storage_unit] cases: cyclic: when: StorageUnit_cyclic_state_of_charge @@ -642,7 +642,7 @@ expressions: standing loss where it is cyclic, the given initial energy at the start of the horizon, which no standing loss has touched yet, and the previous snapshot's less standing loss otherwise - foreach: [snapshot, store] + dims: [snapshot, store] cases: cyclic: when: Store_e_cyclic @@ -659,7 +659,7 @@ expressions: is not the flow still in transit at the first snapshots is lost. A port that does not delay (`delay` zero) delivers its flow unshifted, cyclic or not - foreach: [snapshot, link_output] + dims: [snapshot, link_output] cases: wrapping: when: Link_output_cyclic_delay @@ -705,134 +705,134 @@ expressions: constraints: Generator_fix_p_lower: description: "`Generator-fix-p-lower` — a fixed generator outputs at least its minimum" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: not Generator_p_nom_extendable AND not Generator_committable expression: Generator_p >= Generator_p_min_pu * Generator_p_nom Generator_fix_p_upper: description: "`Generator-fix-p-upper` — a fixed generator outputs at most what is available" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: not Generator_p_nom_extendable AND not Generator_committable expression: Generator_p <= Generator_p_max_pu * Generator_p_nom Link_fix_p_lower: description: "`Link-fix-p-lower` — a fixed link carries at least its minimum, negative for the other way" - foreach: [snapshot, link] + dims: [snapshot, link] where: not Link_p_nom_extendable expression: Link_p >= Link_p_min_pu * Link_p_nom Link_fix_p_upper: description: "`Link-fix-p-upper` — a fixed link carries at most its nominal power" - foreach: [snapshot, link] + dims: [snapshot, link] where: not Link_p_nom_extendable expression: Link_p <= Link_p_max_pu * Link_p_nom Generator_ext_p_lower: description: "`Generator-ext-p-lower` — an extendable generator outputs at least its minimum of the chosen build" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_p_nom_extendable AND not Generator_committable expression: Generator_p >= Generator_p_min_pu * Generator_p_nom_ext Generator_ext_p_upper: description: "`Generator-ext-p-upper` — an extendable generator outputs at most what is available of the chosen build" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_p_nom_extendable AND not Generator_committable expression: Generator_p <= Generator_p_max_pu * Generator_p_nom_ext Generator_ext_p_nom_lower: description: "`Generator-ext-p_nom-lower` — the chosen build is at least its floor" - foreach: [generator] + dims: [generator] where: Generator_p_nom_extendable expression: Generator_p_nom_ext >= Generator_p_nom_min Generator_ext_p_nom_upper: description: "`Generator-ext-p_nom-upper` — the chosen build is at most its cap; a cap of infinity is no row" - foreach: [generator] + dims: [generator] where: Generator_p_nom_extendable AND Generator_p_nom_max expression: Generator_p_nom_ext <= Generator_p_nom_max Generator_p_nom_set: description: "`Generator-p_nom_set` — the chosen build pinned, wherever a value is given" - foreach: [generator] + dims: [generator] where: Generator_p_nom_extendable AND Generator_p_nom_set expression: Generator_p_nom_ext == Generator_p_nom_set Generator_e_sum_min: description: "`Generator-e_sum_min` — energy over the horizon is at least its floor; a floor of minus infinity is no row" - foreach: [generator] + dims: [generator] where: Generator_e_sum_min expression: sum(Generator_p * snapshot_weightings_generators, over=snapshot) >= Generator_e_sum_min Generator_e_sum_max: description: "`Generator-e_sum_max` — energy over the horizon is at most its budget; a budget of infinity is no row" - foreach: [generator] + dims: [generator] where: Generator_e_sum_max expression: sum(Generator_p * snapshot_weightings_generators, over=snapshot) <= Generator_e_sum_max Link_ext_p_lower: description: "`Link-ext-p-lower` — an extendable link carries at least its minimum of the chosen build, negative for the other way" - foreach: [snapshot, link] + dims: [snapshot, link] where: Link_p_nom_extendable expression: Link_p >= Link_p_min_pu * Link_p_nom_ext Link_ext_p_upper: description: "`Link-ext-p-upper` — an extendable link carries at most the chosen build" - foreach: [snapshot, link] + dims: [snapshot, link] where: Link_p_nom_extendable expression: Link_p <= Link_p_max_pu * Link_p_nom_ext Link_ext_p_nom_lower: description: "`Link-ext-p_nom-lower` — the chosen build is at least its floor" - foreach: [link] + dims: [link] where: Link_p_nom_extendable expression: Link_p_nom_ext >= Link_p_nom_min Link_ext_p_nom_upper: description: "`Link-ext-p_nom-upper` — the chosen build is at most its cap; a cap of infinity is no row" - foreach: [link] + dims: [link] where: Link_p_nom_extendable AND Link_p_nom_max expression: Link_p_nom_ext <= Link_p_nom_max Link_p_nom_set: description: "`Link-p_nom_set` — the chosen build pinned, wherever a value is given" - foreach: [link] + dims: [link] where: Link_p_nom_extendable AND Link_p_nom_set expression: Link_p_nom_ext == Link_p_nom_set StorageUnit_fix_p_dispatch_lower: description: "`StorageUnit-fix-p_dispatch-lower` — dispatch is non-negative" - foreach: [snapshot, storage_unit] + dims: [snapshot, storage_unit] where: not StorageUnit_p_nom_extendable expression: StorageUnit_p_dispatch >= 0 StorageUnit_fix_p_dispatch_upper: description: "`StorageUnit-fix-p_dispatch-upper` — a fixed unit dispatches at most its nominal power" - foreach: [snapshot, storage_unit] + dims: [snapshot, storage_unit] where: not StorageUnit_p_nom_extendable expression: StorageUnit_p_dispatch <= StorageUnit_p_max_pu * StorageUnit_p_nom StorageUnit_fix_p_store_lower: description: "`StorageUnit-fix-p_store-lower` — storing is non-negative" - foreach: [snapshot, storage_unit] + dims: [snapshot, storage_unit] where: not StorageUnit_p_nom_extendable expression: StorageUnit_p_store >= 0 StorageUnit_fix_p_store_upper: description: >- `StorageUnit-fix-p_store-upper` — a fixed unit stores at most its nominal power, the minimum-per-unit column carrying that cap negated - foreach: [snapshot, storage_unit] + dims: [snapshot, storage_unit] where: not StorageUnit_p_nom_extendable expression: StorageUnit_p_store <= -StorageUnit_p_min_pu * StorageUnit_p_nom StorageUnit_fix_state_of_charge_lower: description: "`StorageUnit-fix-state_of_charge-lower` — charge is non-negative" - foreach: [snapshot, storage_unit] + dims: [snapshot, storage_unit] where: not StorageUnit_p_nom_extendable expression: StorageUnit_state_of_charge >= 0 StorageUnit_fix_state_of_charge_upper: description: "`StorageUnit-fix-state_of_charge-upper` — a fixed unit holds at most its hours at nominal power" - foreach: [snapshot, storage_unit] + dims: [snapshot, storage_unit] where: not StorageUnit_p_nom_extendable expression: StorageUnit_state_of_charge <= StorageUnit_max_hours * StorageUnit_p_nom Generator_com_p_lower: description: "`Generator-com-p-lower` — a committed unit outputs at least its minimum; off, at least nothing" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable AND not Generator_p_nom_extendable expression: Generator_p >= Generator_p_min_pu * Generator_p_nom * Generator_status Generator_com_p_upper: description: "`Generator-com-p-upper` — a committed unit outputs at most what is available; off, at most nothing" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable AND not Generator_p_nom_extendable expression: Generator_p <= Generator_p_max_pu * Generator_p_nom * Generator_status Generator_com_transition_start_up: description: "`Generator-com-transition-start-up` — turning on is a start, counted against the state the unit carried into the snapshot" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable expression: Generator_start_up >= Generator_status - Generator_previous_status Generator_com_transition_shut_down: description: "`Generator-com-transition-shut-down` — turning off is a stop, counted against the state the unit carried into the snapshot" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable expression: Generator_shut_down >= Generator_previous_status - Generator_status Generator_com_up_time: @@ -840,17 +840,17 @@ constraints: `Generator-com-up-time` — a unit started within its own minimum up time is still on. The first snapshot's share of the window is the brought-in up time's, which the must-stay-up mask carries - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable AND Generator_min_up_time > 0 AND position(snapshot) > 0 expression: sum_back(Generator_start_up, over=snapshot, within=Generator_min_up_time) <= Generator_status Generator_com_down_time: description: "`Generator-com-down-time` — a unit stopped within its own minimum down time is still off" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable AND Generator_min_down_time > 0 AND position(snapshot) > 0 expression: sum_back(Generator_shut_down, over=snapshot, within=Generator_min_down_time) <= 1 - Generator_status Generator_com_status_must_stay_up: description: "`Generator-com-status-min_up_time_must_stay_up` — a unit still serving the up time it brought in stays on" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable AND Generator_must_stay_up expression: Generator_status == 1 Generator_p_ramp_limit_up_run_big_m: @@ -858,7 +858,7 @@ constraints: `Generator-p-ramp_limit_up-run-bigM` — a committed extendable unit raises output no faster than its limit of the chosen build; the big M releases the row in the snapshot it turns on - foreach: [snapshot, generator] + dims: [snapshot, generator] where: >- Generator_committable AND Generator_p_nom_extendable AND Generator_ramp_limit_up AND (position(snapshot) > 0 OR Generator_status_initial == 0) @@ -871,7 +871,7 @@ constraints: `Generator-p-ramp_limit_up-start-bigM` — in the snapshot it turns on, a committed extendable unit ramps no further than its start-up ramp of the chosen build; the big M releases the row everywhere else - foreach: [snapshot, generator] + dims: [snapshot, generator] where: >- Generator_committable AND Generator_p_nom_extendable AND Generator_ramp_limit_up AND (position(snapshot) > 0 OR Generator_status_initial == 0) @@ -884,7 +884,7 @@ constraints: `Generator-p-ramp_limit_down-run-bigM` — a committed extendable unit lowers output no faster than its limit of the chosen build; the big M releases the row in the snapshot it turns off - foreach: [snapshot, generator] + dims: [snapshot, generator] where: >- Generator_committable AND Generator_p_nom_extendable AND Generator_ramp_limit_down AND (position(snapshot) > 0 OR Generator_status_initial == 0) @@ -897,7 +897,7 @@ constraints: `Generator-p-ramp_limit_down-shut-bigM` — in the snapshot it turns off, a committed extendable unit ramps no further than its shut-down ramp of the chosen build; the big M releases the row everywhere else - foreach: [snapshot, generator] + dims: [snapshot, generator] where: >- Generator_committable AND Generator_p_nom_extendable AND Generator_ramp_limit_down AND (position(snapshot) > 0 OR Generator_status_initial == 0) @@ -907,26 +907,26 @@ constraints: + Generator_big_m - Generator_big_m * Generator_shut_down Generator_p_nom_modularity: description: "`Generator-p_nom_modularity` — the chosen build is a whole number of modules" - foreach: [generator] + dims: [generator] where: Generator_p_nom_extendable AND Generator_p_nom_mod > 0 expression: Generator_p_nom_ext == Generator_p_nom_mod * Generator_n_mod Generator_com_ext_p_upper_cap: description: >- `Generator-com-ext-p-upper-cap` — a committed extendable unit outputs at most what is available of the chosen build, whatever its status - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable AND Generator_p_nom_extendable AND NOT (Generator_p_nom_mod > 0) expression: Generator_p <= Generator_p_max_pu * Generator_p_nom_ext Generator_com_ext_p_upper_big_m: description: "`Generator-com-ext-p-upper-bigM` — off, a unit outputs nothing; on, the big M is no bound" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable AND Generator_p_nom_extendable AND NOT (Generator_p_nom_mod > 0) expression: Generator_p <= Generator_big_m * Generator_status Generator_com_ext_p_lower: description: >- `Generator-com-ext-p-lower` — a committed extendable unit outputs at least its minimum of the chosen build; off, the big M releases the row - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable AND Generator_p_nom_extendable AND NOT (Generator_p_nom_mod > 0) expression: >- Generator_p >= @@ -937,7 +937,7 @@ constraints: `Generator-com-ext-p-lower-nonneg` — where no minimum-per-unit is negative, output is also plainly non-negative, a row the big-M lower cannot assert while the unit is off - foreach: [snapshot, generator] + dims: [snapshot, generator] where: >- Generator_committable AND Generator_p_nom_extendable AND Generator_p_min_pu_nonneg AND NOT (Generator_p_nom_mod > 0) @@ -946,14 +946,14 @@ constraints: description: >- `Generator-com-mod-p-lower` — a committed modular unit outputs at least its minimum of one module, whether the build is fixed or a decision - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable AND Generator_p_nom_mod > 0 expression: Generator_p >= Generator_p_min_pu * Generator_p_nom_mod * Generator_status Generator_com_mod_p_upper: description: >- `Generator-com-mod-p-upper` — a committed modular unit outputs at most one module's share, whether the build is fixed or a decision - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable AND Generator_p_nom_mod > 0 expression: Generator_p <= Generator_p_max_pu * Generator_p_nom_mod * Generator_status Generator_status_p_fixed_upper: @@ -961,7 +961,7 @@ constraints: `Generator-status-p-fixed-upper` — a status is at most the modules in place, an explicit row as PyPSA writes it: one where the build is not modular, and the fixed build's whole count of modules where it is - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable AND NOT (Generator_p_nom_extendable AND Generator_p_nom_mod > 0) expression: Generator_status <= Generator_modules_installed Generator_start_up_p_fixed_upper: @@ -969,7 +969,7 @@ constraints: `Generator-start_up-p-fixed-upper` — a start is at most the modules in place, an explicit row as PyPSA writes it: one where the build is not modular, and the fixed build's whole count of modules where it is - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable AND NOT (Generator_p_nom_extendable AND Generator_p_nom_mod > 0) expression: Generator_start_up <= Generator_modules_installed Generator_shut_down_p_fixed_upper: @@ -977,62 +977,62 @@ constraints: `Generator-shut_down-p-fixed-upper` — a stop is at most the modules in place, an explicit row as PyPSA writes it: one where the build is not modular, and the fixed build's whole count of modules where it is - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable AND NOT (Generator_p_nom_extendable AND Generator_p_nom_mod > 0) expression: Generator_shut_down <= Generator_modules_installed Generator_status_p_nom_variable_upper: description: "`Generator-status-p_nom-variable-upper` — a modular unit is on only where a module is built" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable AND Generator_p_nom_extendable AND Generator_p_nom_mod > 0 expression: Generator_status <= Generator_n_mod Generator_start_up_p_nom_variable_upper: description: "`Generator-start_up-p_nom-variable-upper` — a modular unit starts only where a module is built" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable AND Generator_p_nom_extendable AND Generator_p_nom_mod > 0 expression: Generator_start_up <= Generator_n_mod Generator_shut_down_p_nom_variable_upper: description: "`Generator-shut_down-p_nom-variable-upper` — a modular unit stops only where a module is built" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable AND Generator_p_nom_extendable AND Generator_p_nom_mod > 0 expression: Generator_shut_down <= Generator_n_mod Line_fix_s_lower: description: "`Line-fix-s-lower` — a fixed line carries at least the negative of its rating" - foreach: [snapshot, line] + dims: [snapshot, line] where: not Line_s_nom_extendable expression: Line_s >= -Line_s_max_pu * Line_s_nom Line_fix_s_upper: description: "`Line-fix-s-upper` — a fixed line carries at most its rating" - foreach: [snapshot, line] + dims: [snapshot, line] where: not Line_s_nom_extendable expression: Line_s <= Line_s_max_pu * Line_s_nom Line_ext_s_lower: description: "`Line-ext-s-lower` — an extendable line carries at least the negative of its rating of the chosen build" - foreach: [snapshot, line] + dims: [snapshot, line] where: Line_s_nom_extendable expression: Line_s >= -Line_s_max_pu * Line_s_nom_ext Line_ext_s_upper: description: "`Line-ext-s-upper` — an extendable line carries at most its rating of the chosen build" - foreach: [snapshot, line] + dims: [snapshot, line] where: Line_s_nom_extendable expression: Line_s <= Line_s_max_pu * Line_s_nom_ext Line_ext_s_nom_lower: description: "`Line-ext-s_nom-lower` — the chosen build is at least its floor" - foreach: [line] + dims: [line] where: Line_s_nom_extendable expression: Line_s_nom_ext >= Line_s_nom_min Line_ext_s_nom_upper: description: "`Line-ext-s_nom-upper` — the chosen build is at most its cap; a cap of infinity is no row" - foreach: [line] + dims: [line] where: Line_s_nom_extendable AND Line_s_nom_max expression: Line_s_nom_ext <= Line_s_nom_max Line_s_nom_set: description: "`Line-s_nom_set` — the chosen build pinned, wherever a value is given" - foreach: [line] + dims: [line] where: Line_s_nom_extendable AND Line_s_nom_set expression: Line_s_nom_ext == Line_s_nom_set Line_s_set: description: "`Line-s_set` — flow pinned to the given schedule, wherever one is given" - foreach: [snapshot, line] + dims: [snapshot, line] where: Line_s_set expression: Line_s == Line_s_set Kirchhoff_Voltage_Law: @@ -1040,7 +1040,7 @@ constraints: `Kirchhoff-Voltage-Law` — around every independent cycle the impedance-weighted flows sum to nothing, which is what makes the linear power flow physical rather than transport - foreach: [snapshot, cycle] + dims: [snapshot, cycle] expression: sum(Line_s * Line_cycle_weight, over=line) == 0 Generator_p_ramp_limit_up: description: >- @@ -1049,7 +1049,7 @@ constraints: start-up ramp in the snapshot it turns on. A unit that came into the horizon running brought an unknown output, so it carries no row at the first snapshot — nor does any unit a big M releases instead - foreach: [snapshot, generator] + dims: [snapshot, generator] where: >- Generator_ramp_limit_up AND NOT (Generator_committable AND Generator_p_nom_extendable) @@ -1062,7 +1062,7 @@ constraints: shut-down ramp in the snapshot it turns off. A unit that came into the horizon running brought an unknown output, so it carries no row at the first snapshot — nor does any unit a big M releases instead - foreach: [snapshot, generator] + dims: [snapshot, generator] where: >- Generator_ramp_limit_down AND NOT (Generator_committable AND Generator_p_nom_extendable) @@ -1073,59 +1073,59 @@ constraints: `Link-p-ramp_limit_up` — a link raises flow no faster than its limit of the build. The translated term vacates the first snapshot, where a plain optimize builds no row either - foreach: [snapshot, link] + dims: [snapshot, link] where: Link_ramp_limit_up expression: Link_p - shift(Link_p, over=snapshot, offset=1) <= Link_ramp_limit_up * Link_p_nom_effective Link_p_ramp_limit_down: description: "`Link-p-ramp_limit_down` — a link lowers flow no faster than its limit of the build" - foreach: [snapshot, link] + dims: [snapshot, link] where: Link_ramp_limit_down expression: shift(Link_p, over=snapshot, offset=1) - Link_p <= Link_ramp_limit_down * Link_p_nom_effective StorageUnit_ext_p_dispatch_lower: description: "`StorageUnit-ext-p_dispatch-lower` — dispatch is non-negative" - foreach: [snapshot, storage_unit] + dims: [snapshot, storage_unit] where: StorageUnit_p_nom_extendable expression: StorageUnit_p_dispatch >= 0 StorageUnit_ext_p_dispatch_upper: description: "`StorageUnit-ext-p_dispatch-upper` — an extendable unit dispatches at most the chosen build" - foreach: [snapshot, storage_unit] + dims: [snapshot, storage_unit] where: StorageUnit_p_nom_extendable expression: StorageUnit_p_dispatch <= StorageUnit_p_max_pu * StorageUnit_p_nom_ext StorageUnit_ext_p_store_lower: description: "`StorageUnit-ext-p_store-lower` — storing is non-negative" - foreach: [snapshot, storage_unit] + dims: [snapshot, storage_unit] where: StorageUnit_p_nom_extendable expression: StorageUnit_p_store >= 0 StorageUnit_ext_p_store_upper: description: >- `StorageUnit-ext-p_store-upper` — an extendable unit stores at most the chosen build, the minimum-per-unit column carrying that cap negated - foreach: [snapshot, storage_unit] + dims: [snapshot, storage_unit] where: StorageUnit_p_nom_extendable expression: StorageUnit_p_store <= -StorageUnit_p_min_pu * StorageUnit_p_nom_ext StorageUnit_ext_state_of_charge_lower: description: "`StorageUnit-ext-state_of_charge-lower` — charge is non-negative" - foreach: [snapshot, storage_unit] + dims: [snapshot, storage_unit] where: StorageUnit_p_nom_extendable expression: StorageUnit_state_of_charge >= 0 StorageUnit_ext_state_of_charge_upper: description: "`StorageUnit-ext-state_of_charge-upper` — an extendable unit holds at most its hours at the chosen build" - foreach: [snapshot, storage_unit] + dims: [snapshot, storage_unit] where: StorageUnit_p_nom_extendable expression: StorageUnit_state_of_charge <= StorageUnit_max_hours * StorageUnit_p_nom_ext StorageUnit_ext_p_nom_lower: description: "`StorageUnit-ext-p_nom-lower` — the chosen build is at least its floor" - foreach: [storage_unit] + dims: [storage_unit] where: StorageUnit_p_nom_extendable expression: StorageUnit_p_nom_ext >= StorageUnit_p_nom_min StorageUnit_ext_p_nom_upper: description: "`StorageUnit-ext-p_nom-upper` — the chosen build is at most its cap; a cap of infinity is no row" - foreach: [storage_unit] + dims: [storage_unit] where: StorageUnit_p_nom_extendable AND StorageUnit_p_nom_max expression: StorageUnit_p_nom_ext <= StorageUnit_p_nom_max StorageUnit_p_nom_set: description: "`StorageUnit-p_nom_set` — the chosen build pinned, wherever a value is given" - foreach: [storage_unit] + dims: [storage_unit] where: StorageUnit_p_nom_extendable AND StorageUnit_p_nom_set expression: StorageUnit_p_nom_ext == StorageUnit_p_nom_set StorageUnit_energy_balance: @@ -1133,7 +1133,7 @@ constraints: `StorageUnit-energy_balance` — the charge carried in, plus what is stored after its efficiency, less what dispatch draws down before its own, plus inflow not spilled - foreach: [snapshot, storage_unit] + dims: [snapshot, storage_unit] expression: >- StorageUnit_state_of_charge == StorageUnit_charge_carried_in @@ -1142,144 +1142,144 @@ constraints: + (StorageUnit_inflow - StorageUnit_spill) * snapshot_weightings_stores Store_fix_e_lower: description: "`Store-fix-e-lower` — a fixed store holds at least its floor" - foreach: [snapshot, store] + dims: [snapshot, store] where: not Store_e_nom_extendable expression: Store_e >= Store_e_min_pu * Store_e_nom Store_fix_e_upper: description: "`Store-fix-e-upper` — a fixed store holds at most its nominal capacity" - foreach: [snapshot, store] + dims: [snapshot, store] where: not Store_e_nom_extendable expression: Store_e <= Store_e_max_pu * Store_e_nom Store_ext_e_lower: description: "`Store-ext-e-lower` — an extendable store holds at least its floor of the chosen build" - foreach: [snapshot, store] + dims: [snapshot, store] where: Store_e_nom_extendable expression: Store_e >= Store_e_min_pu * Store_e_nom_ext Store_ext_e_upper: description: "`Store-ext-e-upper` — an extendable store holds at most the chosen build" - foreach: [snapshot, store] + dims: [snapshot, store] where: Store_e_nom_extendable expression: Store_e <= Store_e_max_pu * Store_e_nom_ext Store_ext_e_nom_lower: description: "`Store-ext-e_nom-lower` — the chosen build is at least its floor" - foreach: [store] + dims: [store] where: Store_e_nom_extendable expression: Store_e_nom_ext >= Store_e_nom_min Store_ext_e_nom_upper: description: "`Store-ext-e_nom-upper` — the chosen build is at most its cap; a cap of infinity is no row" - foreach: [store] + dims: [store] where: Store_e_nom_extendable AND Store_e_nom_max expression: Store_e_nom_ext <= Store_e_nom_max Store_e_nom_set: description: "`Store-e_nom_set` — the chosen build pinned, wherever a value is given" - foreach: [store] + dims: [store] where: Store_e_nom_extendable AND Store_e_nom_set expression: Store_e_nom_ext == Store_e_nom_set Store_energy_balance: description: "`Store-energy_balance` — the energy carried in, less what is delivered to the bus" - foreach: [snapshot, store] + dims: [snapshot, store] expression: >- Store_e == Store_energy_carried_in - Store_p * snapshot_weightings_stores Generator_p_set: description: "`Generator-p_set` — output pinned to the given schedule, wherever one is given" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_p_set expression: Generator_p == Generator_p_set Link_p_set: description: "`Link-p_set` — flow pinned to the given schedule, wherever one is given" - foreach: [snapshot, link] + dims: [snapshot, link] where: Link_p_set expression: Link_p == Link_p_set StorageUnit_p_set: description: "`StorageUnit-p_set` — net dispatch pinned to the given schedule, wherever one is given" - foreach: [snapshot, storage_unit] + dims: [snapshot, storage_unit] where: StorageUnit_p_set expression: StorageUnit_p_dispatch - StorageUnit_p_store == StorageUnit_p_set StorageUnit_state_of_charge_set: description: "`StorageUnit-state_of_charge_set` — charge pinned to the given schedule, wherever one is given" - foreach: [snapshot, storage_unit] + dims: [snapshot, storage_unit] where: StorageUnit_state_of_charge_set expression: StorageUnit_state_of_charge == StorageUnit_state_of_charge_set Store_e_set: description: "`Store-e_set` — energy pinned to the given schedule, wherever one is given" - foreach: [snapshot, store] + dims: [snapshot, store] where: Store_e_set expression: Store_e == Store_e_set GlobalConstraint_primary_energy_ub: description: "`primary_energy` — its total, at most its constant" - foreach: [global_constraint] + dims: [global_constraint] where: GlobalConstraint_type == 'primary_energy' AND GlobalConstraint_sense == '<=' expression: primary_energy <= GlobalConstraint_constant GlobalConstraint_primary_energy_lb: description: "`primary_energy` — its total, at least its constant" - foreach: [global_constraint] + dims: [global_constraint] where: GlobalConstraint_type == 'primary_energy' AND GlobalConstraint_sense == '>=' expression: primary_energy >= GlobalConstraint_constant GlobalConstraint_primary_energy_eq: description: "`primary_energy` — its total, at its constant" - foreach: [global_constraint] + dims: [global_constraint] where: GlobalConstraint_type == 'primary_energy' AND GlobalConstraint_sense == '==' expression: primary_energy == GlobalConstraint_constant GlobalConstraint_operational_limit_ub: description: "`operational_limit` — its total, at most its constant" - foreach: [global_constraint] + dims: [global_constraint] where: GlobalConstraint_type == 'operational_limit' AND GlobalConstraint_sense == '<=' expression: operational_limit <= GlobalConstraint_constant GlobalConstraint_operational_limit_lb: description: "`operational_limit` — its total, at least its constant" - foreach: [global_constraint] + dims: [global_constraint] where: GlobalConstraint_type == 'operational_limit' AND GlobalConstraint_sense == '>=' expression: operational_limit >= GlobalConstraint_constant GlobalConstraint_operational_limit_eq: description: "`operational_limit` — its total, at its constant" - foreach: [global_constraint] + dims: [global_constraint] where: GlobalConstraint_type == 'operational_limit' AND GlobalConstraint_sense == '==' expression: operational_limit == GlobalConstraint_constant GlobalConstraint_transmission_volume_expansion_limit_ub: description: "`transmission_volume_expansion_limit` — its total, at most its constant" - foreach: [global_constraint] + dims: [global_constraint] where: GlobalConstraint_type == 'transmission_volume_expansion_limit' AND GlobalConstraint_sense == '<=' expression: transmission_volume_expansion <= GlobalConstraint_constant GlobalConstraint_transmission_volume_expansion_limit_lb: description: "`transmission_volume_expansion_limit` — its total, at least its constant" - foreach: [global_constraint] + dims: [global_constraint] where: GlobalConstraint_type == 'transmission_volume_expansion_limit' AND GlobalConstraint_sense == '>=' expression: transmission_volume_expansion >= GlobalConstraint_constant GlobalConstraint_transmission_volume_expansion_limit_eq: description: "`transmission_volume_expansion_limit` — its total, at its constant" - foreach: [global_constraint] + dims: [global_constraint] where: GlobalConstraint_type == 'transmission_volume_expansion_limit' AND GlobalConstraint_sense == '==' expression: transmission_volume_expansion == GlobalConstraint_constant GlobalConstraint_transmission_expansion_cost_limit_ub: description: "`transmission_expansion_cost_limit` — its total, at most its constant" - foreach: [global_constraint] + dims: [global_constraint] where: GlobalConstraint_type == 'transmission_expansion_cost_limit' AND GlobalConstraint_sense == '<=' expression: transmission_expansion_cost <= GlobalConstraint_constant GlobalConstraint_transmission_expansion_cost_limit_lb: description: "`transmission_expansion_cost_limit` — its total, at least its constant" - foreach: [global_constraint] + dims: [global_constraint] where: GlobalConstraint_type == 'transmission_expansion_cost_limit' AND GlobalConstraint_sense == '>=' expression: transmission_expansion_cost >= GlobalConstraint_constant GlobalConstraint_transmission_expansion_cost_limit_eq: description: "`transmission_expansion_cost_limit` — its total, at its constant" - foreach: [global_constraint] + dims: [global_constraint] where: GlobalConstraint_type == 'transmission_expansion_cost_limit' AND GlobalConstraint_sense == '==' expression: transmission_expansion_cost == GlobalConstraint_constant GlobalConstraint_tech_capacity_expansion_limit_ub: description: "`tech_capacity_expansion_limit` — its total, at most its constant" - foreach: [global_constraint] + dims: [global_constraint] where: GlobalConstraint_type == 'tech_capacity_expansion_limit' AND GlobalConstraint_sense == '<=' expression: tech_capacity_expansion <= GlobalConstraint_constant GlobalConstraint_tech_capacity_expansion_limit_lb: description: "`tech_capacity_expansion_limit` — its total, at least its constant" - foreach: [global_constraint] + dims: [global_constraint] where: GlobalConstraint_type == 'tech_capacity_expansion_limit' AND GlobalConstraint_sense == '>=' expression: tech_capacity_expansion >= GlobalConstraint_constant GlobalConstraint_tech_capacity_expansion_limit_eq: description: "`tech_capacity_expansion_limit` — its total, at its constant" - foreach: [global_constraint] + dims: [global_constraint] where: GlobalConstraint_type == 'tech_capacity_expansion_limit' AND GlobalConstraint_sense == '==' expression: tech_capacity_expansion == GlobalConstraint_constant Bus_nodal_balance: @@ -1290,7 +1290,7 @@ constraints: load there. A bus nothing is attached to has no row; PyPSA refuses one that carries load, and this file does not yet. - foreach: [snapshot, bus] + dims: [snapshot, bus] expression: >- sum(Generator_p, by=Generator_bus) + sum(StorageUnit_p_dispatch - StorageUnit_p_store, by=StorageUnit_bus) diff --git a/examples/pypsa_linearized_uc.yaml b/examples/pypsa_linearized_uc.yaml index 5c300c0a..7135641c 100644 --- a/examples/pypsa_linearized_uc.yaml +++ b/examples/pypsa_linearized_uc.yaml @@ -145,31 +145,31 @@ lookups: variables: Generator_p: description: "`Generator-p` — output of a generator in a snapshot" - foreach: [snapshot, generator] + dims: [snapshot, generator] Link_p: description: >- `Link-p` — PyPSA's `p0`, the flow measured at the `Link_bus0` end: a positive value withdraws there and injects at every bus the link's output ports deliver to - foreach: [snapshot, link] + dims: [snapshot, link] Generator_status: description: >- `Generator-status` — how much of a committable unit is on, a share in [0, 1] rather than a binary: the relaxation `linearized_unit_commitment` solves - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable bounds: lower: 0 Generator_start_up: description: "`Generator-start_up` — how much of a committable unit turns on this snapshot" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable bounds: lower: 0 Generator_shut_down: description: "`Generator-shut_down` — how much of a committable unit turns off this snapshot" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable bounds: lower: 0 @@ -179,7 +179,7 @@ expressions: description: >- the commitment state a generator carries into a snapshot — the state it brought into the horizon at the first, the previous snapshot's after that - foreach: [snapshot, generator] + dims: [snapshot, generator] cases: opening: { when: "position(snapshot) == 0", expression: Generator_status_initial } otherwise: shift(Generator_status, over=snapshot, offset=1) @@ -188,7 +188,7 @@ expressions: the output a generator carries into a snapshot — nothing at the start of the horizon, which is why a unit that came in running carries no ramp row there - foreach: [snapshot, generator] + dims: [snapshot, generator] cases: opening: { when: "position(snapshot) == 0", expression: 0 } otherwise: shift(Generator_p, over=snapshot, offset=1) @@ -196,28 +196,28 @@ expressions: constraints: Generator_fix_p_lower: description: "`Generator-fix-p-lower` — a generator outputs at least its minimum" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: not Generator_committable expression: Generator_p >= Generator_p_min_pu * Generator_p_nom Generator_fix_p_upper: description: "`Generator-fix-p-upper` — a generator outputs at most what is available" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: not Generator_committable expression: Generator_p <= Generator_p_max_pu * Generator_p_nom Link_fix_p_lower: description: "`Link-fix-p-lower` — a link carries at least its minimum, negative for the other way" - foreach: [snapshot, link] + dims: [snapshot, link] expression: Link_p >= Link_p_min_pu * Link_p_nom Link_fix_p_upper: description: "`Link-fix-p-upper` — a link carries at most its nominal power" - foreach: [snapshot, link] + dims: [snapshot, link] expression: Link_p <= Link_p_max_pu * Link_p_nom Bus_nodal_balance: description: >- `Bus-nodal_balance` — what is generated at a bus, less what the links take away, plus what arrives over them after losses, meets the load there - foreach: [snapshot, bus] + dims: [snapshot, bus] expression: >- sum(Generator_p, by=Generator_bus) - sum(Link_p, by=Link_bus0) @@ -225,22 +225,22 @@ constraints: == sum(Load_p_set, by=Load_bus) Generator_com_p_lower: description: "`Generator-com-p-lower` — a committed unit outputs at least its minimum; off, at least nothing" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable expression: Generator_p >= Generator_p_min_pu * Generator_p_nom * Generator_status Generator_com_p_upper: description: "`Generator-com-p-upper` — a committed unit outputs at most what is available; off, at most nothing" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable expression: Generator_p <= Generator_p_max_pu * Generator_p_nom * Generator_status Generator_com_transition_start_up: description: "`Generator-com-transition-start-up` — turning on is a start, counted against the state the unit carried into the snapshot" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable expression: Generator_start_up >= Generator_status - Generator_previous_status Generator_com_transition_shut_down: description: "`Generator-com-transition-shut-down` — turning off is a stop, counted against the state the unit carried into the snapshot" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable expression: Generator_shut_down >= Generator_previous_status - Generator_status Generator_com_up_time: @@ -248,17 +248,17 @@ constraints: `Generator-com-up-time` — a unit started within its own minimum up time is still on. The first snapshot's share of the window is the brought-in up time's, which the must-stay-up mask carries - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable AND Generator_min_up_time > 0 AND position(snapshot) > 0 expression: sum_back(Generator_start_up, over=snapshot, within=Generator_min_up_time) <= Generator_status Generator_com_down_time: description: "`Generator-com-down-time` — a unit stopped within its own minimum down time is still off" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable AND Generator_min_down_time > 0 AND position(snapshot) > 0 expression: sum_back(Generator_shut_down, over=snapshot, within=Generator_min_down_time) <= 1 - Generator_status Generator_com_status_must_stay_up: description: "`Generator-com-status-min_up_time_must_stay_up` — a unit still serving the up time it brought in stays on" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable AND Generator_must_stay_up expression: Generator_status == 1 Generator_p_ramp_limit_up: @@ -268,7 +268,7 @@ constraints: start-up ramp in the snapshot it turns on. A unit that came into the horizon running brought an unknown output, so it carries no row at the first snapshot - foreach: [snapshot, generator] + dims: [snapshot, generator] where: >- Generator_committable AND Generator_ramp_limit_up AND (position(snapshot) > 0 OR Generator_status_initial == 0) @@ -284,7 +284,7 @@ constraints: shut-down ramp in the snapshot it turns off. A unit that came into the horizon running brought an unknown output, so it carries no row at the first snapshot - foreach: [snapshot, generator] + dims: [snapshot, generator] where: >- Generator_committable AND Generator_ramp_limit_down AND (position(snapshot) > 0 OR Generator_status_initial == 0) @@ -295,17 +295,17 @@ constraints: * (Generator_previous_status - Generator_status) Generator_status_p_fixed_upper: description: "`Generator-status-p-fixed-upper` — a status is at most one, an explicit row as PyPSA writes it" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable expression: Generator_status <= 1 Generator_start_up_p_fixed_upper: description: "`Generator-start_up-p-fixed-upper` — a start is at most one, an explicit row as PyPSA writes it" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable expression: Generator_start_up <= 1 Generator_shut_down_p_fixed_upper: description: "`Generator-shut_down-p-fixed-upper` — a stop is at most one, an explicit row as PyPSA writes it" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable expression: Generator_shut_down <= 1 Generator_com_p_before: @@ -314,7 +314,7 @@ constraints: fits the share of it still on, less the share it is shutting down at the shut-down ramp. The translated term vacates the first snapshot, as PyPSA's `sns[1:]` does - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable AND Generator_partly_tightened expression: >- shift(Generator_p, over=snapshot, offset=1) @@ -323,14 +323,14 @@ constraints: * (Generator_status - Generator_start_up) <= 0 Generator_com_p_current: description: "`Generator-com-p-current` — output fits the share on, and the share starting up only up to the start-up ramp" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable AND Generator_partly_tightened AND position(snapshot) > 0 expression: >- Generator_p - Generator_p_max_pu * Generator_p_nom * Generator_status + (Generator_p_max_pu * Generator_p_nom - Generator_ramp_limit_start_up * Generator_p_nom) * Generator_start_up <= 0 Generator_com_partly_start_up: description: "`Generator-com-partly-start-up` — raising output while a share is starting up is bounded by the ramp of the share on and the start-up ramp of the share coming on" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable AND Generator_partly_tightened expression: >- Generator_p - shift(Generator_p, over=snapshot, offset=1) @@ -340,7 +340,7 @@ constraints: * Generator_start_up <= 0 Generator_com_partly_shut_down: description: "`Generator-com-partly-shut-down` — lowering output while a share is shutting down is bounded likewise, by the shut-down ramp" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_committable AND Generator_partly_tightened expression: >- shift(Generator_p, over=snapshot, offset=1) - Generator_p diff --git a/examples/pypsa_losses.yaml b/examples/pypsa_losses.yaml index b2d4a362..256099a9 100644 --- a/examples/pypsa_losses.yaml +++ b/examples/pypsa_losses.yaml @@ -142,75 +142,75 @@ lookups: variables: Generator_p: description: "`Generator-p` — output of a generator in a snapshot" - foreach: [snapshot, generator] + dims: [snapshot, generator] Line_s: description: >- `Line-s` — PyPSA's `p0`, the flow measured at the `Line_bus0` end: a positive value withdraws there and injects at `Line_bus1` - foreach: [snapshot, line] + dims: [snapshot, line] Line_s_nom_ext: description: >- `Line-s_nom` — nominal apparent power where it is a decision; the parameter of the same PyPSA name carries the fixed regime - foreach: [line] + dims: [line] where: Line_s_nom_extendable Link_p: description: >- `Link-p` — PyPSA's `p0`, the flow measured at the `Link_bus0` end: a positive value withdraws there and injects at every bus the link's output ports deliver to - foreach: [snapshot, link] + dims: [snapshot, link] Line_loss: description: "`Line-loss` — what a line dissipates carrying its flow, pushed down by the cost and held up by the tangents" - foreach: [snapshot, line] + dims: [snapshot, line] bounds: lower: 0 constraints: Generator_fix_p_lower: description: "`Generator-fix-p-lower` — a generator outputs at least its minimum" - foreach: [snapshot, generator] + dims: [snapshot, generator] expression: Generator_p >= Generator_p_min_pu * Generator_p_nom Generator_fix_p_upper: description: "`Generator-fix-p-upper` — a generator outputs at most what is available" - foreach: [snapshot, generator] + dims: [snapshot, generator] expression: Generator_p <= Generator_p_max_pu * Generator_p_nom Link_fix_p_lower: description: "`Link-fix-p-lower` — a link carries at least its minimum, negative for the other way" - foreach: [snapshot, link] + dims: [snapshot, link] expression: Link_p >= Link_p_min_pu * Link_p_nom Link_fix_p_upper: description: "`Link-fix-p-upper` — a link carries at most its nominal power" - foreach: [snapshot, link] + dims: [snapshot, link] expression: Link_p <= Link_p_max_pu * Link_p_nom Line_fix_s_lower: description: "`Line-fix-s-lower` — a fixed line carries at least the negative of its rating, the loss counted against it" - foreach: [snapshot, line] + dims: [snapshot, line] where: not Line_s_nom_extendable expression: Line_s - Line_loss >= -Line_s_max_pu * Line_s_nom Line_fix_s_upper: description: "`Line-fix-s-upper` — a fixed line carries at most its rating, loss included" - foreach: [snapshot, line] + dims: [snapshot, line] where: not Line_s_nom_extendable expression: Line_s + Line_loss <= Line_s_max_pu * Line_s_nom Line_ext_s_lower: description: "`Line-ext-s-lower` — an extendable line carries at least the negative of its rating of the chosen build" - foreach: [snapshot, line] + dims: [snapshot, line] where: Line_s_nom_extendable expression: Line_s - Line_loss >= -Line_s_max_pu * Line_s_nom_ext Line_ext_s_upper: description: "`Line-ext-s-upper` — an extendable line carries at most its rating of the chosen build" - foreach: [snapshot, line] + dims: [snapshot, line] where: Line_s_nom_extendable expression: Line_s + Line_loss <= Line_s_max_pu * Line_s_nom_ext Line_ext_s_nom_lower: description: "`Line-ext-s_nom-lower` — the chosen build is at least its floor" - foreach: [line] + dims: [line] where: Line_s_nom_extendable expression: Line_s_nom_ext >= Line_s_nom_min Line_ext_s_nom_upper: description: "`Line-ext-s_nom-upper` — the chosen build is at most its cap; a cap of infinity is no row" - foreach: [line] + dims: [line] where: Line_s_nom_extendable AND Line_s_nom_max expression: Line_s_nom_ext <= Line_s_nom_max Kirchhoff_Voltage_Law: @@ -218,14 +218,14 @@ constraints: `Kirchhoff-Voltage-Law` — around every independent cycle the impedance-weighted flows sum to nothing, which is what makes the linear power flow physical rather than transport - foreach: [snapshot, cycle] + dims: [snapshot, cycle] expression: sum(Line_s * Line_cycle_weight, over=line) == 0 Bus_nodal_balance: description: >- `Bus-nodal_balance` — what is generated at a bus, plus what the links and lines bring, meets the load there, less half of every incident line's loss — PyPSA dissipates a branch's loss half at either end - foreach: [snapshot, bus] + dims: [snapshot, bus] expression: >- sum(Generator_p, by=Generator_bus) - sum(Link_p, by=Link_bus0) @@ -237,18 +237,18 @@ constraints: == sum(Load_p_set, by=Load_bus) Line_loss_upper: description: "`Line-loss_upper` — a line dissipates at most the loss at its rating" - foreach: [snapshot, line] + dims: [snapshot, line] expression: Line_loss <= Line_loss_max Line_loss_tangents_forward: description: >- `Line-loss_tangents-{k}-1` — the loss sits above every tangent to its curve for flow one way; PyPSA names one row per segment `k`, this block states them all over the segment dimension - foreach: [snapshot, line, segment] + dims: [snapshot, line, segment] expression: Line_loss + Line_loss_slope * Line_s >= Line_loss_offset Line_loss_tangents_reverse: description: "`Line-loss_tangents-{k}--1` — the same fan mirrored, the loss depending on the flow's magnitude" - foreach: [snapshot, line, segment] + dims: [snapshot, line, segment] expression: Line_loss - Line_loss_slope * Line_s >= Line_loss_offset objective: diff --git a/examples/pypsa_multi_period.yaml b/examples/pypsa_multi_period.yaml index adcd1e4f..0c283619 100644 --- a/examples/pypsa_multi_period.yaml +++ b/examples/pypsa_multi_period.yaml @@ -141,66 +141,66 @@ lookups: variables: Generator_p: description: "`Generator-p` — output of a generator in a snapshot" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_active Link_p: description: >- `Link-p` — PyPSA's `p0`, the flow measured at the `Link_bus0` end: a positive value withdraws there and injects at every bus the link's output ports deliver to - foreach: [snapshot, link] + dims: [snapshot, link] Generator_p_nom_ext: description: >- `Generator-p_nom` — nominal power where it is a decision; the parameter of the same PyPSA name carries the fixed regime - foreach: [generator] + dims: [generator] where: Generator_p_nom_extendable constraints: Generator_fix_p_lower: description: "`Generator-fix-p-lower` — a generator outputs at least its minimum" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: not Generator_p_nom_extendable AND Generator_active expression: Generator_p >= Generator_p_min_pu * Generator_p_nom Generator_fix_p_upper: description: "`Generator-fix-p-upper` — a generator outputs at most what is available" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: not Generator_p_nom_extendable AND Generator_active expression: Generator_p <= Generator_p_max_pu * Generator_p_nom Generator_ext_p_lower: description: "`Generator-ext-p-lower` — an extendable generator outputs at least its minimum of the chosen build" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_p_nom_extendable AND Generator_active expression: Generator_p >= Generator_p_min_pu * Generator_p_nom_ext Generator_ext_p_upper: description: "`Generator-ext-p-upper` — an extendable generator outputs at most what is available of the chosen build" - foreach: [snapshot, generator] + dims: [snapshot, generator] where: Generator_p_nom_extendable AND Generator_active expression: Generator_p <= Generator_p_max_pu * Generator_p_nom_ext Generator_ext_p_nom_lower: description: "`Generator-ext-p_nom-lower` — the chosen build is at least its floor" - foreach: [generator] + dims: [generator] where: Generator_p_nom_extendable expression: Generator_p_nom_ext >= Generator_p_nom_min Generator_ext_p_nom_upper: description: "`Generator-ext-p_nom-upper` — the chosen build is at most its cap; a cap of infinity is no row" - foreach: [generator] + dims: [generator] where: Generator_p_nom_extendable AND Generator_p_nom_max expression: Generator_p_nom_ext <= Generator_p_nom_max Link_fix_p_lower: description: "`Link-fix-p-lower` — a link carries at least its minimum, negative for the other way" - foreach: [snapshot, link] + dims: [snapshot, link] expression: Link_p >= Link_p_min_pu * Link_p_nom Link_fix_p_upper: description: "`Link-fix-p-upper` — a link carries at most its nominal power" - foreach: [snapshot, link] + dims: [snapshot, link] expression: Link_p <= Link_p_max_pu * Link_p_nom Bus_nodal_balance: description: >- `Bus-nodal_balance` — what is generated at a bus, less what the links take away, plus what arrives over them after losses, meets the load there - foreach: [snapshot, bus] + dims: [snapshot, bus] expression: >- sum(Generator_p, by=Generator_bus) - sum(Link_p, by=Link_bus0) @@ -212,7 +212,7 @@ constraints: build in the first period it stands in, is at most its allowance plus a share of what it added the period before; the first period has no predecessor, so `edge=0` leaves it the bare allowance - foreach: [carrier, period] + dims: [carrier, period] where: Carrier_max_growth expression: >- sum(Generator_p_nom_ext * Generator_first_active, by=Generator_carrier) diff --git a/examples/pypsa_quadratic.yaml b/examples/pypsa_quadratic.yaml index 66fbeea9..0155c09a 100644 --- a/examples/pypsa_quadratic.yaml +++ b/examples/pypsa_quadratic.yaml @@ -102,37 +102,37 @@ lookups: variables: Generator_p: description: "`Generator-p` — output of a generator in a snapshot" - foreach: [snapshot, generator] + dims: [snapshot, generator] Link_p: description: >- `Link-p` — PyPSA's `p0`, the flow measured at the `Link_bus0` end: a positive value withdraws there and injects at every bus the link's output ports deliver to - foreach: [snapshot, link] + dims: [snapshot, link] constraints: Generator_fix_p_lower: description: "`Generator-fix-p-lower` — a generator outputs at least its minimum" - foreach: [snapshot, generator] + dims: [snapshot, generator] expression: Generator_p >= Generator_p_min_pu * Generator_p_nom Generator_fix_p_upper: description: "`Generator-fix-p-upper` — a generator outputs at most what is available" - foreach: [snapshot, generator] + dims: [snapshot, generator] expression: Generator_p <= Generator_p_max_pu * Generator_p_nom Link_fix_p_lower: description: "`Link-fix-p-lower` — a link carries at least its minimum, negative for the other way" - foreach: [snapshot, link] + dims: [snapshot, link] expression: Link_p >= Link_p_min_pu * Link_p_nom Link_fix_p_upper: description: "`Link-fix-p-upper` — a link carries at most its nominal power" - foreach: [snapshot, link] + dims: [snapshot, link] expression: Link_p <= Link_p_max_pu * Link_p_nom Bus_nodal_balance: description: >- `Bus-nodal_balance` — what is generated at a bus, less what the links take away, plus what arrives over them after losses, meets the load there - foreach: [snapshot, bus] + dims: [snapshot, bus] expression: >- sum(Generator_p, by=Generator_bus) - sum(Link_p, by=Link_bus0) diff --git a/examples/pypsa_stochastic.yaml b/examples/pypsa_stochastic.yaml index 59f0cb08..62f82042 100644 --- a/examples/pypsa_stochastic.yaml +++ b/examples/pypsa_stochastic.yaml @@ -120,30 +120,30 @@ lookups: variables: Generator_p: description: "`Generator-p` — output of a generator in a snapshot" - foreach: [scenario, snapshot, generator] + dims: [scenario, snapshot, generator] Link_p: description: >- `Link-p` — PyPSA's `p0`, the flow measured at the `Link_bus0` end: a positive value withdraws there and injects at every bus the link's output ports deliver to - foreach: [scenario, snapshot, link] + dims: [scenario, snapshot, link] Generator_p_nom_ext: description: >- `Generator-p_nom` — nominal power where it is a decision; the parameter of the same PyPSA name carries the fixed regime - foreach: [generator] + dims: [generator] where: Generator_p_nom_extendable CVaR_a: description: "`CVaR-a` — how far a scenario's operating cost exceeds the tail's start; nothing where it does not" - foreach: [scenario] + dims: [scenario] bounds: lower: 0 CVaR_theta: description: "`CVaR-theta` — where the tail starts, the value at risk" - foreach: [] + dims: [] CVaR: description: "`CVaR` — the tail's average cost, what the objective prices at `omega`" - foreach: [] + dims: [] expressions: scenario_opex: @@ -155,48 +155,48 @@ expressions: constraints: Generator_fix_p_lower: description: "`Generator-fix-p-lower` — a generator outputs at least its minimum" - foreach: [scenario, snapshot, generator] + dims: [scenario, snapshot, generator] where: not Generator_p_nom_extendable expression: Generator_p >= Generator_p_min_pu * Generator_p_nom Generator_fix_p_upper: description: "`Generator-fix-p-upper` — a generator outputs at most what is available" - foreach: [scenario, snapshot, generator] + dims: [scenario, snapshot, generator] where: not Generator_p_nom_extendable expression: Generator_p <= Generator_p_max_pu * Generator_p_nom Generator_ext_p_lower: description: "`Generator-ext-p-lower` — an extendable generator outputs at least its minimum of the chosen build" - foreach: [scenario, snapshot, generator] + dims: [scenario, snapshot, generator] where: Generator_p_nom_extendable expression: Generator_p >= Generator_p_min_pu * Generator_p_nom_ext Generator_ext_p_upper: description: "`Generator-ext-p-upper` — an extendable generator outputs at most what is available of the chosen build" - foreach: [scenario, snapshot, generator] + dims: [scenario, snapshot, generator] where: Generator_p_nom_extendable expression: Generator_p <= Generator_p_max_pu * Generator_p_nom_ext Generator_ext_p_nom_lower: description: "`Generator-ext-p_nom-lower` — the chosen build is at least its floor" - foreach: [generator] + dims: [generator] where: Generator_p_nom_extendable expression: Generator_p_nom_ext >= Generator_p_nom_min Generator_ext_p_nom_upper: description: "`Generator-ext-p_nom-upper` — the chosen build is at most its cap; a cap of infinity is no row" - foreach: [generator] + dims: [generator] where: Generator_p_nom_extendable AND Generator_p_nom_max expression: Generator_p_nom_ext <= Generator_p_nom_max Link_fix_p_lower: description: "`Link-fix-p-lower` — a link carries at least its minimum, negative for the other way" - foreach: [scenario, snapshot, link] + dims: [scenario, snapshot, link] expression: Link_p >= Link_p_min_pu * Link_p_nom Link_fix_p_upper: description: "`Link-fix-p-upper` — a link carries at most its nominal power" - foreach: [scenario, snapshot, link] + dims: [scenario, snapshot, link] expression: Link_p <= Link_p_max_pu * Link_p_nom Bus_nodal_balance: description: >- `Bus-nodal_balance` — what is generated at a bus, less what the links take away, plus what arrives over them after losses, meets the load there - foreach: [scenario, snapshot, bus] + dims: [scenario, snapshot, bus] expression: >- sum(Generator_p, by=Generator_bus) - sum(Link_p, by=Link_bus0) @@ -204,11 +204,11 @@ constraints: == sum(Load_p_set, by=Load_bus) CVaR_excess: description: "`CVaR-excess-{s}` — a scenario's operating cost beyond the tail's start is its excess; PyPSA names one row per scenario" - foreach: [scenario] + dims: [scenario] expression: CVaR_a - scenario_opex + CVaR_theta >= 0 CVaR_def: description: "`CVaR-def` — the tail's average is at least where it starts plus the expected excess over the tail's probability" - foreach: [] + dims: [] expression: CVaR_theta + CVaR_inv_tail * sum(scenario_weight * CVaR_a, over=scenario) <= CVaR objective: diff --git a/examples/sos.yaml b/examples/sos.yaml index 9e1f1b85..6d7ca2a8 100644 --- a/examples/sos.yaml +++ b/examples/sos.yaml @@ -34,13 +34,13 @@ parameters: variables: p: description: dispatched power - foreach: [snapshot, generator] + dims: [snapshot, generator] bounds: lower: 0 upper: p_max op_cost: description: operating cost, piecewise-linear in dispatch - foreach: [snapshot, generator] + dims: [snapshot, generator] bounds: lower: 0 @@ -58,7 +58,7 @@ piecewise: constraints: balance: - foreach: [snapshot] + dims: [snapshot] expression: sum(p, over=generator) == load objective: diff --git a/schema/math-spec.schema.json b/schema/math-spec.schema.json index 9d5dc8c3..5465841e 100644 --- a/schema/math-spec.schema.json +++ b/schema/math-spec.schema.json @@ -46,17 +46,17 @@ "default": null, "title": "Description" }, - "expression": { - "title": "Expression", - "type": "string" - }, - "foreach": { + "dims": { "items": { "type": "string" }, - "title": "Foreach", + "title": "Dims", "type": "array" }, + "expression": { + "title": "Expression", + "type": "string" + }, "where": { "anyOf": [ { @@ -71,7 +71,7 @@ } }, "required": [ - "foreach", + "dims", "expression" ], "title": "ConstraintBlock", @@ -112,7 +112,7 @@ "anyOf": [ { "additionalProperties": false, - "description": "A named quantity: one arithmetic expression, referenced by the math or read back after a solve.\n\nWritten in YAML as a bare string, or as a mapping once it carries a\n``description:`` \u2014 and serialised back to whichever form it was written in,\nso a round trip through :meth:`Spec.to_yaml` reproduces the file::\n\n expressions:\n total_generation: sum(p, over=generator)\n emissions:\n expression: sum(p * rate, over=generator)\n description: CO2 released, the quantity the cap bounds\n\nA quantity whose value varies by region is written as ``cases:`` over a\ndeclared ``foreach:``, with an ``otherwise:`` for the rest \u2014 see the\nlanguage reference.", + "description": "A named quantity: one arithmetic expression, referenced by the math or read back after a solve.\n\nWritten in YAML as a bare string, or as a mapping once it carries a\n``description:`` \u2014 and serialised back to whichever form it was written in,\nso a round trip through :meth:`Spec.to_yaml` reproduces the file::\n\n expressions:\n total_generation: sum(p, over=generator)\n emissions:\n expression: sum(p * rate, over=generator)\n description: CO2 released, the quantity the cap bounds\n\nA quantity whose value varies by region is written as ``cases:`` over a\ndeclared ``dims:``, with an ``otherwise:`` for the rest \u2014 see the\nlanguage reference.", "properties": { "cases": { "additionalProperties": { @@ -135,35 +135,35 @@ "default": null, "title": "Description" }, - "expression": { + "dims": { "anyOf": [ { - "type": "string" - }, - { - "type": "number" + "items": { + "type": "string" + }, + "type": "array" }, { "type": "null" } ], "default": null, - "title": "Expression" + "title": "Dims" }, - "foreach": { + "expression": { "anyOf": [ { - "items": { - "type": "string" - }, - "type": "array" + "type": "string" + }, + { + "type": "number" }, { "type": "null" } ], "default": null, - "title": "Foreach" + "title": "Expression" }, "otherwise": { "anyOf": [ @@ -482,7 +482,7 @@ }, "SosBlock": { "additionalProperties": false, - "description": "A special-ordered set over one dimension of one variable.\n\nOne set per coordinate of the variable's ``foreach`` minus ``over``; the\nmembers are the variable's *existing* coordinates along ``over``, in that\ndimension's declared order, and ``big_m`` is the optional cap a consumer\nthat reformulates the set puts on its linking rows.\n\n``type: 1`` admits at most one nonzero member, ``type: 2`` at most two,\nand those two consecutive. Unlike every other block this one declares no\nmath to read off ``A``: it is a *set*, carried to a consumer that has the\nconcept and reformulated for one that does not.", + "description": "A special-ordered set over one dimension of one variable.\n\nOne set per coordinate of the variable's ``dims`` minus ``over``; the\nmembers are the variable's *existing* coordinates along ``over``, in that\ndimension's declared order, and ``big_m`` is the optional cap a consumer\nthat reformulates the set puts on its linking rows.\n\n``type: 1`` admits at most one nonzero member, ``type: 2`` at most two,\nand those two consecutive. Unlike every other block this one declares no\nmath to read off ``A``: it is a *set*, carried to a consumer that has the\nconcept and reformulated for one that does not.", "properties": { "big_m": { "anyOf": [ @@ -562,6 +562,13 @@ "default": null, "title": "Description" }, + "dims": { + "items": { + "type": "string" + }, + "title": "Dims", + "type": "array" + }, "domain": { "default": "continuous", "enum": [ @@ -572,13 +579,6 @@ "title": "Domain", "type": "string" }, - "foreach": { - "items": { - "type": "string" - }, - "title": "Foreach", - "type": "array" - }, "where": { "anyOf": [ { @@ -593,7 +593,7 @@ } }, "required": [ - "foreach" + "dims" ], "title": "VariableBlock", "type": "object" diff --git a/src/math_spec/dimensions.py b/src/math_spec/dimensions.py index 1939032c..3c07aed5 100644 --- a/src/math_spec/dimensions.py +++ b/src/math_spec/dimensions.py @@ -7,7 +7,7 @@ Every node's dim set is computable before any data is bound, so this pass runs at load on the resolved AST. The per-node rules are the "Dim algebra" table in ``docs/reference/language/expressions.md``; a constraint's two sides together -must equal its ``foreach``, and a where or a bound may not exceed the frame. +must equal its ``dims``, and a where or a bound may not exceed the frame. """ from __future__ import annotations @@ -90,14 +90,14 @@ def _dims( return frozenset(schema.parameters[node.name].dims) if isinstance(node, VariableNode): - return frozenset(schema.variables[node.name].foreach) + return frozenset(schema.variables[node.name].dims) if isinstance(node, UnresolvedNode | KwargNode): msg = f'{type(node).__name__} reached the dim checker; resolve the expression first.' raise AssertionError(msg) if isinstance(node, DualNode): - return frozenset(schema.constraints[node.constraint].foreach) + return frozenset(schema.constraints[node.constraint].dims) if isinstance(node, FunctionCallNode): return _dims_call(node, schema, context) @@ -116,7 +116,7 @@ def _cases_dims(node: CasesNode, schema: Spec) -> frozenset[str]: A narrower arm broadcasts, as a parameter with fewer dims does. """ - return frozenset(schema.expressions[node.name].foreach or ()) + return frozenset(schema.expressions[node.name].dims or ()) def _not_carried(context: str, call: str, inner: frozenset[str], rewrite: str) -> str: @@ -430,7 +430,7 @@ def check_schema(schema: Spec, resolved: Resolved) -> None: DimensionError: On the first declaration that breaks one. """ for vname, vdef in schema.variables.items(): - frame = frozenset(vdef.foreach) + frame = frozenset(vdef.dims) context = f"Variable '{vname}'" _check_where_dims(resolved.variables[vname], frame, context) for side in ('lower', 'upper'): @@ -440,14 +440,14 @@ def check_schema(schema: Spec, resolved: Resolved) -> None: if not bdims <= frame: raise DimensionError( f"{context}: bounds.{side} parameter '{bound}' has dims " - f"{sorted(bdims - frame)} outside the variable's foreach " + f"{sorted(bdims - frame)} outside the variable's dims " f'{sorted(frame)}.' ) for ename, node in resolved.expressions.items(): if not isinstance(node, CasesNode): continue - frame = frozenset(schema.expressions[ename].foreach or []) + frame = frozenset(schema.expressions[ename].dims or []) for arm in node.arms: context = case_context(ename, None if arm.when is None else arm.label) if arm.when is not None: @@ -455,19 +455,19 @@ def check_schema(schema: Spec, resolved: Resolved) -> None: _check_value_dims(arm.value, schema, frame, context) for cname, (expression, where) in resolved.constraints.items(): - frame = frozenset(schema.constraints[cname].foreach) + frame = frozenset(schema.constraints[cname].dims) context = f"Constraint '{cname}'" _check_where_dims(where, frame, context) got = dims_of(expression, schema, context) if got != frame: stray, missing = sorted(got - frame), sorted(frame - got) detail = ( - f'carries dims {stray} that are not in foreach {sorted(frame)} — every ' + f'carries dims {stray} that are not in its dims: {sorted(frame)} — every ' f'stray dim multiplies the rows this constraint builds; add it to ' - f'foreach if that is intended, or sum it out' + f'dims: if that is intended, or sum it out' if stray - else f'does not carry {missing}, which foreach declares — the same row ' - f'would be repeated across {missing}; drop it from foreach, or use it ' + else f'does not carry {missing}, which its dims: declares — the same row ' + f'would be repeated across {missing}; drop it from dims:, or use it ' f'in the expression' ) raise DimensionError(f'{context}: the expression {detail}.') @@ -487,12 +487,12 @@ def _check_value_dims(node: ArithmeticNode, schema: Spec, frame: frozenset[str], """A region's value may only carry dims the frame does — the ``otherwise:`` included. A wider one would give the quantity dims its declaration does not, which is - the second answer a ``foreach:`` exists to avoid. + the second answer a ``dims:`` exists to avoid. """ got = dims_of(node, schema, context) if not got <= frame: raise DimensionError( - f'{context}: the value carries dims {sorted(got - frame)} outside the foreach ' + f'{context}: the value carries dims {sorted(got - frame)} outside the dims: ' f'{sorted(frame)}. A case is a value within the frame — it cannot widen it.' ) @@ -526,6 +526,6 @@ def _check_where_dims( assert_never(atom) raise DimensionError( f"{context}: where-{noun} '{atom.name}' reads dims {outside} outside the frame {sorted(frame)}. " - f'Reducing a mask over an unlisted dim would silently widen it — add the dim to foreach, ' + f'Reducing a mask over an unlisted dim would silently widen it — add the dim to dims:, ' f'or test a name the frame carries.' ) diff --git a/src/math_spec/lowering.py b/src/math_spec/lowering.py index 4be53d60..d1df7af4 100644 --- a/src/math_spec/lowering.py +++ b/src/math_spec/lowering.py @@ -112,7 +112,7 @@ def lower_program(expanded: _ExpandedSpec) -> program.Program: else: lower, upper = _bound_expression(vdef.bounds.lower), _bound_expression(vdef.bounds.upper) variables[vname] = program.VariableDeclaration( - tuple(vdef.foreach), + tuple(vdef.dims), where=resolved.variables[vname], lower=lower, upper=upper, @@ -125,7 +125,7 @@ def lower_program(expanded: _ExpandedSpec) -> program.Program: expression, where = resolved.constraints[cname] lowering = _Lowering(expanded, f"constraint '{cname}'") constraints[cname] = program.ConstraintDeclaration( - tuple(cdef.foreach), + tuple(cdef.dims), lhs=lowering.expr(expression.left), sense=expression.op, rhs=lowering.expr(expression.right), diff --git a/src/math_spec/model.py b/src/math_spec/model.py index d5246b97..b7dadb76 100644 --- a/src/math_spec/model.py +++ b/src/math_spec/model.py @@ -238,7 +238,7 @@ class VariableBlock(_StrictBlock): _label: ClassVar[str] = 'a variable declaration' - foreach: list[str] + dims: list[str] where: str | None = None bounds: BoundsBlock = BoundsBlock() domain: VariableDomain = 'continuous' @@ -251,7 +251,7 @@ def _absence_needs_a_mask(self) -> VariableBlock: if self.absence != 'undefined' and self.where is None: msg = ( f'absence: {self.absence} needs a `where:` — a variable with no mask exists at every ' - f'coordinate of its foreach, so there is no absence for it to describe. Add the mask, ' + f'coordinate of its dims, so there is no absence for it to describe. Add the mask, ' f'or drop the key.' ) raise ValueError(msg) @@ -263,7 +263,7 @@ class ConstraintBlock(_StrictBlock): _label: ClassVar[str] = 'a constraint declaration' - foreach: list[str] + dims: list[str] where: str | None = None expression: str description: str | None = None @@ -348,7 +348,7 @@ class ExpressionBlock(_StrictBlock): description: CO2 released, the quantity the cap bounds A quantity whose value varies by region is written as ``cases:`` over a - declared ``foreach:``, with an ``otherwise:`` for the rest — see the + declared ``dims:``, with an ``otherwise:`` for the rest — see the language reference. """ @@ -356,7 +356,7 @@ class ExpressionBlock(_StrictBlock): expression: Expression | None = None #: The frame the cases are read over — required with them, refused without. - foreach: list[str] | None = None + dims: list[str] | None = None #: The regions, keyed by the name labelling the row each prints; every ``when`` is proved apart from the others. cases: Annotated[dict[str, ExpressionCase], Field(min_length=1)] = {} #: The value wherever no case's ``when`` holds, printed as the last row. @@ -370,7 +370,7 @@ def _from_string(cls, data: Any) -> Any: @model_validator(mode='after') def _one_form_or_the_other(self) -> Self: - """One ``expression:``, or ``cases:`` with the ``otherwise:`` and ``foreach:`` they need.""" + """One ``expression:``, or ``cases:`` with the ``otherwise:`` and ``dims:`` they need.""" if bool(self.cases) == (self.expression is not None): got = 'both' if self.cases else 'neither' msg = ( @@ -378,15 +378,15 @@ def _one_form_or_the_other(self) -> Self: f'Cases are for a quantity whose value varies by region; one expression is everything else.' ) raise ValueError(msg) - if self.cases and self.foreach is None: + if self.cases and self.dims is None: msg = ( - '`cases:` needs a `foreach:` — it is the frame the cases are read over, and no one ' + '`cases:` needs a `dims:` — it is the frame the cases are read over, and no one ' "case's body gives it, since a case may be a scalar while the condition selecting it is not." ) raise ValueError(msg) - if self.foreach is not None and not self.cases: + if self.dims is not None and not self.cases: msg = ( - '`foreach:` is only for a named expression with `cases:`. Without them the dims fall ' + '`dims:` is only for a named expression with `cases:`. Without them the dims fall ' 'out of the body, and declaring a second answer is a second thing to keep true.' ) raise ValueError(msg) @@ -414,7 +414,7 @@ def __get_pydantic_json_schema__(cls, core_schema: CoreSchema, handler: GetJsonS @model_serializer def _as_written(self) -> str | dict[str, Any]: if self.cases: - written: dict[str, Any] = {'foreach': list(self.foreach or [])} + written: dict[str, Any] = {'dims': list(self.dims or [])} if self.description is not None: written['description'] = self.description written['cases'] = {name: case.model_dump() for name, case in self.cases.items()} @@ -562,7 +562,7 @@ def _check_links(cls, v: list[PiecewiseLink]) -> list[PiecewiseLink]: class SosBlock(_StrictBlock): """A special-ordered set over one dimension of one variable. - One set per coordinate of the variable's ``foreach`` minus ``over``; the + One set per coordinate of the variable's ``dims`` minus ``over``; the members are the variable's *existing* coordinates along ``over``, in that dimension's declared order, and ``big_m`` is the optional cap a consumer that reformulates the set puts on its linking rows. @@ -711,7 +711,7 @@ def _check_version(cls, v: int) -> int: def _drop_absence(self, handler: Any) -> dict[str, Any]: """Absence is not serialised: a null, an infinite bound, a mapping that stripping emptied, a section declaring nothing. - An empty list stays, being a value rather than an absence (``foreach: + An empty list stays, being a value rather than an absence (``dims: []`` is a scalar). On the serializer so that ``model_dump``, :meth:`to_dict` and :meth:`to_yaml` agree. """ @@ -794,9 +794,9 @@ def _frame_dimensions(self) -> Iterator[str]: """Every frame is a product of distinct, declared dimensions.""" frames = [ *(('Parameter', name, p.dims) for name, p in self.parameters.items()), - *(('Variable', name, v.foreach) for name, v in self.variables.items()), - *(('Constraint', name, c.foreach) for name, c in self.constraints.items()), - *(('Named expression', name, e.foreach or []) for name, e in self.expressions.items()), + *(('Variable', name, v.dims) for name, v in self.variables.items()), + *(('Constraint', name, c.dims) for name, c in self.constraints.items()), + *(('Named expression', name, e.dims or []) for name, e in self.expressions.items()), ] for kind, name, dims in frames: yield from (undeclared_dimension(kind, name, d) for d in dims if d not in self.dimensions) @@ -857,10 +857,10 @@ def _sos_shapes(self) -> Iterator[str]: f' Variables: {sorted(self.variables)}\n' f'A set is over one variable, so a parameter or an expression cannot carry one.' ) - elif block.over not in self.variables[block.variable].foreach: + elif block.over not in self.variables[block.variable].dims: yield ( f"{context}: over '{block.over}' is not a dim of variable " - f"'{block.variable}' (foreach {self.variables[block.variable].foreach}). The set runs " + f"'{block.variable}' (dims {self.variables[block.variable].dims}). The set runs " f"along one of the variable's own dims — one set per coordinate of the rest." ) elif block.variable in claimed: diff --git a/src/math_spec/piecewise.py b/src/math_spec/piecewise.py index ee1ca09f..7123c4f0 100644 --- a/src/math_spec/piecewise.py +++ b/src/math_spec/piecewise.py @@ -168,14 +168,14 @@ def _parameter(self, name: str, dims: list[str], description: str) -> None: def _weight(self, name: str, **fields: Any) -> None: """A variable over the frame and the breakpoint dim, masked as the block is.""" self.raw['variables'][name] = { - 'foreach': [*self.frame, self.pw.over], + 'dims': [*self.frame, self.pw.over], **({'where': self.mask} if self.mask else {}), **fields, } - def _constraint(self, name: str, foreach: list[str], expression: str, where: str | None = None) -> None: + def _constraint(self, name: str, dims: list[str], expression: str, where: str | None = None) -> None: self.raw['constraints'][name] = { - 'foreach': foreach, + 'dims': dims, **({'where': where} if where else {}), 'expression': expression, } @@ -315,7 +315,7 @@ def _widen(self, frame: list[str], dims: Iterable[tuple[str, frozenset[str]]]) - """Add each labelled dim set to *frame* in declaration order, refusing the breakpoint dim. Declaration order, because iterating a set would vary the emitted - ``foreach`` — and every column index behind it — per process. + ``dims`` — and every column index behind it — per process. """ for what, found in dims: for d in (d for d in self.schema.dimensions if d in found): diff --git a/src/math_spec/program.py b/src/math_spec/program.py index 1ade8d87..5478a31a 100644 --- a/src/math_spec/program.py +++ b/src/math_spec/program.py @@ -200,7 +200,7 @@ class Dual(Expression): Stands only under an :class:`ExpressionDeclaration` the math never reads: the loader refuses ``dual()`` anywhere a solver ingests. One value per - coordinate of the named constraint's own ``foreach`` frame, which is what + coordinate of the named constraint's own ``dims`` frame, which is what :func:`fan_in` answers ``one-to-one`` for — the leaf reshapes nothing, like a parameter. """ @@ -659,11 +659,11 @@ class ConstraintDeclaration: @dataclass(frozen=True) class SosDeclaration: - """One special-ordered set per coordinate of the variable's ``foreach`` minus ``over``. + """One special-ordered set per coordinate of the variable's ``dims`` minus ``over``. The only declaration that adds neither a column nor a row: it names columns a consumer already has and says what may be nonzero among them. Which - dims those are is the variable's own ``foreach`` and is read from it: a + dims those are is the variable's own ``dims`` and is read from it: a copy here would be a second home for a fact (:meth:`Program.variable`). diff --git a/src/math_spec/resolution.py b/src/math_spec/resolution.py index 7dca8cee..627a44a7 100644 --- a/src/math_spec/resolution.py +++ b/src/math_spec/resolution.py @@ -143,7 +143,7 @@ def of(cls, schema: Spec) -> Namespace: }, { **{p: tuple(pd.dims) for p, pd in schema.parameters.items()}, - **{v: tuple(vd.foreach) for v, vd in schema.variables.items()}, + **{v: tuple(vd.dims) for v, vd in schema.variables.items()}, }, schema.constraints, ) @@ -437,7 +437,7 @@ def _name(self, node: NameNode, *, amount: bool) -> ArithmeticNode: self.errors.append( f"{self.context}: '{node.name}' is a dimension, and a dimension is " f'not a value in an expression. Dimensions appear in ' - f"'foreach:', in operator arguments (sum(x, over={node.name})), " + f"'dims:', in operator arguments (sum(x, over={node.name})), " f'and in where-comparisons — to use its coordinates as data, ' f'declare a parameter over it.' ) diff --git a/src/math_spec/typesetting/walk.py b/src/math_spec/typesetting/walk.py index 2df0d4a2..4569052f 100644 --- a/src/math_spec/typesetting/walk.py +++ b/src/math_spec/typesetting/walk.py @@ -251,7 +251,7 @@ def __init__( def _frame_of(self, name: str) -> list[str]: block = self.schema.expressions[name] if block.cases: - return list(block.foreach or ()) + return list(block.dims or ()) return self._sorted(dims_of(self.schema.resolved.expressions[name], self.schema, f"expression '{name}'")) def _op(self, name: OperatorName) -> str: @@ -312,7 +312,7 @@ def _arithmetic(self, node: ArithmeticNode, ctx: _Context) -> tuple[str, int]: return ctx.indexed(self.symbols.name[node.name], list(self.schema.parameters[node.name].dims)), _ATOM if isinstance(node, VariableNode): - return ctx.indexed(self.symbols.name[node.name], list(self.schema.variables[node.name].foreach)), _ATOM + return ctx.indexed(self.symbols.name[node.name], list(self.schema.variables[node.name].dims)), _ATOM if isinstance(node, UnaryOperatorNode): if node.op == '+': @@ -642,13 +642,13 @@ def _constraints(self) -> list[Line]: def _constraint(self, name: str) -> Line: block = self.schema.constraints[name] node, where = self.schema.resolved.constraints[name] - ctx = self._context(frame=block.foreach) + ctx = self._context(frame=block.dims) condition = self._condition(ctx, where) return Line( label=name, left=self._expression(node.left, ctx), right=f'{self._op(_PREDICATES[node.op])} {self._expression(node.right, ctx)}', - condition=self._quantifier(list(block.foreach), condition), + condition=self._quantifier(list(block.dims), condition), ) def _definitions(self) -> list[Line]: @@ -732,15 +732,15 @@ def _variables(self) -> list[Line]: for name, block in self.schema.variables.items(): lines.append(self._variable(name)) if name in sets: - lines.append(self._sos(name, sets[name], self._context(frame=block.foreach))) + lines.append(self._sos(name, sets[name], self._context(frame=block.dims))) return lines def _variable(self, name: str) -> Line: block = self.schema.variables[name] - ctx = self._context(frame=block.foreach) - symbol = ctx.indexed(self.symbols.name[name], list(block.foreach)) + ctx = self._context(frame=block.dims) + symbol = ctx.indexed(self.symbols.name[name], list(block.dims)) where = self.schema.resolved.variables[name] - condition = self._quantifier(list(block.foreach), self._condition(ctx, where)) + condition = self._quantifier(list(block.dims), self._condition(ctx, where)) lower, upper = block.bounds.lower, block.bounds.upper if block.domain == 'binary': @@ -763,13 +763,13 @@ def _variable(self, name: str) -> Line: def _sos(self, name: str, block: SosBlock, ctx: _Context) -> Line: """The variable's family along the set's dim, as one member of the SOS set, quantified over the other dims.""" - foreach = self.schema.variables[name].foreach - family = self.format.parenthesise(ctx.indexed(self.symbols.name[name], list(foreach))) + dims = self.schema.variables[name].dims + family = self.format.parenthesise(ctx.indexed(self.symbols.name[name], list(dims))) return Line( label=f'{name} sos', left=self.format.subscript(family, [self._membership(block.over)]), right=f'{self._op("in")} {self._op("sos_set")}{block.type}', - condition=self._quantifier([d for d in foreach if d != block.over], ''), + condition=self._quantifier([d for d in dims if d != block.over], ''), ) def _bound(self, ctx: _Context, value: float | str) -> str: @@ -798,7 +798,7 @@ def glossaries(self, noticed: Noticed) -> list[Glossary]: for p, block in self.schema.parameters.items() ] variables = [ - self._entry(self.symbols.name[v], f'{fmt.mono(v)}{self._over(list(block.foreach))}', block.description) + self._entry(self.symbols.name[v], f'{fmt.mono(v)}{self._over(list(block.dims))}', block.description) for v, block in self.schema.variables.items() ] definitions = [ diff --git a/tests/fixtures.py b/tests/fixtures.py index d06c0c07..593c573f 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -31,8 +31,8 @@ 'cost': {'dims': ['generator']}, 'load': {'dims': ['snapshot']}, }, - 'variables': {'p': {'foreach': ['snapshot', 'generator'], 'bounds': {'lower': 0, 'upper': 'p_max'}}}, - 'constraints': {'balance': {'foreach': ['snapshot'], 'expression': 'sum(p, over=generator) == load'}}, + 'variables': {'p': {'dims': ['snapshot', 'generator'], 'bounds': {'lower': 0, 'upper': 'p_max'}}}, + 'constraints': {'balance': {'dims': ['snapshot'], 'expression': 'sum(p, over=generator) == load'}}, 'objective': {'sense': 'minimize', 'expression': 'sum(p * cost)'}, } @@ -49,7 +49,7 @@ 'flag': {'dims': ['g'], 'dtype': 'bool'}, 'tag': {'dims': ['g'], 'dtype': 'str'}, }, - 'variables': {'p': {'foreach': ['g']}, 'q': {'foreach': ['g', 'h']}, 'r': {'foreach': ['h']}}, + 'variables': {'p': {'dims': ['g']}, 'q': {'dims': ['g', 'h']}, 'r': {'dims': ['h']}}, } diff --git a/tests/fixtures/every_program_node.yaml b/tests/fixtures/every_program_node.yaml index 86ef02c0..fd26b5bf 100644 --- a/tests/fixtures/every_program_node.yaml +++ b/tests/fixtures/every_program_node.yaml @@ -16,36 +16,36 @@ parameters: lead: { dims: [g], dtype: int } width: { dims: [g], dtype: int } variables: - p: { foreach: [t, g], bounds: { lower: 0, upper: 100 } } - q: { foreach: [zone], bounds: { lower: 0, upper: 100 } } + p: { dims: [t, g], bounds: { lower: 0, upper: 100 } } + q: { dims: [zone], bounds: { lower: 0, upper: 100 } } expressions: by_region: - foreach: [t, g] + dims: [t, g] cases: opening: { when: "position(t) == 0", expression: 0 } otherwise: shift(p, over=t, offset=1, edge='wrap') shadow: dual(grouped) # a Dual leaf, the one node only an entry the math never reads lowers to constraints: regional: - foreach: [t, g] + dims: [t, g] expression: "p - by_region <= 0" arithmetic: - foreach: [t, g] + dims: [t, g] expression: "-p + p * cost - p / cost + rate ** 2 <= 1" reduced: - foreach: [] + dims: [] expression: "sum(p) >= 0" grouped: - foreach: [t, zone] + dims: [t, zone] expression: "sum(p, by=zone_of) - q <= load" pulled_back: - foreach: [t, g] + dims: [t, g] expression: "p - at(q, by=zone_of) <= 0" translated: - foreach: [t, g] + dims: [t, g] expression: "p - shift(p, over=t, offset=lead, edge='wrap') <= 0" windowed: - foreach: [t, g] + dims: [t, g] expression: "sum_back(p, over=t, within=width) <= 10" objective: sense: minimize diff --git a/tests/test_advice.py b/tests/test_advice.py index 3d5f5452..f57a84fc 100644 --- a/tests/test_advice.py +++ b/tests/test_advice.py @@ -25,7 +25,7 @@ #: ``h`` is the target of ``lk`` and nothing else reaches it; ``g`` is an axis. TARGET_ONLY = override( SMALL_MODEL, - variables={'p': {'foreach': ['g']}}, + variables={'p': {'dims': ['g']}}, objective={'sense': 'minimize', 'expression': 'sum(p * c)'}, ) @@ -44,10 +44,10 @@ def test_a_dimension_nothing_reaches_is_named(): [ pytest.param({}, id='targeted-by-a-lookup'), pytest.param( - {'constraints': {'cap': {'foreach': ['h'], 'expression': 'sum(p, by=lk) <= k'}}}, + {'constraints': {'cap': {'dims': ['h'], 'expression': 'sum(p, by=lk) <= k'}}}, id='grouping-into-it', ), - pytest.param({'variables.r': {'foreach': ['h']}}, id='indexing-by-it'), + pytest.param({'variables.r': {'dims': ['h']}}, id='indexing-by-it'), ], ) def test_a_dimension_something_reaches_is_in_use(patch): diff --git a/tests/test_boundedness.py b/tests/test_boundedness.py index 7940c6c5..07ae4dba 100644 --- a/tests/test_boundedness.py +++ b/tests/test_boundedness.py @@ -19,7 +19,7 @@ BASE = override( SMALL_MODEL, - variables={'v': {'foreach': ['g']}, 'w': {'foreach': ['g']}}, + variables={'v': {'dims': ['g']}, 'w': {'dims': ['g']}}, objective={'sense': 'minimize', 'expression': 'sum(v, over=g)'}, ) @@ -70,7 +70,7 @@ def test_a_variable_the_objective_drives_unopposed_is_named_with_its_side(patch, pytest.param({'variables.v.bounds': {'lower': 0}}, id='bounded-on-the-improving-side'), pytest.param({'variables.v.bounds': {'lower': 'c'}}, id='a-parameter-bound-is-data'), pytest.param({'variables.v.domain': 'binary'}, id='a-binary-is-bounded-by-its-domain'), - pytest.param({'constraints': {'k': {'foreach': ['g'], 'expression': 'v >= c'}}}, id='named-by-a-constraint'), + pytest.param({'constraints': {'k': {'dims': ['g'], 'expression': 'v >= c'}}}, id='named-by-a-constraint'), pytest.param({'sos': {'s': {'variable': 'v', 'over': 'g', 'type': 1}}}, id='carried-by-a-set'), pytest.param({'objective.expression': 'sum(c * v, over=g)'}, id='a-parameter-coefficient-may-be-zero'), pytest.param({'objective.expression': 'sum(v - v, over=g)'}, id='both-signs-may-cancel'), @@ -95,7 +95,7 @@ def test_nothing_is_claimed_where_the_file_does_not_decide_it(patch): 'shift': {'objective.expression': 'sum(shift(v, over=g, offset=1), over=g)'}, 'sum_back': {'objective.expression': 'sum(sum_back(v, over=g, within=2), over=g)'}, # `at` reads onto the lookup's source, so the variable it drives is on `h` - 'at': {'variables.u': {'foreach': ['h']}, 'objective.expression': 'sum(at(u, by=lk), over=g)'}, + 'at': {'variables.u': {'dims': ['h']}, 'objective.expression': 'sum(at(u, by=lk), over=g)'}, } #: `dual` is refused in any objective, and boundedness walks the objective — diff --git a/tests/test_degree.py b/tests/test_degree.py index e05ebd8e..05a4fca7 100644 --- a/tests/test_degree.py +++ b/tests/test_degree.py @@ -112,7 +112,7 @@ def test_carries_variable_refuses_an_unresolved_name(): def _dual_ast(text: str): - schema = schema_of(SMALL_MODEL, **{'constraints.lim': {'foreach': ['g'], 'expression': 'p <= c'}}) + schema = schema_of(SMALL_MODEL, **{'constraints.lim': {'dims': ['g'], 'expression': 'p <= c'}}) return expression_of(text, schema, Namespace.of(schema), 'test') @@ -146,9 +146,9 @@ def test_calls_dual_finds_a_dual_inside_a_cased_arm(): schema = schema_of( SMALL_MODEL, **{ - 'constraints.lim': {'foreach': ['g'], 'expression': 'p <= c'}, + 'constraints.lim': {'dims': ['g'], 'expression': 'p <= c'}, 'expressions.dcase': { - 'foreach': ['g'], + 'dims': ['g'], 'cases': {'flagged': {'when': 'flag', 'expression': 'dual(lim)'}}, 'otherwise': 0, }, diff --git a/tests/test_dimensions.py b/tests/test_dimensions.py index c96fb1b7..a14c87fb 100644 --- a/tests/test_dimensions.py +++ b/tests/test_dimensions.py @@ -43,10 +43,10 @@ 'horizon': {'dims': ['snapshot'], 'dtype': 'int'}, 'bus_lead': {'dims': ['bus'], 'dtype': 'int'}, }, - 'variables': {'p': {'foreach': ['snapshot', 'generator'], 'bounds': {'lower': 0, 'upper': 'p_max'}}}, + 'variables': {'p': {'dims': ['snapshot', 'generator'], 'bounds': {'lower': 0, 'upper': 'p_max'}}}, 'constraints': { 'balance': { - 'foreach': ['snapshot', 'bus'], + 'dims': ['snapshot', 'bus'], 'expression': 'sum(p, by=gen_bus) == load', } }, @@ -112,14 +112,14 @@ def _dims_with(expr: str, **overrides) -> frozenset[str]: def test_a_dual_carries_the_constraints_own_frame(): - """`dual(c)` is a row dual at every coordinate of the constraint's declared `foreach`.""" + """`dual(c)` is a row dual at every coordinate of the constraint's declared `dims`.""" s = _schema() - assert _dims_with('dual(balance)') == frozenset(s.constraints['balance'].foreach) == {'snapshot', 'bus'} + assert _dims_with('dual(balance)') == frozenset(s.constraints['balance'].dims) == {'snapshot', 'bus'} def test_a_bare_name_reaches_the_variable_a_dual_the_same_named_constraint(): """Constraints sit outside the flat namespace, so only `dual()` reads the constraint store — a bare name never does, even one a constraint shares (#74).""" - shadowing = {'variables.balance': {'foreach': ['snapshot'], 'bounds': {'lower': 0}}} + shadowing = {'variables.balance': {'dims': ['snapshot'], 'bounds': {'lower': 0}}} assert _dims_with('balance', **shadowing) == {'snapshot'}, 'a bare name resolves to the variable of that name' assert _dims_with('dual(balance)', **shadowing) == {'snapshot', 'bus'}, 'dual() alone reaches the constraint' @@ -213,29 +213,29 @@ def test_an_outer_product_is_legal_and_carries_both_dim_sets(): ('patch', 'match'), [ pytest.param( - {'constraints.stray': {'foreach': ['snapshot'], 'expression': 'p <= p_max'}}, - r"carries dims \['generator'\] that are not in foreach", + {'constraints.stray': {'dims': ['snapshot'], 'expression': 'p <= p_max'}}, + r"carries dims \['generator'\] that are not in its dims:", id='stray-dim-in-a-constraint', ), pytest.param( - {'constraints.unused': {'foreach': ['snapshot', 'generator', 'bus'], 'expression': 'p <= p_max'}}, + {'constraints.unused': {'dims': ['snapshot', 'generator', 'bus'], 'expression': 'p <= p_max'}}, r"does not carry \['bus'\]", - id='foreach-dim-the-equation-never-uses', + id='dims-dim-the-equation-never-uses', ), pytest.param( - {'variables.cap': {'foreach': ['generator'], 'where': 'load > 0'}}, + {'variables.cap': {'dims': ['generator'], 'where': 'load > 0'}}, r"where-parameter 'load' reads dims \['bus', 'snapshot'\]", id='where-dim-outside-the-frame', ), pytest.param( - {'variables.cap': {'foreach': ['generator'], 'where': 'snapshot > 0'}}, + {'variables.cap': {'dims': ['generator'], 'where': 'snapshot > 0'}}, "where-dimension 'snapshot'", id='where-comparison-on-a-dim-outside-the-frame', ), pytest.param( - {'variables.cap': {'foreach': ['generator'], 'bounds': {'lower': 0, 'upper': 'load'}}}, + {'variables.cap': {'dims': ['generator'], 'bounds': {'lower': 0, 'upper': 'load'}}}, r"bounds.upper parameter 'load' has dims \['bus', 'snapshot'\]", - id='bound-parameter-dim-outside-foreach', + id='bound-parameter-dim-outside-dims', ), ], ) @@ -254,8 +254,8 @@ class TestTheEdgeRulesAreDecidedAtLoad: BASE: ClassVar[dict[str, Any]] = { 'dimensions': {'t': {'dtype': 'int'}, 'g': {'dtype': 'str'}}, 'parameters': {'cap': {'dims': ['g']}, 'lead': {'dims': ['g'], 'dtype': 'int'}}, - 'variables': {'p': {'foreach': ['t', 'g'], 'bounds': {'lower': 0, 'upper': 1}}}, - 'constraints': {'k': {'foreach': ['t', 'g'], 'expression': 'p <= 1'}}, + 'variables': {'p': {'dims': ['t', 'g'], 'bounds': {'lower': 0, 'upper': 1}}}, + 'constraints': {'k': {'dims': ['t', 'g'], 'expression': 'p <= 1'}}, } def _refused(self, expression: str) -> str: diff --git a/tests/test_exclusivity.py b/tests/test_exclusivity.py index a3c09200..b91cad3f 100644 --- a/tests/test_exclusivity.py +++ b/tests/test_exclusivity.py @@ -44,8 +44,8 @@ 'capacity': {'dims': ['storage']}, 'age': {'dims': ['storage'], 'dtype': 'int'}, }, - 'variables': {'soc': {'foreach': ['snapshot', 'storage']}}, - 'constraints': {'balance': {'foreach': ['snapshot', 'storage'], 'expression': 'soc == 1'}}, + 'variables': {'soc': {'dims': ['snapshot', 'storage']}}, + 'constraints': {'balance': {'dims': ['snapshot', 'storage'], 'expression': 'soc == 1'}}, } @@ -201,8 +201,8 @@ def test_a_pair_with_more_regions_than_the_budget(self): model = { 'dimensions': {'generator': {}}, 'parameters': {f'p{axis}': {'dims': ['generator']} for axis in range(4)}, - 'variables': {'x': {'foreach': ['generator']}}, - 'constraints': {'c': {'foreach': ['generator'], 'expression': 'x >= 0'}}, + 'variables': {'x': {'dims': ['generator']}}, + 'constraints': {'c': {'dims': ['generator'], 'expression': 'x >= 0'}}, } [refusal] = refusals(to_spec(model), {'wide': bands, 'rest': 'not (' + bands + ')'}) assert f'exceeds the budget of {CELL_BUDGET}' in refusal diff --git a/tests/test_expansion.py b/tests/test_expansion.py index df0a80cf..2b8bc944 100644 --- a/tests/test_expansion.py +++ b/tests/test_expansion.py @@ -237,5 +237,5 @@ def test_macro_templates_validated_even_when_unused(macros, match): @pytest.mark.parametrize('fragment', ['my_python_helper', 'macros:', 'escape']) def test_an_unknown_operator_is_refused_at_load_with_the_rewrite(fragment): with pytest.raises(LanguageError) as exc: - schema(constraints={'c': {'foreach': ['snapshot'], 'expression': 'my_python_helper(p) <= load'}}) + schema(constraints={'c': {'dims': ['snapshot'], 'expression': 'my_python_helper(p) <= load'}}) assert fragment in str(exc.value) diff --git a/tests/test_lowering.py b/tests/test_lowering.py index 3bab6500..f743319f 100644 --- a/tests/test_lowering.py +++ b/tests/test_lowering.py @@ -77,8 +77,8 @@ TINY = { 'dimensions': {'g': {}}, 'parameters': {'cost': {'dims': ['g']}}, - 'variables': {'p': {'foreach': ['g'], 'bounds': {'lower': 0, 'upper': 1}}}, - 'constraints': {'c': {'foreach': [], 'expression': 'sum(p, over=g) >= 1'}}, + 'variables': {'p': {'dims': ['g'], 'bounds': {'lower': 0, 'upper': 1}}}, + 'constraints': {'c': {'dims': [], 'expression': 'sum(p, over=g) >= 1'}}, } #: `fixtures.SMALL_MODEL` plus a second lookup and a per-entity @@ -129,13 +129,13 @@ def test_lower_program_structure(dispatch_program): assert list(dispatch_program.parameters) == ['p_max', 'load', 'cost'], 'keyed by name, in declaration order' ((vname, v),) = dispatch_program.variables.items() assert vname == 'p' - assert v.dims == ('snapshot', 'generator'), 'the frame is the foreach, in the order the file wrote it' + assert v.dims == ('snapshot', 'generator'), 'the frame is the dims, in the order the file wrote it' assert v.where == Mask(P_MAX_POSITIVE) assert v.upper == Parameter('p_max') ((cname, c),) = dispatch_program.constraints.items() assert cname == 'power_balance' - assert c.dims == ('snapshot',), 'the frame is the foreach, in the order the file wrote it' + assert c.dims == ('snapshot',), 'the frame is the dims, in the order the file wrote it' assert c.lhs == Sum(Variable('p'), ('generator',)) assert c.sense == '==', "the comparison crosses as the file's own operator, untranslated" assert c.rhs == Parameter('load') @@ -607,7 +607,7 @@ def _footprint_of(constraint: str, objective: str) -> Footprint: return to_program( override( TINY, - constraints={'k': {'foreach': ['g'], 'expression': constraint}}, + constraints={'k': {'dims': ['g'], 'expression': constraint}}, objective={'sense': 'minimize', 'expression': objective}, ) ).footprint @@ -678,10 +678,10 @@ def test_a_dimension_carries_the_dtype_its_labels_are_checked_against(): CASED = { 'dimensions': {'t': {'dtype': 'int'}, 'g': {'dtype': 'str'}}, 'parameters': {'committable': {'dims': ['g'], 'dtype': 'bool'}, 'initial': {'dims': ['g']}}, - 'variables': {'status': {'foreach': ['t', 'g'], 'domain': 'binary'}}, + 'variables': {'status': {'dims': ['t', 'g'], 'domain': 'binary'}}, 'expressions': { 'previous': { - 'foreach': ['t', 'g'], + 'dims': ['t', 'g'], 'cases': { 'always_on': {'when': 'not committable', 'expression': 1}, 'boundary': {'when': 'committable and position(t) == 0', 'expression': 'initial'}, @@ -689,7 +689,7 @@ def test_a_dimension_carries_the_dtype_its_labels_are_checked_against(): 'otherwise': 'shift(status, over=t, offset=1)', } }, - 'constraints': {'no_restart': {'foreach': ['t', 'g'], 'expression': 'status - previous <= 1'}}, + 'constraints': {'no_restart': {'dims': ['t', 'g'], 'expression': 'status - previous <= 1'}}, } @@ -850,8 +850,8 @@ def test_a_lowered_spec_still_pickles_and_lowers_to_the_same_program(): { 'dimensions': {'t': {'dtype': 'int'}, 'g': {'dtype': 'str'}}, 'parameters': {'load': {'dims': ['t']}, 'cost': {'dims': ['g']}}, - 'variables': {'p': {'foreach': ['t', 'g'], 'bounds': {'lower': 0}}}, - 'constraints': {'balance': {'foreach': ['t'], 'expression': 'sum(p, over=g) >= load'}}, + 'variables': {'p': {'dims': ['t', 'g'], 'bounds': {'lower': 0}}}, + 'constraints': {'balance': {'dims': ['t'], 'expression': 'sum(p, over=g) >= load'}}, 'expressions': {'spend': 'sum(p * cost, over=g)'}, 'objective': {'sense': 'minimize', 'expression': 'sum(spend)'}, } @@ -878,8 +878,8 @@ def test_a_lowered_program_pickles_and_is_the_same_program(): { 'dimensions': {'t': {'dtype': 'int'}, 'g': {'dtype': 'str'}}, 'parameters': {'load': {'dims': ['t']}, 'cost': {'dims': ['g']}}, - 'variables': {'p': {'foreach': ['t', 'g'], 'bounds': {'lower': 0}}}, - 'constraints': {'balance': {'foreach': ['t'], 'expression': 'sum(p, over=g) >= load'}}, + 'variables': {'p': {'dims': ['t', 'g'], 'bounds': {'lower': 0}}}, + 'constraints': {'balance': {'dims': ['t'], 'expression': 'sum(p, over=g) >= load'}}, 'expressions': {'spend': 'sum(p * cost, over=g)'}, 'objective': {'sense': 'minimize', 'expression': 'sum(spend)'}, } @@ -902,8 +902,8 @@ def test_two_groups_of_a_program_merge_with_or_as_they_did_behind_the_proxy(): { 'dimensions': {'t': {'dtype': 'int'}}, 'parameters': {'load': {'dims': ['t']}}, - 'variables': {'p': {'foreach': ['t'], 'bounds': {'lower': 0}}}, - 'constraints': {'meet': {'foreach': ['t'], 'expression': 'p >= load'}}, + 'variables': {'p': {'dims': ['t'], 'bounds': {'lower': 0}}}, + 'constraints': {'meet': {'dims': ['t'], 'expression': 'p >= load'}}, 'objective': {'sense': 'minimize', 'expression': 'sum(p)'}, } ) diff --git a/tests/test_piecewise.py b/tests/test_piecewise.py index 1ccd7650..57e3cfd6 100644 --- a/tests/test_piecewise.py +++ b/tests/test_piecewise.py @@ -47,10 +47,10 @@ variables: p: - foreach: [snapshot] + dims: [snapshot] bounds: {lower: 0, upper: 100} op_cost: - foreach: [snapshot] + dims: [snapshot] bounds: {lower: 0} piecewise: @@ -62,7 +62,7 @@ constraints: balance: - foreach: [snapshot] + dims: [snapshot] expression: p == load objective: @@ -71,7 +71,7 @@ """ GATED = override( raw_of(NONCONVEX_YAML), - **{'variables.u': {'foreach': ['snapshot'], 'domain': 'binary'}, 'piecewise.cost_curve.activity': 'u'}, + **{'variables.u': {'dims': ['snapshot'], 'domain': 'binary'}, 'piecewise.cost_curve.activity': 'u'}, ) #: The convex curve stated as its segment lines, plus a binary the method cannot gate on. LP = override( @@ -79,20 +79,20 @@ **{ 'piecewise.cost_curve.method': 'lp', 'piecewise.cost_curve.links': [['p', 'bp_x'], ['op_cost', 'bp_y', '>=']], - 'variables.running': {'foreach': ['snapshot'], 'domain': 'binary'}, + 'variables.running': {'dims': ['snapshot'], 'domain': 'binary'}, }, ) #: The ``lp`` curve masked by one of its own values-parameters, so every check a block can carry is on it. LP_MASKED = override(LP, **{'piecewise.cost_curve.points': 'bp_x'}) -#: Two dims in the frame, so the emitted ``foreach`` has an order to get wrong. +#: Two dims in the frame, so the emitted ``dims`` has an order to get wrong. TWO_DIM = override( raw_of(NONCONVEX_YAML), **{ 'dimensions.generator': {'dtype': 'str'}, 'parameters.bp_x.dims': ['generator', 'bp'], 'parameters.bp_y.dims': ['generator', 'bp'], - 'variables.p.foreach': ['snapshot', 'generator'], - 'variables.op_cost.foreach': ['snapshot', 'generator'], + 'variables.p.dims': ['snapshot', 'generator'], + 'variables.op_cost.dims': ['snapshot', 'generator'], 'constraints.balance.expression': 'sum(p, over=generator) == load', 'objective.expression': 'sum(op_cost)', }, @@ -168,7 +168,7 @@ def test_the_emitted_foreach_follows_declaration_order(order): same way for the same names within one process — so a run that reads the set rather than the declaration fails one of the two orderings.""" schema = schema_of(TWO_DIM, dimensions={d: TWO_DIM['dimensions'][d] for d in order}) - assert expand_piecewise(schema).variables['cost_curve_lam'].foreach == order + assert expand_piecewise(schema).variables['cost_curve_lam'].dims == order @pytest.mark.parametrize( @@ -217,7 +217,7 @@ def test_any_affine_expression_is_a_legal_link(link): ), pytest.param( GATED, - {'variables.u': {'foreach': ['snapshot'], 'bounds': {'lower': 0, 'upper': 1}}}, + {'variables.u': {'dims': ['snapshot'], 'bounds': {'lower': 0, 'upper': 1}}}, 'must be binary', id='activity-must-be-binary', ), diff --git a/tests/test_separability.py b/tests/test_separability.py index abb9169f..33b95204 100644 --- a/tests/test_separability.py +++ b/tests/test_separability.py @@ -32,7 +32,7 @@ 'width': {'dims': ['u'], 'dtype': 'int'}, 'cap': {'dims': ['zone']}, }, - 'variables': {'p': {'foreach': ['h', 'u'], 'bounds': {'lower': 0}}}, + 'variables': {'p': {'dims': ['h', 'u'], 'bounds': {'lower': 0}}}, 'objective': {'sense': 'minimize', 'expression': 'sum(p * cost)'}, } @@ -41,8 +41,8 @@ def _verdict(dimension: str = 'h', **patch: Any): return ms.to_program({**BASE, **patch}).separability[dimension] -def _rows(expression: str, *, foreach: list[str] | None = None, **block: Any) -> dict[str, Any]: - return {'constraints': {'k': {'foreach': foreach or ['h', 'u'], 'expression': expression, **block}}} +def _rows(expression: str, *, dims: list[str] | None = None, **block: Any) -> dict[str, Any]: + return {'constraints': {'k': {'dims': dims or ['h', 'u'], 'expression': expression, **block}}} @pytest.mark.parametrize( @@ -67,7 +67,7 @@ def test_a_separable_model_reports_the_lookahead_a_window_needs(patch, ahead): @pytest.mark.parametrize( ('patch', 'fragment'), [ - pytest.param(_rows('sum(p, over=h) <= budget', foreach=['u']), 'sums over h', id='a-budget-over-the-horizon'), + pytest.param(_rows('sum(p, over=h) <= budget', dims=['u']), 'sums over h', id='a-budget-over-the-horizon'), pytest.param(_rows("p >= shift(p, over=h, offset=1, edge='wrap')"), 'wraps around h', id='a-cyclic-shift'), ], ) @@ -127,9 +127,9 @@ def test_a_named_reach_resolves_to_the_lookahead_its_values_need(patch, least, a def test_resolving_keeps_the_static_reach_and_what_a_lookup_decides(): verdict = _verdict( constraints={ - 'fixed': {'foreach': ['h', 'u'], 'expression': 'p >= shift(p, over=h, offset=-2, edge=0)'}, - 'named': {'foreach': ['h', 'u'], 'expression': 'p >= shift(p, over=h, offset=width, edge=0)'}, - 'grouped': {'foreach': ['h', 'u'], 'expression': 'p >= shift(p, over=h, offset=1, by=day_of, edge=0)'}, + 'fixed': {'dims': ['h', 'u'], 'expression': 'p >= shift(p, over=h, offset=-2, edge=0)'}, + 'named': {'dims': ['h', 'u'], 'expression': 'p >= shift(p, over=h, offset=width, edge=0)'}, + 'grouped': {'dims': ['h', 'u'], 'expression': 'p >= shift(p, over=h, offset=1, by=day_of, edge=0)'}, } ).resolved({'width': -1}) assert verdict.ahead == 2, 'a folded value never narrows what the model reads on its own' @@ -155,7 +155,7 @@ def test_a_read_through_a_lookup_is_undecided_on_the_axis_it_reads(): def test_a_coupling_names_the_change_that_would_lift_it(): - coupled = _verdict(**_rows('sum(p, over=h) <= budget', foreach=['u'])).coupled["constraint 'k'"] + coupled = _verdict(**_rows('sum(p, over=h) <= budget', dims=['u'])).coupled["constraint 'k'"] assert 'sum_back(within=n)' in coupled, 'a horizon total becomes a rolling one' wrapped = _verdict(**_rows("p >= shift(p, over=h, offset=1, edge='wrap')")).coupled["constraint 'k'"] assert 'position(h) == 0' in wrapped, 'a wrap becomes an opening-state seed' @@ -176,7 +176,7 @@ def test_a_sum_over_the_axis_couples_a_constraint_and_leaves_the_objective_alone every other. A verdict treating the two alike would refuse every windowable model there is — and `BASE`'s objective sums over `h` in every case above.""" assert _verdict(**_rows('p >= 0')).windowable, 'the objective sums over h and that is not a coupling' - coupled = _verdict(**_rows('sum(p, over=h) <= budget', foreach=['u'])) + coupled = _verdict(**_rows('sum(p, over=h) <= budget', dims=['u'])) assert not coupled.windowable, 'the same sum in a constraint is one' @@ -187,7 +187,7 @@ def test_a_position_inside_a_cased_region_is_found(): verdict = _verdict( expressions={ 'prev': { - 'foreach': ['h', 'u'], + 'dims': ['h', 'u'], 'cases': {'opening': {'when': 'position(h) == 0', 'expression': 0}}, 'otherwise': 'shift(p, over=h, offset=1, edge=0)', } @@ -200,8 +200,8 @@ def test_a_position_inside_a_cased_region_is_found(): def test_the_lookahead_is_the_widest_reach_of_any_block(): verdict = _verdict( constraints={ - 'near': {'foreach': ['h', 'u'], 'expression': 'p >= shift(p, over=h, offset=-1, edge=0)'}, - 'far': {'foreach': ['h', 'u'], 'expression': 'p >= shift(p, over=h, offset=-5, edge=0)'}, + 'near': {'dims': ['h', 'u'], 'expression': 'p >= shift(p, over=h, offset=-1, edge=0)'}, + 'far': {'dims': ['h', 'u'], 'expression': 'p >= shift(p, over=h, offset=-5, edge=0)'}, } ) assert verdict.ahead == 5, 'one window must see past its last row as far as any block reads' @@ -209,7 +209,7 @@ def test_the_lookahead_is_the_widest_reach_of_any_block(): def test_a_grouping_that_consumes_the_axis_couples_it(): program = ms.to_program( - {**BASE, 'constraints': {'z': {'foreach': ['h', 'zone'], 'expression': 'sum(p, by=zone_of) <= cap'}}} + {**BASE, 'constraints': {'z': {'dims': ['h', 'zone'], 'expression': 'sum(p, by=zone_of) <= cap'}}} ) verdict = program.separability['u'] assert not verdict.windowable, 'the grouping consumes u, so a window of u is a different sum' @@ -238,6 +238,6 @@ def test_a_reduction_over_several_axes_couples_every_one_of_them(): """`sum(p)` with no `over=` collapses every dimension its operand carries, so the verdict for each of them has to say so — a walk that read only the first would call the rest windowable.""" - program = ms.to_program({**BASE, 'constraints': {'all': {'foreach': [], 'expression': 'sum(p) <= budget'}}}) + program = ms.to_program({**BASE, 'constraints': {'all': {'dims': [], 'expression': 'sum(p) <= budget'}}}) assert not program.separability['h'].windowable, 'the reduction consumes h' assert not program.separability['u'].windowable, 'and u, in the same node' diff --git a/tests/test_validation.py b/tests/test_validation.py index 0e33184a..a66eb299 100644 --- a/tests/test_validation.py +++ b/tests/test_validation.py @@ -44,12 +44,12 @@ class TestValidateExpressions: ('patch', 'fragments'), [ pytest.param( - {'constraints': {'cap': {'foreach': ['g'], 'expression': 'nope <= c'}}}, + {'constraints': {'cap': {'dims': ['g'], 'expression': 'nope <= c'}}}, ("'nope' not found", "Constraint 'cap'", 'c'), id='an-unknown-name-in-a-constraint', ), pytest.param( - {'constraints': {'cap': {'foreach': ['g'], 'expression': 'p + c'}}}, + {'constraints': {'cap': {'dims': ['g'], 'expression': 'p + c'}}}, ('exactly one comparison',), id='a-constraint-without-a-comparison', ), @@ -59,12 +59,12 @@ class TestValidateExpressions: id='an-objective-with-a-comparison', ), pytest.param( - {'constraints': {'cap': {'foreach': ['g'], 'expression': 'c <= 1'}}}, + {'constraints': {'cap': {'dims': ['g'], 'expression': 'c <= 1'}}}, ('decides nothing', "Constraint 'cap'", "'c <= 1'"), id='a-comparison-with-no-variable-in-it', ), pytest.param( - {'constraints': {'cap': {'foreach': ['g'], 'expression': 'p * p * p <= c'}}}, + {'constraints': {'cap': {'dims': ['g'], 'expression': 'p * p * p <= c'}}}, ("Constraint 'cap'", 'this product is degree 3'), id='a-cubic-constraint', ), @@ -74,12 +74,12 @@ class TestValidateExpressions: id='a-variable-under-a-power', ), pytest.param( - {'constraints': {'cap': {'foreach': ['g'], 'where': 'c >', 'expression': 'p <= c'}}}, + {'constraints': {'cap': {'dims': ['g'], 'where': 'c >', 'expression': 'p <= c'}}}, ('Failed to parse where string',), id='a-malformed-where-string', ), pytest.param( - {'constraints': {'cap': {'foreach': ['g'], 'where': 'not_a_param > 0', 'expression': 'p <= c'}}}, + {'constraints': {'cap': {'dims': ['g'], 'where': 'not_a_param > 0', 'expression': 'p <= c'}}}, ("'not_a_param' not found",), id='an-unknown-name-in-a-where-used-to-evaluate-to-false', ), @@ -93,15 +93,15 @@ def test_a_bad_declaration_is_refused_at_load(self, patch, fragments): def test_the_objective_and_a_constraint_take_degree_two(self): _schema( - constraints={'floor': {'foreach': ['g'], 'expression': 'p * p >= 1'}}, + constraints={'floor': {'dims': ['g'], 'expression': 'p * p >= 1'}}, objective={'expression': 'sum(p * p * c, over=g)'}, ) def test_multiple_errors_collected(self): message = _refusal( constraints={ - 'a': {'foreach': ['g'], 'expression': 'nope <= 1'}, - 'b': {'foreach': ['g'], 'expression': 'p + 1'}, + 'a': {'dims': ['g'], 'expression': 'nope <= 1'}, + 'b': {'dims': ['g'], 'expression': 'p + 1'}, }, ) assert "'nope' not found" in message @@ -111,7 +111,7 @@ def test_multiple_errors_collected(self): ('patch', 'fragments'), [ pytest.param( - {'constraints': {'cap': {'foreach': ['g'], 'expression': 'p <= bad'}}}, + {'constraints': {'cap': {'dims': ['g'], 'expression': 'p <= bad'}}}, ("Constraint 'cap'", 'the divisor contains variables, which is not affine'), id='constraint', ), @@ -147,7 +147,7 @@ def test_a_nonlinear_entry_is_refused_where_the_math_reads_it(self, patch, fragm id='bound', ), pytest.param( - {'constraints': {'cap': {'foreach': ['g'], 'where': 'bad > 0', 'expression': 'p <= c'}}}, + {'constraints': {'cap': {'dims': ['g'], 'where': 'bad > 0', 'expression': 'p <= c'}}}, "'bad' not found", id='where', ), @@ -177,12 +177,12 @@ def test_an_unreferenced_nonlinear_entry_loads_and_is_reported(self): assert 'lcoe' in to_markdown(model), 'and the page prints it, under its own name' -def _kwarg_model(expression: str, foreach: list[str] | None = None) -> dict[str, Any]: +def _kwarg_model(expression: str, dims: list[str] | None = None) -> dict[str, Any]: """A model over (snapshot, generator), with `zone` a lookup into `bus`. `zone` deliberately targets a dim `p` does *not* carry: grouping into one it already has needs that dim twice, which is its own error. - An explicit ``foreach=[]`` is a scalar constraint; ``None`` is the + An explicit ``dims=[]`` is a scalar constraint; ``None`` is the default frame over `snapshot`. """ return { @@ -193,15 +193,15 @@ def _kwarg_model(expression: str, foreach: list[str] | None = None) -> dict[str, }, 'lookups': {'zone': {'over': 'generator', 'into': 'bus'}}, 'parameters': {'load': {'dims': ['snapshot']}}, - 'variables': {'p': {'foreach': ['snapshot', 'generator']}}, - 'constraints': {'c': {'foreach': ['snapshot'] if foreach is None else foreach, 'expression': expression}}, + 'variables': {'p': {'dims': ['snapshot', 'generator']}}, + 'constraints': {'c': {'dims': ['snapshot'] if dims is None else dims, 'expression': expression}}, } class TestDual: """`dual(c)`: a primitive legal only in an entry the math never reads, its argument a constraint name resolved against constraints alone.""" - BASE = override(SMALL_MODEL, **{'constraints.lim': {'foreach': ['g'], 'expression': 'p <= c'}}) + BASE = override(SMALL_MODEL, **{'constraints.lim': {'dims': ['g'], 'expression': 'p <= c'}}) @pytest.mark.parametrize( ('patch', 'fragments'), @@ -232,7 +232,7 @@ class TestDual: id='an-uncalled-macro-template-names-an-unknown-constraint', ), pytest.param( - {'constraints': {'lim': {'foreach': ['g'], 'expression': 'dual(lim) <= c'}}}, + {'constraints': {'lim': {'dims': ['g'], 'expression': 'dual(lim) <= c'}}}, ('a dual exists only after a solve', 'the math cannot read one'), id='a-dual-written-inside-a-constraint', ), @@ -244,7 +244,7 @@ class TestDual: pytest.param( { 'macros': {'shadow': {'args': ['x'], 'template': 'dual(x)'}}, - 'constraints': {'lim': {'foreach': ['g'], 'expression': 'shadow(lim) <= c'}}, + 'constraints': {'lim': {'dims': ['g'], 'expression': 'shadow(lim) <= c'}}, }, ('a dual exists only after a solve', 'the math cannot read one'), id='a-dual-smuggled-through-a-macro-into-a-constraint', @@ -252,7 +252,7 @@ class TestDual: pytest.param( { 'expressions': {'price': 'dual(lim)'}, - 'constraints': {'lim': {'foreach': ['g'], 'expression': 'price <= c'}}, + 'constraints': {'lim': {'dims': ['g'], 'expression': 'price <= c'}}, }, ('a dual exists only after a solve', 'keep the entry that carries it out of constraints'), id='a-dual-smuggled-through-an-entry-into-a-constraint', @@ -300,7 +300,7 @@ def test_a_dim_kwarg_typo_is_rejected(self, expression, fragments): assert fragment in message @pytest.mark.parametrize( - ('expression', 'foreach'), + ('expression', 'dims'), [ pytest.param('sum(p, over=generator) == load', ['snapshot'], id='a-sum'), pytest.param('sum(p, by=zone) == load', ['snapshot', 'bus'], id='a-grouped-sum'), @@ -312,8 +312,8 @@ def test_a_dim_kwarg_typo_is_rejected(self, expression, fragments): pytest.param('shift(p, over=snapshot, offset=1) == load', ['snapshot', 'generator'], id='a-bare-shift'), ], ) - def test_declared_dimensions_still_pass(self, expression, foreach): - to_spec(_kwarg_model(expression, foreach)) + def test_declared_dimensions_still_pass(self, expression, dims): + to_spec(_kwarg_model(expression, dims)) def test_macro_formals_are_not_mistaken_for_dimensions(self): """A formal in a dim position is legal inside the template body.""" @@ -354,7 +354,7 @@ def _schema_with_typed_a(dtype: str, expression: str) -> Spec: return _schema( **{ 'parameters.a': {'dims': ['g'], 'dtype': dtype}, - 'constraints': {'cap': {'foreach': ['g'], 'expression': expression}}, + 'constraints': {'cap': {'dims': ['g'], 'expression': expression}}, } ) @@ -427,7 +427,7 @@ def test_the_version_gates_no_behaviour(self): 'starts_at': {'over': 'period', 'into': 'snapshot'}, }, 'parameters': {'load': {'dims': ['snapshot']}}, - 'variables': {'p': {'foreach': ['snapshot']}}, + 'variables': {'p': {'dims': ['snapshot']}}, } ) @@ -481,12 +481,12 @@ class TestRulesDecidedWithoutData: ('patch', 'fragments'), [ pytest.param( - {'constraints': {'cap': {'foreach': ['g'], 'expression': 'nope <= c'}}}, + {'constraints': {'cap': {'dims': ['g'], 'expression': 'nope <= c'}}}, ("'nope' not found", "Constraint 'cap'", 'c'), id='an-unknown-name-in-a-constraint', ), pytest.param( - {'constraints': {'cap': {'foreach': ['g'], 'expression': 'p + c'}}}, + {'constraints': {'cap': {'dims': ['g'], 'expression': 'p + c'}}}, ('exactly one comparison',), id='a-constraint-without-a-comparison', ), @@ -496,12 +496,12 @@ class TestRulesDecidedWithoutData: id='an-objective-with-a-comparison', ), pytest.param( - {'constraints': {'cap': {'foreach': ['g'], 'expression': 'c <= 1'}}}, + {'constraints': {'cap': {'dims': ['g'], 'expression': 'c <= 1'}}}, ('decides nothing', "Constraint 'cap'", "'c <= 1'"), id='a-comparison-with-no-variable-in-it', ), pytest.param( - {'constraints': {'cap': {'foreach': ['g'], 'expression': 'p * p * p <= c'}}}, + {'constraints': {'cap': {'dims': ['g'], 'expression': 'p * p * p <= c'}}}, ("Constraint 'cap'", 'this product is degree 3'), id='a-cubic-constraint', ), @@ -511,12 +511,12 @@ class TestRulesDecidedWithoutData: id='a-variable-under-a-power', ), pytest.param( - {'constraints': {'cap': {'foreach': ['g'], 'where': 'c >', 'expression': 'p <= c'}}}, + {'constraints': {'cap': {'dims': ['g'], 'where': 'c >', 'expression': 'p <= c'}}}, ('Failed to parse where string',), id='a-malformed-where-string', ), pytest.param( - {'constraints': {'cap': {'foreach': ['g'], 'where': 'not_a_param > 0', 'expression': 'p <= c'}}}, + {'constraints': {'cap': {'dims': ['g'], 'where': 'not_a_param > 0', 'expression': 'p <= c'}}}, ("'not_a_param' not found",), id='an-unknown-name-in-a-where', ), @@ -612,9 +612,9 @@ class TestRulesDecidedWithoutData: id='infinite-bounds-that-cross', ), pytest.param( - {'variables.p.foreach': ['g', 'g']}, + {'variables.p.dims': ['g', 'g']}, ("Variable 'p' names dimension 'g' twice",), - id='foreach-repeats-a-dim', + id='dims-repeats-a-dim', ), pytest.param( {'parameters.c.dims': ['g', 'g']}, ("Parameter 'c' names dimension 'g' twice",), id='dims-repeat-a-dim' @@ -650,7 +650,7 @@ class TestRulesDecidedWithoutData: id='a-misspelt-key-names-the-near-miss', ), pytest.param( - {'variables.p.foreach': ['g', 'z']}, ("references undeclared dimension 'z'",), id='foreach-undeclared' + {'variables.p.dims': ['g', 'z']}, ("references undeclared dimension 'z'",), id='dims-undeclared' ), pytest.param({'parameters.c.dims': ['z']}, ("references undeclared dimension 'z'",), id='dims-undeclared'), pytest.param( @@ -783,10 +783,10 @@ def test_to_yaml_reproduces_the_model(self): assert to_spec(parse_yaml(model.to_yaml())) == model def test_an_empty_list_survives_the_round_trip(self): - """`foreach: []` is a scalar declaration, not an absence — stripping it would put the variable on every dim it names.""" - model = _schema(**{'variables.p.foreach': []}) - assert model.to_dict()['variables']['p']['foreach'] == [], 'the empty frame is written out, not dropped' - assert to_spec(model.to_dict()).variables['p'].foreach == [], 'and reads back as the scalar it declares' + """`dims: []` is a scalar declaration, not an absence — stripping it would put the variable on every dim it names.""" + model = _schema(**{'variables.p.dims': []}) + assert model.to_dict()['variables']['p']['dims'] == [], 'the empty frame is written out, not dropped' + assert to_spec(model.to_dict()).variables['p'].dims == [], 'and reads back as the scalar it declares' def test_an_empty_section_is_not_written(self): written = to_spec(DISPATCH_MODEL).to_yaml() @@ -805,7 +805,7 @@ def test_a_default_is_written_out_and_an_absence_is_not(self): CASED_BASE = { 'dimensions': {'snapshot': {'dtype': 'int'}, 'generator': {}}, 'parameters': {'p_max': {'dims': ['generator']}, 'load': {'dims': ['snapshot']}}, - 'variables': {'p': {'foreach': ['snapshot', 'generator']}}, + 'variables': {'p': {'dims': ['snapshot', 'generator']}}, } #: The one region of a quantity whose `otherwise` carries everything else. @@ -821,7 +821,7 @@ def _cased(cases: dict[str, Any] | None = None, **block: Any) -> dict[str, Any]: """`_headroom` over a cased block: `OPENING` or *cases*, an `otherwise:` of 0, and *block* on top.""" return _headroom( { - 'foreach': ['snapshot', 'generator'], + 'dims': ['snapshot', 'generator'], 'cases': OPENING if cases is None else cases, 'otherwise': 0, **block, @@ -871,19 +871,19 @@ def test_the_fallback_is_written_as_the_bare_value(self): ('block', 'fragment'), [ pytest.param( - {'expression': 'load', 'foreach': ['snapshot'], 'cases': OPENING, 'otherwise': 0}, + {'expression': 'load', 'dims': ['snapshot'], 'cases': OPENING, 'otherwise': 0}, 'this has both', id='both', ), pytest.param({'description': 'nothing at all'}, 'this has neither', id='neither'), - pytest.param({'cases': OPENING, 'otherwise': 0}, '`cases:` needs a `foreach:`', id='no-foreach'), + pytest.param({'cases': OPENING, 'otherwise': 0}, '`cases:` needs a `dims:`', id='no-dims'), pytest.param( - {'expression': 'load', 'foreach': ['snapshot']}, - '`foreach:` is only for a named expression with `cases:`', - id='foreach-alone', + {'expression': 'load', 'dims': ['snapshot']}, + '`dims:` is only for a named expression with `cases:`', + id='dims-alone', ), pytest.param( - {'foreach': ['snapshot', 'generator'], 'cases': OPENING}, + {'dims': ['snapshot', 'generator'], 'cases': OPENING}, 'a `cases:` block needs an `otherwise:`', id='no-otherwise', ), @@ -948,20 +948,20 @@ def test_a_pair_that_cannot_be_decided_is_refused_as_an_overlap_is(self): def test_the_frame_must_name_declared_dimensions(self): with pytest.raises(SchemaError, match="references undeclared dimension 'region'"): - to_spec(_cased(foreach=['snapshot', 'region'])) + to_spec(_cased(dims=['snapshot', 'region'])) def test_a_case_may_not_widen_the_frame(self): """A case is a value within the frame, and `load` carries a dim it lacks.""" cases = {'gas': {'when': "generator == 'gas'", 'expression': 'p_max'}} with pytest.raises(DimensionError, match="otherwise: the value carries dims \\['snapshot'\\]"): - to_spec(_cased(cases, foreach=['generator'], otherwise='load')) + to_spec(_cased(cases, dims=['generator'], otherwise='load')) def test_a_when_may_not_test_a_dim_outside_the_frame(self): """The same rule a variable's or a constraint's mask is held to.""" with pytest.raises( DimensionError, match=r"where-dimension 'snapshot' reads dims \['snapshot'\] outside the frame" ): - to_spec(_cased(foreach=['generator'])) + to_spec(_cased(dims=['generator'])) def test_an_unknown_name_in_a_case_is_a_load_error(self): with pytest.raises(SchemaError, match="case 'opening'"): @@ -975,10 +975,10 @@ def test_a_case_may_not_compare(self): def test_a_constraint_naming_it_carries_the_declared_frame(self): """Not the union of the cases: one narrower than the frame broadcasts.""" model = _cased() - model['constraints'] = {'spare': {'foreach': ['snapshot', 'generator'], 'expression': 'p <= headroom'}} + model['constraints'] = {'spare': {'dims': ['snapshot', 'generator'], 'expression': 'p <= headroom'}} to_spec(model) - model['constraints'] = {'spare': {'foreach': ['generator'], 'expression': 'p <= headroom'}} + model['constraints'] = {'spare': {'dims': ['generator'], 'expression': 'p <= headroom'}} with pytest.raises(DimensionError, match='snapshot'): to_spec(model) @@ -991,7 +991,7 @@ def test_a_fault_in_an_arm_names_the_declaration_and_is_reported_once(self): """ model = _cased({'opening': {'when': 'position(snapshot) == 0', 'expression': 'nope'}}) model['constraints'] = { - name: {'foreach': ['snapshot', 'generator'], 'expression': f'p <= headroom + {n}'} + name: {'dims': ['snapshot', 'generator'], 'expression': f'p <= headroom + {n}'} for n, name in enumerate(('cap', 'floor')) } message = _refusal(model) @@ -1006,7 +1006,7 @@ def test_the_fallback_is_not_named_as_a_case(self): second time and where the label was read off the arm. """ model = _cased(otherwise='nope') - model['constraints'] = {'cap': {'foreach': ['snapshot', 'generator'], 'expression': 'p <= headroom'}} + model['constraints'] = {'cap': {'dims': ['snapshot', 'generator'], 'expression': 'p <= headroom'}} message = _refusal(model) assert "Named expression 'headroom', otherwise: 'nope' not found" in message assert "case 'otherwise'" not in message, 'the fallback is not one of the cases' @@ -1014,13 +1014,13 @@ def test_the_fallback_is_not_named_as_a_case(self): def test_a_case_may_name_another_expression(self): model = _cased({'opening': {'when': 'position(snapshot) == 0', 'expression': 'spare'}}) model['expressions']['spare'] = 'p_max * 2' - model['constraints'] = {'cap': {'foreach': ['snapshot', 'generator'], 'expression': 'p <= headroom'}} + model['constraints'] = {'cap': {'dims': ['snapshot', 'generator'], 'expression': 'p <= headroom'}} to_spec(model) def test_a_macro_may_name_one(self): model = _cased() model['macros'] = {'twice': {'args': ['x'], 'template': 'x * 2'}} - model['constraints'] = {'cap': {'foreach': ['snapshot', 'generator'], 'expression': 'p <= twice(headroom)'}} + model['constraints'] = {'cap': {'dims': ['snapshot', 'generator'], 'expression': 'p <= twice(headroom)'}} to_spec(model) @@ -1081,10 +1081,10 @@ def test_a_name_no_expression_could_write_is_refused(self, section: str, name: s 'dimensions': {'dtype': 'str'}, 'lookups': {'over': 'g', 'into': 'h'}, 'parameters': {'dims': ['g']}, - 'variables': {'foreach': ['g']}, + 'variables': {'dims': ['g']}, 'expressions': {'expression': 'c'}, 'macros': {'args': ['x'], 'template': 'x * 2'}, - 'constraints': {'foreach': ['g'], 'expression': 'p <= c'}, + 'constraints': {'dims': ['g'], 'expression': 'p <= c'}, 'piecewise': {'over': 'g', 'links': [['p', 'c'], ['q', 'c']], 'method': 'convex'}, 'sos': {'variable': 'p', 'over': 'g', 'type': 1}, } @@ -1168,11 +1168,11 @@ def record(*args, **kwargs): **{ 'variables.p.where': 'p_max > 0', 'expressions.headroom': { - 'foreach': ['snapshot', 'generator'], + 'dims': ['snapshot', 'generator'], 'cases': {'opening': {'when': 'position(snapshot) == 0', 'expression': 'p_max'}}, 'otherwise': 'p_max - p', }, - 'constraints.spare': {'foreach': ['snapshot', 'generator'], 'expression': 'p <= headroom'}, + 'constraints.spare': {'dims': ['snapshot', 'generator'], 'expression': 'p <= headroom'}, }, ) ) diff --git a/tests/test_yaml_loading.py b/tests/test_yaml_loading.py index 083dd745..d4fd5a23 100644 --- a/tests/test_yaml_loading.py +++ b/tests/test_yaml_loading.py @@ -24,12 +24,12 @@ cost: {dims: [generator]} variables: p: - foreach: [snapshot, generator] + dims: [snapshot, generator] where: "cost > 0" bounds: {lower: 0, upper: 100} constraints: balance: - foreach: [snapshot] + dims: [snapshot] expression: sum(p, over=generator) == 5 objective: expression: sum(p * cost) @@ -75,14 +75,14 @@ def test_the_loader_yields_plain_types(tmp_path): schema = to_spec(raw) assert all(type(name) is str for name in schema.dimensions), 'a declaration is keyed by a plain str' - assert type(schema.variables['p'].foreach) is list - assert all(type(d) is str for d in schema.variables['p'].foreach) + assert type(schema.variables['p'].dims) is list + assert all(type(d) is str for d in schema.variables['p'].dims) def test_duplicate_key_is_an_error_naming_both_lines(tmp_path): """PyYAML keeps the last one, discarding a declaration the file contains.""" path = _write( - tmp_path, MODEL.replace('constraints:\n', 'constraints:\n balance:\n foreach: []\n equations: []\n') + tmp_path, MODEL.replace('constraints:\n', 'constraints:\n balance:\n dims: []\n equations: []\n') ) first = MODEL.splitlines().index(' balance:') + 1 @@ -101,12 +101,12 @@ def test_a_merge_key_override_is_not_a_duplicate(tmp_path): """`<<:` then a key of the same name is an override — the point of merging.""" path = _write( tmp_path, - 'defaults: &d\n foreach: [generator]\n' + 'defaults: &d\n dims: [generator]\n' 'dimensions:\n generator: {dtype: str}\n' - 'variables:\n p:\n <<: *d\n foreach: [generator]\n', + 'variables:\n p:\n <<: *d\n dims: [generator]\n', ) - assert read_yaml(path)['variables']['p']['foreach'] == ['generator'], 'the explicit key wins over the merged one' + assert read_yaml(path)['variables']['p']['dims'] == ['generator'], 'the explicit key wins over the merged one' @pytest.mark.parametrize( diff --git a/tests/typesetting/golden/model.yaml b/tests/typesetting/golden/model.yaml index f093df2b..e1ebe59d 100644 --- a/tests/typesetting/golden/model.yaml +++ b/tests/typesetting/golden/model.yaml @@ -43,36 +43,36 @@ parameters: variables: p: # both bounds, and a where with all three connectives - foreach: [snapshot, generator] + dims: [snapshot, generator] where: "p_max > 0 AND NOT is_flexible OR p_min > 0" bounds: { lower: p_min, upper: p_max } spill: # lower only - foreach: [snapshot] + dims: [snapshot] bounds: { lower: 0 } slack: # upper only - foreach: [snapshot] + dims: [snapshot] bounds: { upper: 100 } theta: # unbounded - foreach: [bus] + dims: [bus] on: # a binary domain, which is a set rather than a pair of bounds - foreach: [snapshot, generator] + dims: [snapshot, generator] domain: binary units: # an integer domain, which is both: bounds, and where the values live - foreach: [generator] + dims: [generator] domain: integer bounds: { lower: 0, upper: 10 } spare: # integer with neither bound: the domain is the whole line - foreach: [generator] + dims: [generator] domain: integer - reserve: # an empty foreach: a scalar declaration, whose line carries no quantifier - foreach: [] + reserve: # an empty dims: a scalar declaration, whose line carries no quantifier + dims: [] bounds: { lower: 0 } headroom: # scalar too, but masked, so the condition stands with no set beside it - foreach: [] + dims: [] where: "budget" bounds: { lower: 0 } weight: # the family a sos runs along - foreach: [snapshot, generator] + dims: [snapshot, generator] bounds: { lower: 0, upper: 1 } sos: @@ -89,7 +89,7 @@ expressions: marginal_price: dual(balance) # the row dual of a constraint, the one builtin only an entry the math never reads may call startup_cost: # a quantity defined by region: no two cases overlap, and `otherwise` is the rest description: what starting a unit in this snapshot costs, which the horizon's edge changes - foreach: [snapshot, generator] + dims: [snapshot, generator] cases: opening: { when: "position(snapshot) == 0", expression: cost } winter: { when: "position(snapshot) > 0 and season_of == 'winter'", expression: cost * 2 } @@ -97,105 +97,105 @@ expressions: constraints: budgeted: # names the plain expression: its symbol prints here, its definition once below - foreach: [snapshot] + dims: [snapshot] expression: spend <= budget starts: # names the cased expression: its symbol prints here, its block once below - foreach: [snapshot, generator] + dims: [snapshot, generator] expression: p <= startup_cost balance: # sum over a lookup - foreach: [snapshot, bus] + dims: [snapshot, bus] expression: sum(p, by=gen_bus) + spill - slack == load ramp: # roll (cyclic) and shift (acyclic) in one equation - foreach: [snapshot, generator] + dims: [snapshot, generator] expression: p - shift(p, over=snapshot, offset=1, edge='wrap') <= shift(p, over=snapshot, offset=1) + p_max edges: # the two translations `ramp` leaves out: a fill, and forwards - foreach: [snapshot, generator] + dims: [snapshot, generator] expression: >- shift(p, over=snapshot, offset=1, edge=0) <= shift(p, over=snapshot, offset=-1, edge=0) + p_max ahead: # the cyclic translation forwards, which is a fourth symbol again - foreach: [snapshot, generator] + dims: [snapshot, generator] expression: p <= shift(p, over=snapshot, offset=-1, edge='wrap') composed: # two steps of one policy are one step; a zero step is none at all - foreach: [snapshot, generator] + dims: [snapshot, generator] expression: shift(shift(p, over=snapshot, offset=1), over=snapshot, offset=1) <= shift(p_max, over=generator, offset=0) uncomposed: # a named offset under a numbered one stays two steps, not their sum - foreach: [snapshot, generator] + dims: [snapshot, generator] expression: shift(shift(p, over=snapshot, offset=lead, edge=0), over=snapshot, offset=1) <= p_max crossed: # two dimensions translated at one leaf, each with its own policy - foreach: [snapshot, generator] + dims: [snapshot, generator] expression: shift(shift(p, over=snapshot, offset=1, edge='wrap'), over=generator, offset=-1) <= p_max lead_time: # an offset the data carries, so it prints as a symbol rather than a number - foreach: [snapshot, generator] + dims: [snapshot, generator] expression: shift(p, over=snapshot, offset=lead, edge=0) <= p_max in_season: # a translation partitioned by a lookup: the group rides on the operator - foreach: [snapshot, generator] + dims: [snapshot, generator] expression: p <= shift(p, over=snapshot, offset=1, edge='wrap', by=season_of) held_in_season: # the same group, with a fill: each season's opening row is kept and given a zero - foreach: [snapshot, generator] + dims: [snapshot, generator] expression: p <= shift(p, over=snapshot, offset=1, edge=0, by=season_of) window: # a trailing window of fixed width - foreach: [snapshot, generator] + dims: [snapshot, generator] expression: sum_back(on, over=snapshot, within=3) <= units history: # the same window, its width in the data and its edge wrapped - foreach: [snapshot, generator] + dims: [snapshot, generator] expression: sum_back(on, over=snapshot, within=min_up, edge='wrap') <= units seasonal_window: # a window partitioned by a lookup: the group rides on the operator - foreach: [snapshot, generator] + dims: [snapshot, generator] expression: sum_back(on, over=snapshot, within=3, by=season_of) <= units pullback: # at(), which re-indexes through a lookup instead of an offset - foreach: [snapshot, bus] + dims: [snapshot, bus] expression: spill <= at(zone_cap, by=zone_of) grouped_twice: # one grouping through two maps: the domain carries both conditions - foreach: [snapshot, bus, technology] + dims: [snapshot, bus, technology] expression: sum(p, by=[gen_bus, gen_tech]) <= tech_cap pulled_back_twice: # its adjoint, reading one slot through a pair of labels - foreach: [generator] + dims: [generator] expression: units <= at(tech_cap, by=[gen_bus, gen_tech]) arithmetic: # division, both unary signs, a sign beside a sign, floats with and without an exponent, bracketing - foreach: [snapshot] + dims: [snapshot] expression: >- sum(p / 2 + -cost - -1e-5 * p + 2.5e-7 * cost + 0.5 * p, over=generator) >= -sum(+p, over=generator) * -3 total: # a sum naming no dim, whose domain is the one place the dims it took are said - foreach: [] + dims: [] expression: sum(p) <= budget scalar: # a parameter over nothing, and a mask that is a bare parameter - foreach: [generator] + dims: [generator] where: "cost" expression: units <= budget running: # a mask on a variable's existence, and one on a dimension's label - foreach: [snapshot, bus] + dims: [snapshot, bus] where: "theta AND snapshot >= 3" expression: theta <= load first: # a position in a dimension, and the same position within a group - foreach: [snapshot, generator] + dims: [snapshot, generator] where: "position(snapshot) == 0 OR position(snapshot, by=season_of) == 0" expression: on == 1 last: # the same two counted from the end, which print against a size rather than as themselves - foreach: [snapshot, generator] + dims: [snapshot, generator] where: "position(snapshot) == -1 OR position(snapshot, by=season_of) == -1" expression: on == 0 northern: # a lookup compared to a label, to another lookup, and to nothing - foreach: [snapshot, bus] + dims: [snapshot, bus] where: "zone_of == 'north' AND zone_of != area_of AND zone_of" expression: slack <= load efficiency: # a Greek-named parameter, which is given — so the convention wins and it prints as the word - foreach: [snapshot, generator] + dims: [snapshot, generator] expression: p <= eta * p_max ceiling: # the infinity literal, which is the one way infinity prints - foreach: [bus] + dims: [bus] expression: theta <= inf always: # a mask that is only the constant true, which the language says is no mask at all — so none prints - foreach: [snapshot] + dims: [snapshot] where: "true" expression: spill >= 0 redundant: # the same constant *inside* a mask, where it is what the file says and prints - foreach: [snapshot] + dims: [snapshot] where: "True AND spill" expression: spill >= 0 never: # the other constant mask, which says the rows are none and is worth seeing - foreach: [snapshot] + dims: [snapshot] where: "false" expression: slack >= 0 diff --git a/tests/typesetting/test_cases.py b/tests/typesetting/test_cases.py index 6c96667e..70da82ee 100644 --- a/tests/typesetting/test_cases.py +++ b/tests/typesetting/test_cases.py @@ -23,9 +23,9 @@ from math_spec.typesetting.format import Format #: One region and the fallback. `opening` is a column and `otherwise` a scalar, -#: so the cases alone would not give a quantity its shape — the `foreach` does. +#: so the cases alone would not give a quantity its shape — the `dims` does. BY_REGION = { - 'foreach': ['snapshot', 'generator'], + 'dims': ['snapshot', 'generator'], 'cases': {'opening': {'when': 'position(snapshot) == 0', 'expression': 'p_max'}}, 'otherwise': 0, } @@ -35,7 +35,7 @@ DISPATCH, **{ 'expressions.headroom': BY_REGION, - 'constraints.spare': {'foreach': ['snapshot', 'generator'], 'expression': 'p <= headroom'}, + 'constraints.spare': {'dims': ['snapshot', 'generator'], 'expression': 'p <= headroom'}, }, ) @@ -45,7 +45,7 @@ CASED, **{ 'expressions.headroom.cases.opening.expression': 'p', - 'expressions.opening_cost.foreach': ['snapshot', 'generator'], + 'expressions.opening_cost.dims': ['snapshot', 'generator'], 'expressions.opening_cost.cases': { 'opening': {'when': 'position(snapshot) == 0', 'expression': 'headroom * cost'}, }, diff --git a/tests/typesetting/test_cli.py b/tests/typesetting/test_cli.py index 40c5b5ea..ef4f65a9 100644 --- a/tests/typesetting/test_cli.py +++ b/tests/typesetting/test_cli.py @@ -56,7 +56,7 @@ def test_the_verbs_are_check_and_the_formats_and_nothing_else(): parameters: c: {dims: [g]} variables: - p: {foreach: [g], bounds: {lower: 0}} + p: {dims: [g], bounds: {lower: 0}} objective: {sense: minimize, expression: "sum(p * c)"} """ diff --git a/tests/typesetting/test_declaration.py b/tests/typesetting/test_declaration.py index da889e1b..d1872884 100644 --- a/tests/typesetting/test_declaration.py +++ b/tests/typesetting/test_declaration.py @@ -28,7 +28,7 @@ 'expressions.spend': 'sum(p * cost, over=generator)', 'expressions.total': 'sum(p)', 'expressions.priced': 'cost * 2', - 'constraints.budgeted': {'foreach': ['snapshot'], 'where': 'load > 0', 'expression': 'spend <= 10'}, + 'constraints.budgeted': {'dims': ['snapshot'], 'where': 'load > 0', 'expression': 'spend <= 10'}, }, ) @@ -77,7 +77,7 @@ ], ) def test_a_declaration_prints_the_line_the_whole_model_render_prints(model: dict, name: str, expected: str): - """The frame comes from the declared `foreach` of a cased expression, a + """The frame comes from the declared `dims` of a cased expression, a constraint or a variable, and from the body's own dims of a plain expression; the given/chosen cut a variable inside it decides.""" assert typeset_declaration(model, name, 'latex') == expected @@ -138,7 +138,7 @@ def test_a_name_declared_as_none_of_the_three_is_refused(name: str, match: str): def test_a_name_shared_by_a_constraint_and_a_variable_is_refused_rather_than_guessed(): """Constraints sit outside the flat namespace, so the model admits the pair; one line prints one of them.""" - model = override(PLAIN, **{'constraints.p': {'foreach': ['snapshot', 'generator'], 'expression': 'p <= 1'}}) + model = override(PLAIN, **{'constraints.p': {'dims': ['snapshot', 'generator'], 'expression': 'p <= 1'}}) with pytest.raises(SchemaError, match="'p' is both a constraint and a variable"): typeset_declaration(model, 'p', 'latex') diff --git a/tests/typesetting/test_golden.py b/tests/typesetting/test_golden.py index 5fe8ffa2..e93b0712 100644 --- a/tests/typesetting/test_golden.py +++ b/tests/typesetting/test_golden.py @@ -252,8 +252,8 @@ def test_a_model_with_no_objective_prints_the_rest(): """The one arm the fixture structurally cannot take. See :data:`UNREACHABLE`.""" model = { 'dimensions': {'t': {'dtype': 'int'}}, - 'variables': {'x': {'foreach': ['t'], 'bounds': {'lower': 0}}}, - 'constraints': {'cap': {'foreach': ['t'], 'expression': 'x <= 1'}}, + 'variables': {'x': {'dims': ['t'], 'bounds': {'lower': 0}}}, + 'constraints': {'cap': {'dims': ['t'], 'expression': 'x <= 1'}}, } rendered = to_latex(model) assert 'Objective' not in rendered, 'no objective was declared, so no section says one was' diff --git a/tests/typesetting/test_walk.py b/tests/typesetting/test_walk.py index 79b5406a..0db831e7 100644 --- a/tests/typesetting/test_walk.py +++ b/tests/typesetting/test_walk.py @@ -45,7 +45,7 @@ def test_a_dimension_index_never_steals_a_letter_a_variable_owns(name: FormatNam model = { 'dimensions': {'plant': {'dtype': 'str'}, 'snapshot': {'dtype': 'int'}}, 'parameters': {'cost': {'dims': ['plant']}}, - 'variables': {'p': {'foreach': ['snapshot', 'plant'], 'bounds': {'lower': 0}}}, + 'variables': {'p': {'dims': ['snapshot', 'plant'], 'bounds': {'lower': 0}}}, 'objective': {'expression': 'sum(p * cost)'}, } text = typeset(model, name) @@ -70,8 +70,8 @@ def _masked(dtype: str) -> dict[str, object]: 'dimensions': {'g': {'dtype': 'str'}}, 'parameters': {'flag': {'dims': ['g'], 'dtype': dtype}}, 'variables': { - 'keep': {'foreach': ['g'], 'where': 'flag', 'bounds': {'lower': 0, 'upper': 1}}, - 'drop': {'foreach': ['g'], 'where': 'NOT flag', 'bounds': {'lower': 0, 'upper': 1}}, + 'keep': {'dims': ['g'], 'where': 'flag', 'bounds': {'lower': 0, 'upper': 1}}, + 'drop': {'dims': ['g'], 'where': 'NOT flag', 'bounds': {'lower': 0, 'upper': 1}}, }, 'objective': {'sense': 'minimize', 'expression': 'sum(keep, over=g)'}, } @@ -114,10 +114,8 @@ def _storage(shift: str) -> dict[str, object]: """ return { 'dimensions': {'snapshot': {'dtype': 'int'}}, - 'variables': {'soc': {'foreach': ['snapshot'], 'bounds': {'lower': 0, 'upper': 100}}}, - 'constraints': { - 'balance': {'foreach': ['snapshot'], 'expression': f'soc == shift(soc, over=snapshot, {shift})'} - }, + 'variables': {'soc': {'dims': ['snapshot'], 'bounds': {'lower': 0, 'upper': 100}}}, + 'constraints': {'balance': {'dims': ['snapshot'], 'expression': f'soc == shift(soc, over=snapshot, {shift})'}}, } @@ -159,10 +157,10 @@ def test_a_fill_and_a_group_take_the_operators_two_slots(name: FormatName, fmt: model = { 'dimensions': {'snapshot': {'dtype': 'int'}, 'season': {'dtype': 'str'}}, 'lookups': {'season_of': {'over': 'snapshot', 'into': 'season'}}, - 'variables': {'p': {'foreach': ['snapshot'], 'bounds': {'lower': 0}}}, + 'variables': {'p': {'dims': ['snapshot'], 'bounds': {'lower': 0}}}, 'constraints': { 'held': { - 'foreach': ['snapshot'], + 'dims': ['snapshot'], 'expression': 'p <= shift(p, over=snapshot, offset=1, edge=0, by=season_of)', } }, @@ -193,10 +191,10 @@ def test_a_translation_under_a_pullback_survives_it(name: FormatName, fmt: Forma }, 'lookups': {'period_of': {'over': 'snapshot', 'into': 'period'}}, 'parameters': {'cap': {'dims': ['period']}}, - 'variables': {'p': {'foreach': ['snapshot'], 'bounds': {'lower': 0}}}, + 'variables': {'p': {'dims': ['snapshot'], 'bounds': {'lower': 0}}}, 'constraints': { 'within': { - 'foreach': ['snapshot'], + 'dims': ['snapshot'], 'expression': 'p <= at(shift(cap, over=period, offset=1, edge=0), by=period_of)', } }, @@ -217,10 +215,10 @@ def test_translations_that_disagree_at_the_edge_do_not_merge(name: FormatName, f """ model = { 'dimensions': {'snapshot': {'dtype': 'int'}}, - 'variables': {'soc': {'foreach': ['snapshot'], 'bounds': {'lower': 0}}}, + 'variables': {'soc': {'dims': ['snapshot'], 'bounds': {'lower': 0}}}, 'constraints': { 'b': { - 'foreach': ['snapshot'], + 'dims': ['snapshot'], 'expression': "soc <= shift(shift(soc, over=snapshot, offset=1, edge='wrap'), over=snapshot, offset=1)", } }, @@ -266,8 +264,8 @@ def test_a_negative_fill_prints(name: FormatName, fmt: Format): model = { 'dimensions': {'g': {}}, 'parameters': {'cap': {'dims': ['g']}}, - 'variables': {'p': {'foreach': ['g']}}, - 'constraints': {'k': {'foreach': ['g'], 'expression': 'p <= shift(cap, over=g, offset=1, edge=-1)'}}, + 'variables': {'p': {'dims': ['g']}}, + 'constraints': {'k': {'dims': ['g'], 'expression': 'p <= shift(cap, over=g, offset=1, edge=-1)'}}, } assert fmt.operators['edge_minus'] in typeset(model, name, legend=False) @@ -277,8 +275,8 @@ def _selected(mask: str) -> dict[str, Any]: return { 'dimensions': {'snapshot': {'dtype': 'int'}, 'season': {'dtype': 'str'}}, 'lookups': {'season_of': {'over': 'snapshot', 'into': 'season'}}, - 'variables': {'soc': {'foreach': ['snapshot'], 'bounds': {'lower': 0}}}, - 'constraints': {'seed': {'foreach': ['snapshot'], 'where': mask, 'expression': 'soc == 0'}}, + 'variables': {'soc': {'dims': ['snapshot'], 'bounds': {'lower': 0}}}, + 'constraints': {'seed': {'dims': ['snapshot'], 'where': mask, 'expression': 'soc == 0'}}, } @@ -431,7 +429,7 @@ def test_a_dual_prints_the_constraint_symbol_not_a_same_named_variable(name: For model = override( DISPATCH_MODEL, **{ - 'variables.balance': {'foreach': ['snapshot'], 'bounds': {'lower': 0}}, + 'variables.balance': {'dims': ['snapshot'], 'bounds': {'lower': 0}}, 'expressions.mp': 'dual(balance)', }, ) @@ -498,7 +496,7 @@ def test_a_given_quantity_is_upright(name: str, expected: str): def test_a_name_that_is_a_greek_letter_prints_as_the_letter(name: FormatName, fmt: Format): """A variable called `theta` set as the italic word *theta* is the one derived symbol no paper would accept.""" - model = override(DISPATCH_MODEL, **{'variables.theta': {'foreach': ['snapshot']}}) + model = override(DISPATCH_MODEL, **{'variables.theta': {'dims': ['snapshot']}}) assert fmt.greek('theta') in typeset(model, name) @@ -577,8 +575,8 @@ def test_a_dimension_is_not_a_head_a_qualifier_hangs_off(name: FormatName, fmt: 'dimensions': {'snapshot': {'dtype': 'int'}, 'generator': {'dtype': 'str'}}, 'parameters': {'cost': {'dims': ['generator']}, 'capex': {'dims': ['generator']}}, 'variables': { - 'p': {'foreach': ['snapshot', 'generator'], 'bounds': {'lower': 0}}, - 'p_nom': {'foreach': ['generator'], 'bounds': {'lower': 0}}, + 'p': {'dims': ['snapshot', 'generator'], 'bounds': {'lower': 0}}, + 'p_nom': {'dims': ['generator'], 'bounds': {'lower': 0}}, }, 'objective': {'sense': 'minimize', 'expression': 'sum(p * cost) + sum(p_nom * capex)'}, } @@ -644,14 +642,14 @@ def test_every_operator_probe_renders(path, name: FormatName, fmt: Format): 'dimensions': {'snapshot': {'dtype': 'int'}, 'generator': {'dtype': 'str'}, 'bus': {'dtype': 'str'}}, 'lookups': {'bus_of': {'over': 'generator', 'into': 'bus'}}, 'parameters': {'load': {'dims': ['snapshot']}, 'k': {'dims': []}, 'flag': {'dims': ['snapshot'], 'dtype': 'bool'}}, - 'variables': {'p': {'foreach': ['snapshot', 'generator']}, 'q': {'foreach': ['snapshot', 'generator']}}, + 'variables': {'p': {'dims': ['snapshot', 'generator']}, 'q': {'dims': ['snapshot', 'generator']}}, } def _row(expression: str, where: str | None = None, **patch: object) -> str: model = override( BUSES, - **{'constraints.k': {'foreach': ['snapshot', 'generator'], 'expression': expression, 'where': where}}, + **{'constraints.k': {'dims': ['snapshot', 'generator'], 'expression': expression, 'where': where}}, **patch, ) return next(line for line in to_latex(model, legend=False).splitlines() if line.startswith(r'\text{k}')) @@ -754,7 +752,7 @@ def test_a_string_value_in_a_where_prints_as_a_quoted_label(name: FormatName, fm model = { 'dimensions': {'plant': {'dtype': 'str'}}, 'parameters': {'fuel': {'dims': ['plant'], 'dtype': 'str'}, 'cost': {'dims': ['plant']}}, - 'variables': {'p': {'foreach': ['plant'], 'where': "fuel == 'gas_ccgt'"}}, + 'variables': {'p': {'dims': ['plant'], 'where': "fuel == 'gas_ccgt'"}}, 'objective': {'expression': 'sum(p * cost)'}, } text = typeset(model, name, legend=False)