From a137b8d51c6048109468627935e448ee3118978f Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 1 Oct 2026 11:41:20 +0000 Subject: [PATCH 01/12] fix(language): a where that reads a given expression names it as a given expression, not as a variable The namespace files a given expression with the variables, because it is read as a column. A where that read one was refused as if it read a variable. The three refusals (the left name, the right name, a side of a comparison of expressions) now name the given expression and say why a mask may not read it: it may hold a variable. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01PBmqeqBrqpQC6MVoSrguHK --- CHANGELOG.md | 1 + src/mathspec/_where_resolver.py | 30 ++++++++++++++++++++++++++---- tests/test_validation.py | 19 +++++++++++++++++++ 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6165f23e..b028ba63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ it releases that version ([RELEASING.md](https://github.com/energy-models/mathsp ## Upcoming version +- fix(language): a where that reads a given expression names it as a given expression, not as a variable ([#PR](https://github.com/energy-models/mathspec/pull/PR)) - feat(language): merge refuses a second objective, and a composed objective reads a sum that each file adds its part to ([#763](https://github.com/energy-models/mathspec/pull/763)) - docs: the composition how-to shows a sum that one file defines and other files add terms to ([#800](https://github.com/energy-models/mathspec/pull/800)) - feat(language): a term names the sum it adds to with `adds_to:`, and no file marks a sum as open ([#763](https://github.com/energy-models/mathspec/pull/763)) diff --git a/src/mathspec/_where_resolver.py b/src/mathspec/_where_resolver.py index d2d53f1b..3d52a4c1 100644 --- a/src/mathspec/_where_resolver.py +++ b/src/mathspec/_where_resolver.py @@ -72,6 +72,7 @@ TypedPredicate, VariableDefined, carries_variable, + variables_of, walk, ) @@ -81,6 +82,14 @@ from mathspec.program import DeclaredDtype from mathspec.resolution import Namespace +#: Why a mask may not read a given expression. The namespace files a given +#: expression with the variables, since it is read as a column, so the refusal +#: names it apart. +GIVEN_IN_A_MASK = ( + 'A given expression may hold a variable, and a where mask is built before variables exist — it may test ' + 'parameters and dimension coordinates only.' +) + @dataclass(frozen=True) class WhereResolver: @@ -361,7 +370,12 @@ def _expression_comparison(self, node: UnresolvedComparisonNode) -> ExpressionCo assert len(sides) == 2, 'a side of a where builds or refuses, since a where holds no formal' dims: set[str] = set() for side in sides: - if carries_variable(side): + if given := sorted(variables_of(side) & set(ns.schema.given.expressions)): + self.errors.append( + f'{context}: a where compares expressions, and one side reads the given expression ' + f'{given[0]!r}. {GIVEN_IN_A_MASK}' + ) + elif carries_variable(side): self.errors.append( f'{context}: a where compares expressions, and one side names a variable. A where mask ' f'is built before variables exist — it may test parameters and dimension coordinates only.' @@ -453,7 +467,8 @@ def _plain_comparison(self, node: UnresolvedComparisonNode, plain: _Plain) -> Pr return node dims = tuple(ns.relations[left_name].dim(k) for k in ns.relations[left_name].key) return RelationPairComparison(left_name, left, right_name, right, plain.op, dims) - self.errors.append(_declared_rhs_error(context, plain, value, rhs_kind)) + given = right_name in ns.schema.given.expressions + self.errors.append(_declared_rhs_error(context, plain, value, rhs_kind, given=given)) return node kind = ns.kind(left_name) @@ -491,6 +506,8 @@ def _plain_comparison(self, node: UnresolvedComparisonNode, plain: _Plain) -> Pr assert column is not None shape = ns.relations[left_name] return RelationComparison(left_name, column, plain.op, value, tuple(shape.dim(k) for k in shape.key)) + case 'variable' if left_name in ns.schema.given.expressions: + self.errors.append(f"{context}: where references the given expression '{left_name}'. {GIVEN_IN_A_MASK}") case 'variable': self.errors.append( f"{context}: where references variable '{left_name}'. A where " @@ -693,9 +710,14 @@ def _not_arithmetic(context: str, side: ColumnNode | KeywordNode) -> str: ) -def _declared_rhs_error(context: str, node: _Plain, value: str, kind: str) -> str: - """Why the right-hand side of a where-comparison may not name a variable, a relation or a dimension.""" +def _declared_rhs_error(context: str, node: _Plain, value: str, kind: str, *, given: bool = False) -> str: + """Why the right-hand side of a where-comparison may not name a variable, a relation or a dimension. + + A given expression is filed with the variables, and *given* says the name is one. + """ comparison = f"'{node.name} {node.op} {value}'" + if given: + return f'{context}: {comparison} compares against the given expression {value!r}. {GIVEN_IN_A_MASK}' if kind == 'variable': return ( f'{context}: {comparison} compares against variable {value!r}. ' diff --git a/tests/test_validation.py b/tests/test_validation.py index 77cdd228..03d889b8 100644 --- a/tests/test_validation.py +++ b/tests/test_validation.py @@ -758,6 +758,25 @@ def test_a_bad_comparison_of_expressions_is_refused_at_load(self, patch, fragmen for fragment in fragments: assert fragment in message + @pytest.mark.parametrize( + 'where', + [ + pytest.param('e > 0', id='on-the-left'), + pytest.param('c > e', id='on-the-right'), + pytest.param('c > 2 * e', id='inside-arithmetic'), + ], + ) + def test_a_given_expression_in_a_where_is_named_as_one(self, where): + """The refusal called a given expression a variable (#803). + + A given expression is read as a column, so the resolver files it with + the variables, and the message named the wrong kind of thing. + """ + message = _refusal(**{'given.expressions.e': {'dims': ['g']}, 'variables.p.where': where}) + assert "given expression 'e'" in message, 'the refusal names the kind the file declares' + assert "variable 'e'" not in message and 'names a variable' not in message, 'and no other kind' + assert 'may hold a variable' in message, 'it says why a given expression cannot mask' + def test_a_case_comparing_expressions_is_refused_as_undecidable(self): """Two cases split by arithmetic cannot be proved apart without the numbers, and the rewrite is named.""" message = _refusal( From 3905d3e1261191637cb8a9092f43293251d45862 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 1 Oct 2026 11:41:42 +0000 Subject: [PATCH 02/12] docs: the changelog line links #809 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01PBmqeqBrqpQC6MVoSrguHK --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b028ba63..abebee07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ it releases that version ([RELEASING.md](https://github.com/energy-models/mathsp ## Upcoming version -- fix(language): a where that reads a given expression names it as a given expression, not as a variable ([#PR](https://github.com/energy-models/mathspec/pull/PR)) +- fix(language): a where that reads a given expression names it as a given expression, not as a variable ([#809](https://github.com/energy-models/mathspec/pull/809)) - feat(language): merge refuses a second objective, and a composed objective reads a sum that each file adds its part to ([#763](https://github.com/energy-models/mathspec/pull/763)) - docs: the composition how-to shows a sum that one file defines and other files add terms to ([#800](https://github.com/energy-models/mathspec/pull/800)) - feat(language): a term names the sum it adds to with `adds_to:`, and no file marks a sum as open ([#763](https://github.com/energy-models/mathspec/pull/763)) From b34b7270fce92bded6f19c5989b79724051197c4 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 1 Oct 2026 11:48:34 +0000 Subject: [PATCH 03/12] chore: release 0.3.0 The Upcoming section becomes 0.3.0, grouped into composition, language, typesetting and advice, and documentation. The notes name the two breaks against 0.2.0 (#788, #751). The #742 line is left out: #763 replaced `empty: true` before any release carried it. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01PBmqeqBrqpQC6MVoSrguHK --- CHANGELOG.md | 75 +++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 53 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index abebee07..68f7b627 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,40 +10,71 @@ request renames that heading to the version and the day, such as `## 0.1.0 (2026-10-01)`, and edits the section into the release notes. Merging it releases that version ([RELEASING.md](https://github.com/energy-models/mathspec/blob/main/RELEASING.md)). -## Upcoming version +## 0.3.0 (2026-10-01) + +A spec can now be written as several files. [`merge`](https://mathspec.readthedocs.io/en/latest/howto/compose/) +composes **fragments**, each a spec that owns part of the math and reads what +another file declares under `given:`. [`override`](https://mathspec.readthedocs.io/en/latest/howto/compose/#a-base-and-its-patches) +lays **patches** over a base, each changing part of it. A named expression adds +itself as a term to a sum with `adds_to:`, and one fragment sets the objective. +[PyPSA in 24 files](https://mathspec.readthedocs.io/en/latest/examples/pypsa/) +is the PyPSA spec composed this way, and merges back to the one file. + +This release breaks two things: + +- A parameter value that is null or NaN is refused when the data is attached. + A coordinate with no value has no row: drop the row from the data. `inf` and + `-inf` stay values. +- `dual(c)` is the rate at which the optimal objective rises with the right + side of `c`, for every comparator and under either sense. An equality now has + a sign, and the dual of a binding `<=` row under `minimize` is no longer + non-negative. A result that reads a dual under the old sign reads it the + other way. -- fix(language): a where that reads a given expression names it as a given expression, not as a variable ([#809](https://github.com/energy-models/mathspec/pull/809)) +The pull requests in this release: + +### Composition + +- feat(language): a spec is composed from files that each state part of it, and patched with files that each change part of it ([#732](https://github.com/energy-models/mathspec/pull/732)) +- feat(language): a term names the sum it adds to with `adds_to:`, a `given: expressions:` entry of its own file, and a sum another file defines takes terms too ([#763](https://github.com/energy-models/mathspec/pull/763)) - feat(language): merge refuses a second objective, and a composed objective reads a sum that each file adds its part to ([#763](https://github.com/energy-models/mathspec/pull/763)) -- docs: the composition how-to shows a sum that one file defines and other files add terms to ([#800](https://github.com/energy-models/mathspec/pull/800)) -- feat(language): a term names the sum it adds to with `adds_to:`, and no file marks a sum as open ([#763](https://github.com/energy-models/mathspec/pull/763)) -- fix(language): a cases block with one case may compare expressions in its when: ([#795](https://github.com/energy-models/mathspec/pull/795)) -- feat(language): a parameter value that is null or NaN is refused when the data is attached ([#788](https://github.com/energy-models/mathspec/pull/788)) -- fix(language): a where string names several columns in at's over= and into=, as an expression does ([#782](https://github.com/energy-models/mathspec/pull/782)) +- fix(language): merge and override take a list of files, and its order is the order of the terms and of the patches ([#761](https://github.com/energy-models/mathspec/pull/761)) +- fix(language): a merged spec's descriptions do not depend on the order the fragments are passed in, and a reader's fills one its owner left out ([#739](https://github.com/energy-models/mathspec/pull/739)) +- fix(language): a given declaration that leaves out its domain or dtype folds into an introducer that sets one ([#768](https://github.com/energy-models/mathspec/pull/768)) - fix(language): a file whose terms read each other's sums is refused at load ([#780](https://github.com/energy-models/mathspec/pull/780)) -- fix(language): dual(c) is the rate at which the optimal objective rises with the right side of c, so an equality has a sign too ([#751](https://github.com/energy-models/mathspec/pull/751)) -- fix(language): a macro formal written inside a list takes the name the call binds to it ([#779](https://github.com/energy-models/mathspec/pull/779)) +- fix(language): a where that reads a given expression names it as a given expression, not as a variable ([#809](https://github.com/energy-models/mathspec/pull/809)) +- refactor(language): a term prints as the definition it is, and one rule folds every reading ([#738](https://github.com/energy-models/mathspec/pull/738)) +- fix(typeset): a term a file adds to a sum keeps its definition line when the expressions are inlined ([#766](https://github.com/energy-models/mathspec/pull/766)) + +### Language + +- feat(language): two files that state the same spec write one text, and `canonical --check` fails a file that is not in it ([#731](https://github.com/energy-models/mathspec/pull/731)) +- feat(language): a named expression may declare the frame it is read over ([#741](https://github.com/energy-models/mathspec/pull/741)) - feat(language): a sum names several dimensions in one over= list ([#778](https://github.com/energy-models/mathspec/pull/778)) - feat(language): a divisor or a power over parameters may add, so a discount factor is written where it is used ([#757](https://github.com/energy-models/mathspec/pull/757)) -- docs(language): a reported quotient is absent where its divisor is zero, whether a solve or the data gave that zero ([#765](https://github.com/energy-models/mathspec/pull/765)) -- fix(language): a given declaration that leaves out its domain or dtype folds into an introducer that sets one ([#768](https://github.com/energy-models/mathspec/pull/768)) +- feat(language): a parameter value that is null or NaN is refused when the data is attached ([#788](https://github.com/energy-models/mathspec/pull/788)) +- fix(language): dual(c) is the rate at which the optimal objective rises with the right side of c, so an equality has a sign too ([#751](https://github.com/energy-models/mathspec/pull/751)) +- fix(language): a cases block with one case may compare expressions in its when: ([#795](https://github.com/energy-models/mathspec/pull/795)) +- fix(language): a where string names several columns in at's over= and into=, as an expression does ([#782](https://github.com/energy-models/mathspec/pull/782)) +- fix(language): a macro formal written inside a list takes the name the call binds to it ([#779](https://github.com/energy-models/mathspec/pull/779)) + +### Typesetting and advice + +- fix(typeset): a substituted term that opens with a minus prints as a subtraction ([#759](https://github.com/energy-models/mathspec/pull/759)) - fix(advice): an objective that reads a given expression or an empty sum gets advice rather than a KeyError ([#767](https://github.com/energy-models/mathspec/pull/767)) -- fix(typeset): a term a file adds to a sum keeps its definition line when the expressions are inlined ([#766](https://github.com/energy-models/mathspec/pull/766)) -- fix(language): merge and override take a list of files, and its order is the order of the terms and of the patches ([#761](https://github.com/energy-models/mathspec/pull/761)) -- docs: the composition how-to builds a component library from terms each file adds to a sum ([#762](https://github.com/energy-models/mathspec/pull/762)) + +### Documentation + - docs(pypsa): a single spec covers every pypsa model class and component ([#620](https://github.com/energy-models/mathspec/pull/620)) - docs(pypsa): the pypsa spec is also 24 topic files that merge back to it, each component adding its share of a sum by name ([#736](https://github.com/energy-models/mathspec/pull/736)) -- fix(typeset): a substituted term that opens with a minus prints as a subtraction ([#759](https://github.com/energy-models/mathspec/pull/759)) +- docs(pypsa): an efficiency, a rate or a phase shift may change from snapshot to snapshot ([#717](https://github.com/energy-models/mathspec/pull/717)) +- docs: the composition how-to builds a component library from terms each file adds to a sum ([#762](https://github.com/energy-models/mathspec/pull/762)) +- docs: the composition how-to shows a sum that one file defines and other files add terms to ([#800](https://github.com/energy-models/mathspec/pull/800)) - docs: a file restates a shared dimension as its dtype alone, and `merge` carries the one description written for it ([#743](https://github.com/energy-models/mathspec/pull/743)) -- feat(language): a sum other files add terms to is declared as an expression with `empty: true` ([#742](https://github.com/energy-models/mathspec/pull/742)) +- docs(language): a reported quotient is absent where its divisor is zero, whether a solve or the data gave that zero ([#765](https://github.com/energy-models/mathspec/pull/765)) - docs(notation): the notation page shows a named expression whose declared frame is wider than its body ([#756](https://github.com/energy-models/mathspec/pull/756)) -- feat(language): a named expression may declare the frame it is read over ([#741](https://github.com/energy-models/mathspec/pull/741)) - docs: code examples on the site are readable in light and dark mode, and a diagram shows what mathspec leaves to engines and other tools ([#730](https://github.com/energy-models/mathspec/pull/730)) - docs: the site follows the reader's light or dark setting, and a page shows where it sits in the navigation ([#727](https://github.com/energy-models/mathspec/pull/727)) -- feat(language): a spec is composed from files that each state part of it, and patched with files that each change part of it ([#732](https://github.com/energy-models/mathspec/pull/732)) -- refactor(language): a term prints as the definition it is, and one rule folds every reading ([#738](https://github.com/energy-models/mathspec/pull/738)) -- feat(language): two files that state the same spec write one text, and `canonical --check` fails a file that is not in it ([#731](https://github.com/energy-models/mathspec/pull/731)) -- fix(language): a merged spec's descriptions do not depend on the order the fragments are passed in, and a reader's fills one its owner left out ([#739](https://github.com/energy-models/mathspec/pull/739)) -- docs(pypsa): an efficiency, a rate or a phase shift may change from snapshot to snapshot ([#717](https://github.com/energy-models/mathspec/pull/717)) ## 0.2.0 (2026-09-25) From 7a6397a273560fa49a689cd2a8ac1dd1c9164cff Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 1 Oct 2026 12:38:59 +0000 Subject: [PATCH 04/12] docs: the 0.3.0 notes say that nothing a 0.2.0 file or call does stops working #788 and #751 change only documentation and a docstring. mathspec neither attaches data nor computes duals, so neither breaks a file or an import. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01PBmqeqBrqpQC6MVoSrguHK --- CHANGELOG.md | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 68f7b627..a14f7110 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,16 +20,13 @@ itself as a term to a sum with `adds_to:`, and one fragment sets the objective. [PyPSA in 24 files](https://mathspec.readthedocs.io/en/latest/examples/pypsa/) is the PyPSA spec composed this way, and merges back to the one file. -This release breaks two things: - -- A parameter value that is null or NaN is refused when the data is attached. - A coordinate with no value has no row: drop the row from the data. `inf` and - `-inf` stay values. -- `dual(c)` is the rate at which the optimal objective rises with the right - side of `c`, for every comparator and under either sense. An equality now has - a sign, and the dual of a binding `<=` row under `minimize` is no longer - non-negative. A result that reads a dual under the old sign reads it the - other way. +Nothing a 0.2.0 file or call does stops working. Two rules of the language +are now stated exactly. A consumer that attaches data refuses a null or NaN +parameter value: a coordinate with no value has no row, and `inf` and `-inf` +are values ([#788](https://github.com/energy-models/mathspec/pull/788)). And +`dual(c)` is the rate at which the optimal objective rises with the right side +of `c`, for every comparator and under either sense, which is what engines +return ([#751](https://github.com/energy-models/mathspec/pull/751)). The pull requests in this release: From 8c347edab1d6c49791ae2e2884860be72a87039b Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 1 Oct 2026 12:41:23 +0000 Subject: [PATCH 05/12] chore: the release is 0.2.1, since nothing in it breaks a file or an import Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01PBmqeqBrqpQC6MVoSrguHK --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a14f7110..1a702640 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ request renames that heading to the version and the day, such as `## 0.1.0 (2026-10-01)`, and edits the section into the release notes. Merging it releases that version ([RELEASING.md](https://github.com/energy-models/mathspec/blob/main/RELEASING.md)). -## 0.3.0 (2026-10-01) +## 0.2.1 (2026-10-01) A spec can now be written as several files. [`merge`](https://mathspec.readthedocs.io/en/latest/howto/compose/) composes **fragments**, each a spec that owns part of the math and reads what From fbd52a40c2c5ad03a86e16ff63a94d95b2266b4e Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 1 Oct 2026 12:41:23 +0000 Subject: [PATCH 06/12] chore: a release with no break raises the patch version, features included Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01PBmqeqBrqpQC6MVoSrguHK --- AGENTS.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index 7c41ab53..f99138cf 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -43,7 +43,8 @@ To learn what the project _is_, read [docs/](docs/index.md), and deprecation cycle, `legacy_` path, or hand-written message for the old spelling. A test asserting the old behaviour is not a blocker; say in the PR where its coverage moved. A release that breaks a file or an import raises - the minor version, and its notes name the break. + the minor version, and its notes name the break. Every other release raises + the patch version, features included. - **Every `feat`, `fix`, `perf`, `refactor`, `docs` or `revert` PR adds its title under `## Upcoming version` in `CHANGELOG.md`**, with a link to the PR. The label `no changelog` opts one out; only the user sets it. From e0df43fe1d1f78dc807c3679f5a08f90eac375a0 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 1 Oct 2026 12:43:46 +0000 Subject: [PATCH 07/12] docs: the 0.2.1 notes say that every fragment typesets and gets advice on its own Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01PBmqeqBrqpQC6MVoSrguHK --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a702640..41ee080c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,9 @@ composes **fragments**, each a spec that owns part of the math and reads what another file declares under `given:`. [`override`](https://mathspec.readthedocs.io/en/latest/howto/compose/#a-base-and-its-patches) lays **patches** over a base, each changing part of it. A named expression adds itself as a term to a sum with `adds_to:`, and one fragment sets the objective. +**Every fragment typesets on its own**, in LaTeX, Typst and Markdown, and gets +advice before any merge: what it reads from another file prints in its legend, +so one component's math can be reviewed without the rest of the model. [PyPSA in 24 files](https://mathspec.readthedocs.io/en/latest/examples/pypsa/) is the PyPSA spec composed this way, and merges back to the one file. From f6b32531ab1d99a140af489ad0b726fe044b9605 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 1 Oct 2026 14:52:12 +0000 Subject: [PATCH 08/12] feat(language): a where predicate is named once under masks:, and another file reads it under given: masks: A masks: entry names a where predicate. A bare mask name in a where:, a when: or a holds:, under NOT and inside count, shift and at, stands for its predicate. A use is a NamedMask node with the predicate under it, so .atoms, .names_read, .dims and the case-overlap proof read through it, and the typesetter prints the symbol and defines the mask once under Masks, with iff. given: masks: reads another file's mask. A where reads it as boolean data over its frame, a ParameterDefined, as a where may not read a given expression. merge folds the reading into the definer; the reader's dims bound the definer's frame. Refused at load: a cycle, a predicate that folds to a literal, a predicate wider than the declared dims, a mask in arithmetic or in a comparison, and a variable that asks through a mask whether it exists. Docs, named.md masks section (n 14, median 14, over25 2, both quoted messages); declarations.md given: masks (n 8, median 13, over25 0). Closes #803. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01PBmqeqBrqpQC6MVoSrguHK --- CHANGELOG.md | 4 + docs/howto/compose.md | 1 + docs/reference/language/declarations.md | 47 +++- docs/reference/language/expressions.md | 5 +- docs/reference/language/file.md | 5 +- docs/reference/language/index.md | 2 +- docs/reference/language/named.md | 85 ++++++- docs/reference/notation.md | 53 +++++ docs/reference/reading.md | 21 +- docs/reference/typeset.md | 3 + mkdocs.yml | 2 +- schema/mathspec.schema.json | 100 +++++++- src/mathspec/_expression_resolver.py | 11 + src/mathspec/_where_resolver.py | 29 ++- src/mathspec/advising.py | 1 + src/mathspec/composition.py | 19 +- src/mathspec/exclusivity.py | 3 + src/mathspec/lowering.py | 15 ++ src/mathspec/program.py | 60 ++++- src/mathspec/resolution.py | 106 ++++++++- src/mathspec/spec.py | 74 +++++- src/mathspec/typesetting/__init__.py | 11 +- src/mathspec/typesetting/format.py | 5 +- src/mathspec/typesetting/legend.py | 17 +- src/mathspec/typesetting/symbols.py | 7 +- src/mathspec/typesetting/walk.py | 30 ++- src/mathspec/validation.py | 4 + tests/test_given.py | 4 +- tests/test_masks.py | 291 ++++++++++++++++++++++++ tests/typesetting/golden/latex.out | 13 ++ tests/typesetting/golden/markdown.out | 27 +++ tests/typesetting/golden/model.yaml | 11 + tests/typesetting/golden/typst.out | 10 + tests/typesetting/test_declaration.py | 2 +- tools/notation.py | 7 +- 35 files changed, 1018 insertions(+), 67 deletions(-) create mode 100644 tests/test_masks.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 41ee080c..2a405acc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ request renames that heading to the version and the day, such as `## 0.1.0 (2026-10-01)`, and edits the section into the release notes. Merging it releases that version ([RELEASING.md](https://github.com/energy-models/mathspec/blob/main/RELEASING.md)). +## Upcoming version + +- feat(language): a where predicate is named once under `masks:`, and another file reads it under `given: masks:` ([#PR](https://github.com/energy-models/mathspec/pull/PR)) + ## 0.2.1 (2026-10-01) A spec can now be written as several files. [`merge`](https://mathspec.readthedocs.io/en/latest/howto/compose/) diff --git a/docs/howto/compose.md b/docs/howto/compose.md index 3950adbe..af1a92f6 100644 --- a/docs/howto/compose.md +++ b/docs/howto/compose.md @@ -204,6 +204,7 @@ which each component pins at its own port. | any other declaration | one fragment declares it, and a second is refused | | an entry under `given:` | it is checked against the fragment that introduces the name, then folded into it. Its description fills the declaration where the introducer wrote none | | a given expression | the definition's body carries no dimension the reader's `dims` do not name | +| a given mask | the defining fragment's frame names no dimension the reader's `dims` do not name | | a given entry no fragment introduces | it stays under `given:` until a host model provides it | | an expression with `adds_to:` | the sum it names is the body one fragment defines, if any, followed by every term by its name, in the order of the list. [Terms](../reference/language/declarations.md#terms) gives what is refused | | `objective` | one fragment sets it, and a second is refused. Several fragments contribute to it as terms of a sum the objective reads | diff --git a/docs/reference/language/declarations.md b/docs/reference/language/declarations.md index 2cef6649..c05e75e3 100644 --- a/docs/reference/language/declarations.md +++ b/docs/reference/language/declarations.md @@ -89,8 +89,8 @@ A pinned variable is still a variable. `given:` holds what this file reads and does not build: data under `parameters:`, columns under `variables:`, named expressions under -`expressions:`, and row families under `constraints:`. It takes those four keys -and no other. A file with a `given:` block loads and prints on its own. +`expressions:`, masks under `masks:`, and row families under `constraints:`. It +takes those five keys and no other. A file with a `given:` block loads and prints on its own. ### `given: parameters` @@ -230,7 +230,8 @@ constraints: There is no body. A [term](#terms) of this file may add to the name. This file reads the name as it reads a given variable: a quantity over the frame, of degree one. A `where` does not read it, because a mask is built before any -variable exists. A name declared under both `expressions:` and +variable exists. A predicate another file defines is read as a +[given mask](#given-masks) instead. A name declared under both `expressions:` and `given: expressions:` is refused. The typeset legend lists a given expression under _Given_. @@ -334,6 +335,46 @@ like any other. fragment '#2' adds 't' to 'injection', and 't' reads 'injection' back through 'x' of '#1', so the sum would define itself. A term may not read what reads its sum: write 't' from something else, or define 'x' without 'injection'. ``` +### `given: masks` + +A given mask is a [mask](named.md#masks) this file reads and another file +defines. + +```yaml +dimensions: + period: { dtype: int } + generator: { dtype: str } +given: + variables: + p: { dims: [period, generator] } + masks: + stands: + dims: [period, generator] + description: the generator stands in this period +parameters: + ramp: { dims: [generator] } +constraints: + ramp_up: + dims: [period, generator] + where: stands AND shift(stands, along=period, offset=1) + expression: p - shift(p, along=period, offset=1) <= ramp +``` + +| Field | | | +| ------------- | ------------------------------------------- | -------------- | +| `dims` | required. The dimensions the mask runs over | | +| `description` | free text | default `null` | + +There is no predicate. A `where` reads the name as data over the frame, true +or false at each coordinate. A `where` may read a given mask where it may not +read a given expression, since a mask reads nothing a solve decides. In +arithmetic the name is refused, as a mask is. The typeset legend lists a given +mask under _Given_. + +[`merge`](../../howto/compose.md#a-library-of-components) folds a given mask +into the mask another fragment defines. The `dims` are an upper bound: the +definer's frame may name no dimension they leave out. + ## `constraints` One block is one rule. The name of the block is the name of the constraint. diff --git a/docs/reference/language/expressions.md b/docs/reference/language/expressions.md index 66062fac..6e973b17 100644 --- a/docs/reference/language/expressions.md +++ b/docs/reference/language/expressions.md @@ -72,7 +72,7 @@ a square. ## Name resolution One flat namespace covers dimensions, relations, parameters, variables, named -expressions, macros and the built-in operators. A collision is a load error that +expressions, masks, macros and the built-in operators. A collision is a load error that names both declarations, and nothing shadows anything. Position decides which kinds of name are legal: @@ -82,7 +82,7 @@ Position decides which kinds of name are legal: | expression (`p * cost`) | a variable, or a parameter whose values are numbers ([dtype](declarations.md#parameters)) | | dimension argument (`over=`, `along=`) | a dimension | | relation argument (`by=`) | a relation. `over=`, `into=` and `within=` name its columns | -| `where` string | a parameter, variable, dimension or relation ([where strings](#where-strings)) | +| `where` string | a parameter, variable, dimension, relation or mask ([where strings](#where-strings)) | | `bounds.lower` / `bounds.upper` | a parameter name, or a number | | the `edge` key of `shift` | `'wrap'` in quotes, or a bare number | | `dual` argument (`dual(c)`) | a constraint. It resolves against the constraints alone ([named expressions](named.md#reading-a-constraints-dual)) | @@ -147,6 +147,7 @@ QUOTED ::= "'" chars "'" | '"' chars '"' | `name` (bare) | variable | The variable exists at this coordinate | | `name` (bare) | relation | A row exists, read at the relation's key. A relation may be [partial](relations.md#the-data-contract), and this selects the labels that do map | | `name` (bare) | dimension | A load error. It would be true everywhere | +| `name` (bare) | mask | The mask's predicate ([masks](named.md#masks)). A given mask is true or false at each coordinate, as a `bool` parameter is | | `name OP value` | parameter | Element-wise, and a null compares false | | `name OP value` | dimension | A filter on the frame's own coordinate column | | `name OP value`, `name.col OP value` | relation | A filter on a value column, read at the relation's key. Name the column where the key determines several | diff --git a/docs/reference/language/file.md b/docs/reference/language/file.md index d635d178..56b28ae5 100644 --- a/docs/reference/language/file.md +++ b/docs/reference/language/file.md @@ -5,8 +5,8 @@ SPDX-License-Identifier: CC-BY-4.0 # File shape -A spec file is a YAML mapping with **twelve declaration keys**, plus -`version` and `description`. Any subset of the twelve is accepted. +A spec file is a YAML mapping with **thirteen declaration keys**, plus +`version` and `description`. Any subset of the thirteen is accepted. | Key | | | ------------- | ------------------------------------------------------------------------------------------------- | @@ -18,6 +18,7 @@ A spec file is a YAML mapping with **twelve declaration keys**, plus | `constraints` | the rules those decisions obey | | `objective` | what is minimised or maximised | | `expressions` | named quantities, reusable in the math and readable after a solve ([named expressions](named.md)) | +| `masks` | named `where` predicates, read by name wherever a `where` string is ([masks](named.md#masks)) | | `macros` | templates that take arguments ([macros](named.md#macros)) | | `piecewise` | piecewise-linear curves ([piecewise](piecewise.md)) | | `sos` | special-ordered sets ([sos](piecewise.md#sos)) | diff --git a/docs/reference/language/index.md b/docs/reference/language/index.md index db513116..a9bde3e5 100644 --- a/docs/reference/language/index.md +++ b/docs/reference/language/index.md @@ -46,7 +46,7 @@ names the fix. | # | Rule | | | --- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- | -| 1 | A file has twelve declaration keys, plus `version` and `description`. An unknown key is refused, with the nearest valid key named. | [File shape](file.md) | +| 1 | A file has thirteen declaration keys, plus `version` and `description`. An unknown key is refused, with the nearest valid key named. | [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. | [Names](expressions.md#name-resolution) | | 4 | Where a name may stand depends on what it is. A dimension follows `over=` or `along=`, and is never multiplied. | [Names](expressions.md#name-resolution) | diff --git a/docs/reference/language/named.md b/docs/reference/language/named.md index 30bbc682..4fa811c3 100644 --- a/docs/reference/language/named.md +++ b/docs/reference/language/named.md @@ -3,11 +3,12 @@ SPDX-FileCopyrightText: mathspec contributors SPDX-License-Identifier: CC-BY-4.0 --> -# Named expressions and macros +# Named expressions, masks and macros An `expressions:` entry names a quantity once, for the math to read or for a -solve to report. A `macros:` entry is a template with arguments, substituted -before anything reads it. +solve to report. A `masks:` entry names a `where` predicate once, for every +`where:` that reads it. A `macros:` entry is a template with arguments, +substituted before anything reads it. ## `expressions` @@ -186,6 +187,84 @@ duals are opposite. A row that `c`'s `where:` deletes has no dual. +## `masks` + +A mask names a [`where` predicate](expressions.md#where-strings) once. A +`where:`, a `when:` or a `holds:` then reads it by name: + +```yaml +dimensions: + period: { dtype: int } + generator: { dtype: str } +parameters: + build_year: { dims: [generator] } + lifetime: { dims: [generator] } + period_year: { dims: [period] } + p_nom: { dims: [generator] } +masks: + stands: + dims: [period, generator] + where: build_year <= period_year AND period_year < build_year + lifetime + description: the generator stands in this period +variables: + p: + dims: [period, generator] + where: stands + bounds: { lower: 0, upper: p_nom } +constraints: + ramp_up: + dims: [period, generator] + where: stands AND shift(stands, along=period, offset=1) + expression: p - shift(p, along=period, offset=1) <= 0.5 * p_nom +``` + +The typeset document prints the mask's symbol where it is read. The symbol is +upright, as data is. The predicate prints once, under _Masks_, with ⟺ where an +expression prints =: + +```math +\mathrm{stands}_{e,g} \iff \mathrm{build\_year}_{g} \le \mathrm{period\_year}_{e} \wedge \mathrm{period\_year}_{e} < \mathrm{build\_year}_{g} + \mathrm{lifetime}_{g} \qquad \forall\, e \in \mathcal{E},\ g \in \mathcal{G} +``` + +```math +p_{e,g} - p_{e - 1,g} \le 0.5 \cdot \mathrm{p}^{\mathrm{nom}}_{g} \qquad \forall\, e \in \mathcal{E},\ g \in \mathcal{G} \,:\, \mathrm{stands}_{e,g} \wedge \mathrm{stands}_{e - 1,g} +``` + +| Field | | | +| ------------- | ----------------------------------------------- | ------------------- | +| `where` | required. The predicate, in the `where` grammar | | +| `dims` | the frame the mask is read over | the predicate's own | +| `description` | free text | default `null` | + +An entry with no `dims:` and no `description:` may be the bare `where` string. + +- **A bare mask name stands for its predicate.** It does so in a `where:`, a + `when:` and a `holds:`, under `NOT`, and inside `count`, `shift` and `at`. + The rows are the rows the predicate written out gives. +- **A declared frame bounds the predicate.** The predicate may carry no + dimension the `dims:` do not name, and it may carry fewer: the mask is then + the same along the rest. +- **A mask may read another mask**, and a named expression that reads data + only. A cycle is refused with its chain: + + ```text + Mask 'again': circular mask reference: again -> again + ``` + +- **A mask is never a number.** In arithmetic or in a comparison it is + refused: + + ```text + Named expression 'e': 'stands' is a mask, which is true or false where it is read, and not a number. Write it bare in the where — stands, or NOT stands — rather than comparing it or computing with it. + ``` + +- **A predicate that folds to `True` or `False` is refused**, since it names + every row or none. +- **A variable's own `where:` may not ask, through a mask, whether the + variable exists.** The loader refuses this as it refuses the bare name. + +Another file reads a mask under [`given: masks`](declarations.md#given-masks). + ## `macros` A macro is a template that takes arguments and is substituted into an expression diff --git a/docs/reference/notation.md b/docs/reference/notation.md index 231f3664..761cca9b 100644 --- a/docs/reference/notation.md +++ b/docs/reference/notation.md @@ -129,6 +129,13 @@ parameters: | $`\mathit{marginal\_price}`$ | `marginal_price` over $`\mathcal{T} \times \mathcal{B}`$ | | $`\mathrm{startup\_cost}`$ | `startup_cost` over $`\mathcal{T} \times \mathcal{G}`$ — what starting a unit in this snapshot costs, which the horizon's edge changes | +#### Masks + +| Symbol | Meaning | +|---|---| +| $`\mathrm{dispatchable}`$ | `dispatchable` over $`\mathcal{G}`$ — a unit that dispatches and cannot be turned down | +| $`\mathrm{priced\_dispatch}`$ | `priced_dispatch` over $`\mathcal{G}`$ | + Upright is what the data supplies — a parameter such as $`\mathrm{p}^{\mathrm{max}}`$, a coordinate map, a label — and italic is what the solver chooses, such as $`p`$. An index is italic too, being what a quantifier chooses, and a set is script. $`t \ominus k`$ denotes cyclic translation: index $`t-k`$ taken modulo the size of the dimension (`roll`). Plain $`t-k`$ (`shift`) has no wraparound — terms translated past the edge are simply absent. @@ -793,6 +800,52 @@ expressions: \mathit{marginal\_price}_{t,b} = \lambda_{\mathrm{balance},t,b} \qquad \forall\, t \in \mathcal{T},\ b \in \mathcal{B} ``` +### Named masks + +#### Named mask + +a named predicate with its frame and its words: a use prints the symbol, the predicate prints once + +```yaml +masks: + dispatchable: + dims: [generator] + where: "p_max > 0 AND NOT is_flexible" +``` + +```math +\mathrm{dispatchable}_{g} \iff \mathrm{p}^{\mathrm{max}}_{g} > 0 \wedge \neg \mathrm{is\_flexible}_{g} \qquad \forall\, g \in \mathcal{G} +``` + +#### Mask that reads a mask + +the one-line form, reading another mask and a data-only entry + +```yaml +masks: + priced_dispatch: "dispatchable AND spend_cap > 0" +``` + +```math +\mathrm{priced\_dispatch}_{g} \iff \mathrm{dispatchable}_{g} \wedge \mathrm{spend}^{\mathrm{cap}}_{g} > 0 \qquad \forall\, g \in \mathcal{G} +``` + +#### Mask in a condition + +a mask as the where, beside a condition of the constraint's own + +```yaml +constraints: + masked: + dims: [snapshot, generator] + where: "priced_dispatch AND position(snapshot) > 0" + expression: p <= p_max +``` + +```math +p_{t,g} \le \mathrm{p}^{\mathrm{max}}_{g} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{priced\_dispatch}_{g} \wedge \mathrm{pos}(t) > 0 +``` + ### Shifts #### Cyclic and acyclic shift diff --git a/docs/reference/reading.md b/docs/reference/reading.md index 53acc422..dcfa17df 100644 --- a/docs/reference/reading.md +++ b/docs/reference/reading.md @@ -132,6 +132,12 @@ entry's expression, the same object that `program.expressions[name].expression` holds, and its value is the body's value. `children()` steps into the body, so a walk reads through it. +A `NamedMask` stands where a `masks:` entry is read. Its `body` is the entry's +predicate, the root of `program.masks[name].where`, and it is true where the +body is true. `where_children()` steps into the body, so `.atoms`, +`.names_read` and `.dims` read through it, and a consumer that builds rows +needs nothing from `program.masks`. + Every `where` arrives as a `Mask`. Its `.root` is the resolved predicate. The mask also answers four questions: @@ -165,11 +171,14 @@ stands at a mask's root or nowhere. A `Region`'s `when` is a `Mask` too. ## What a program does not build `program.given.parameters`, `program.given.variables`, -`program.given.expressions` and `program.given.constraints` name what the spec -reads and does not build ([given](language/declarations.md#given)). Every -other group is a build instruction. These four are names to look up in the -model this one is layered onto. An expression reads a given expression as a -`Variable` of that name, over the frame under `program.given.expressions`. +`program.given.expressions`, `program.given.masks` and +`program.given.constraints` name what the spec reads and does not build +([given](language/declarations.md#given)). Every other group is a build +instruction. These five are names to look up in the model this one is layered +onto. An expression reads a given expression as a `Variable` of that name, +over the frame under `program.given.expressions`. A `where` reads a given mask +as a `ParameterDefined` of that name: boolean data over the frame under +`program.given.masks`. An entry of `program.expressions` whose `adds_to` names a given expression is a term this file adds to it. The name is still one the program reads and does not build. @@ -196,7 +205,7 @@ layer.given.constraints['balance'].dims # ('snapshot', 'bus') The host model provides each name: it holds a column or a row family of that name. A consumer that builds the program checks that the host provides each name on the same frame, and refuses the program where it does not. A consumer -with no host refuses a program whose four groups are not all empty. `advice` +with no host refuses a program whose five groups are not all empty. `advice` returns one note of kind `given` per name ([what `advice` warns about](language/errors.md#what-advice-warns-about)). diff --git a/docs/reference/typeset.md b/docs/reference/typeset.md index 90b85ece..82091857 100644 --- a/docs/reference/typeset.md +++ b/docs/reference/typeset.md @@ -57,6 +57,9 @@ a flag. The [Python API](api.md#typesetting) gives each signature. `cases:` block, a [reported entry](language/named.md#reported-expressions) and a [term](language/declarations.md#terms) keep their definition line under either `inline_expressions` setting. +- A [mask](language/named.md#masks) prints its upright symbol where it is + read and its predicate once, under a **Masks** heading, with ⟺. It prints + by symbol under either `inline_expressions` setting. - Wherever the math moves an index, which every `shift` does, the document prints a line saying what that notation means. - A file that does not load does not print. diff --git a/mkdocs.yml b/mkdocs.yml index 253a94a4..84aa9c27 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -53,7 +53,7 @@ nav: - Dimensions: reference/language/dimensions.md - Relations: reference/language/relations.md - Expressions: reference/language/expressions.md - - Named expressions and macros: reference/language/named.md + - Named expressions, masks and macros: reference/language/named.md - Operators: reference/language/operators.md - Piecewise curves and SOS: reference/language/piecewise.md - Assumptions: reference/language/assumptions.md diff --git a/schema/mathspec.schema.json b/schema/mathspec.schema.json index 317fc8ff..d257c548 100644 --- a/schema/mathspec.schema.json +++ b/schema/mathspec.schema.json @@ -284,7 +284,7 @@ }, "GivenBlock": { "additionalProperties": false, - "description": "What this file reads and does not build, by kind. Closed at the four kinds.", + "description": "What this file reads and does not build, by kind. Closed at the five kinds.", "properties": { "constraints": { "additionalProperties": { @@ -302,6 +302,14 @@ "title": "Expressions", "type": "object" }, + "masks": { + "additionalProperties": { + "$ref": "#/$defs/GivenMaskBlock" + }, + "default": {}, + "title": "Masks", + "type": "object" + }, "parameters": { "additionalProperties": { "$ref": "#/$defs/GivenParameterBlock" @@ -382,6 +390,36 @@ "title": "GivenExpressionBlock", "type": "object" }, + "GivenMaskBlock": { + "additionalProperties": false, + "description": "A mask this file reads and another file defines.\n\nThe frame is all this file states. A where reads the name as data over\nthat frame, true or false at each coordinate, since a mask reads nothing\na solve decides: the predicate is the definer's.", + "properties": { + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "dims": { + "items": { + "type": "string" + }, + "title": "Dims", + "type": "array" + } + }, + "required": [ + "dims" + ], + "title": "GivenMaskBlock", + "type": "object" + }, "GivenParameterBlock": { "additionalProperties": false, "description": "Data this file reads and another file declares.\n\nIt says what a [`ParameterBlock`][] says, because the frame and the\ndtype are all a parameter declaration holds: a where compares against the\ndtype, and the dim rules read the frame.", @@ -506,6 +544,55 @@ "title": "MacroBlock", "type": "object" }, + "MaskBlock": { + "anyOf": [ + { + "additionalProperties": false, + "description": "A named predicate: one where string, read wherever a ``where:``, a ``when:`` or a ``holds:`` names it.\n\nWritten in YAML as a bare where string, or as a mapping once it carries a\n``dims:`` or a ``description:``, and serialised back to whichever form it\nwas written in::\n\n masks:\n committable: \"Generator_committable AND Generator_active\"\n stands:\n dims: [period, generator]\n where: build_year <= period_year AND period_year < build_year + lifetime\n description: the generator stands in this period\n\nA bare mask name in a where string stands for its predicate, inside\n``count``, ``shift`` and ``at`` too. ``dims:`` declares the frame the mask\nis read over; left out, the predicate decides it. A mask is a predicate,\nso it is never a value in an expression.", + "properties": { + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "dims": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Dims" + }, + "where": { + "title": "Where", + "type": "string" + } + }, + "required": [ + "where" + ], + "title": "MaskBlock", + "type": "object" + }, + { + "type": "string" + } + ] + }, "ObjectiveBlock": { "additionalProperties": false, "description": "A declared objective function.", @@ -874,7 +961,7 @@ }, "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": false, - "description": "The declared math \u2014 one YAML file, or one dict, validated. Nothing here has seen data.\n\nA ``Spec`` that exists has passed the whole language: constructing one by\nany route \u2014 ``to_spec``, [`model_validate`][], the constructor \u2014 runs\nevery load-time check, expression pass included, and raises\n[`LanguageError`][] on a spec the language refuses.\nHolding one is the proof, so nothing downstream checks it again.\n\nThe API is the twelve declaration sections plus ``version`` and\n``description``, three ways back out \u2014 [`to_dict`][] for the spec as\ndata, [`to_yaml`][] for the file a reviewer reads, [`expand`][] for the\nspec with its formulations written out as plain rows \u2014 and [`program`][], the\nspec typed, which every reader after load walks. Everything else on this\nclass is pydantic's, not a contract this package keeps.", + "description": "The declared math \u2014 one YAML file, or one dict, validated. Nothing here has seen data.\n\nA ``Spec`` that exists has passed the whole language: constructing one by\nany route \u2014 ``to_spec``, [`model_validate`][], the constructor \u2014 runs\nevery load-time check, expression pass included, and raises\n[`LanguageError`][] on a spec the language refuses.\nHolding one is the proof, so nothing downstream checks it again.\n\nThe API is the thirteen declaration sections plus ``version`` and\n``description``, three ways back out \u2014 [`to_dict`][] for the spec as\ndata, [`to_yaml`][] for the file a reviewer reads, [`expand`][] for the\nspec with its formulations written out as plain rows \u2014 and [`program`][], the\nspec typed, which every reader after load walks. Everything else on this\nclass is pydantic's, not a contract this package keeps.", "properties": { "assumptions": { "additionalProperties": { @@ -925,6 +1012,7 @@ "default": { "constraints": {}, "expressions": {}, + "masks": {}, "parameters": {}, "variables": {} } @@ -937,6 +1025,14 @@ "title": "Macros", "type": "object" }, + "masks": { + "additionalProperties": { + "$ref": "#/$defs/MaskBlock" + }, + "default": {}, + "title": "Masks", + "type": "object" + }, "objective": { "anyOf": [ { diff --git a/src/mathspec/_expression_resolver.py b/src/mathspec/_expression_resolver.py index ae0630d0..9932764e 100644 --- a/src/mathspec/_expression_resolver.py +++ b/src/mathspec/_expression_resolver.py @@ -182,6 +182,9 @@ def _name(self, node: NameNode) -> Expression | None: """ if node.name in self.formals: return None + if node.name in self.ns.masks or node.name in self.ns.given_masks: + self.errors.append(mask_compared(node.name, self.context)) + return None if node.name in self.ns.bodies: try: return cast('Expression', self.ns.named(node.name, self.context)) @@ -726,6 +729,14 @@ def _vacates(offset: int | str) -> bool: return offset != 0 +def mask_compared(name: str, context: str) -> str: + """The refusal for a mask written where a number goes: a side of a comparison, or arithmetic.""" + return ( + f"{context}: '{name}' is a mask, which is true or false where it is read, and not a number. " + f'Write it bare in the where — {name}, or NOT {name} — rather than comparing it or computing with it.' + ) + + def _named_offset_edge_message(name: str) -> str: """Why a named offset must say what the vacated positions contribute. diff --git a/src/mathspec/_where_resolver.py b/src/mathspec/_where_resolver.py index 3d52a4c1..bdbe39ef 100644 --- a/src/mathspec/_where_resolver.py +++ b/src/mathspec/_where_resolver.py @@ -28,7 +28,7 @@ names_in, nodes, ) -from mathspec._expression_resolver import ExpressionResolver +from mathspec._expression_resolver import ExpressionResolver, mask_compared from mathspec._where_parser import ( ColumnNode, UnresolvedComparisonNode, @@ -37,7 +37,7 @@ UnresolvedWhereNode, ) from mathspec.dimensions import dims_of, pulled_back_dims -from mathspec.errors import DimensionError, LanguageError, did_you_mean, prefixed +from mathspec.errors import DimensionError, LanguageError, SchemaError, did_you_mean, prefixed from mathspec.expansion import expand from mathspec.operators import ( PARTITION_NAMES_ITS_GROUP, @@ -56,6 +56,7 @@ ExpressionComparison, Mask, Multiply, + NamedMask, Negate, Not, Or, @@ -114,7 +115,7 @@ def _expressions(self) -> ExpressionResolver: def where(self, node: Predicate | UnresolvedWhereNode) -> Predicate | UnresolvedWhereNode: """One predicate node typed, or returned unresolved with its refusal appended.""" - if isinstance(node, BooleanLiteral | TypedPredicate): + if isinstance(node, BooleanLiteral | TypedPredicate | NamedMask): return node if isinstance(node, NameNode): return self._where_name(node) @@ -132,13 +133,24 @@ def where(self, node: Predicate | UnresolvedWhereNode) -> Predicate | Unresolved return Or(self._child(node.left), self._child(node.right)) assert_never(node) + @property + def masks(self) -> frozenset[str]: + """Every mask this file names, defined or given.""" + return self.ns.masks | self.ns.given_masks + def _child(self, node: Predicate | UnresolvedWhereNode) -> Predicate: """A connective's child, typed as resolved: an unresolved one survives only with its refusal appended.""" return cast('Predicate', self.where(node)) def _where_name(self, node: NameNode) -> Predicate | UnresolvedWhereNode: - """A bare name: a parameter's or relation's definedness, or a variable's existence.""" + """A bare name: a mask's predicate, a parameter's or relation's definedness, or a variable's existence.""" ns, context = self.ns, self.context + if node.name in ns.masks: + try: + return ns.mask(node.name, context) + except SchemaError as e: + self.errors.append(str(e)) + return node kind = ns.kind(node.name) if kind is None: self.errors.append(ns.unknown(node.name, context, allow_dims=True)) @@ -308,6 +320,10 @@ def _comparison(self, node: UnresolvedComparisonNode) -> Predicate | UnresolvedW """ if isinstance(node.left, FunctionCallNode) and node.left.name == 'position': return self._position(node.left, node) + compared = [name for side in (node.left, node.right) if (name := _side_name_or_none(side)) is not None] + if masked := [name for name in compared if name in self.masks]: + self.errors.append(mask_compared(masked[0], self.context)) + return node plain = self._plain(node) if plain is None: return self._expression_comparison(node) @@ -625,6 +641,11 @@ class _Plain(NamedTuple): quoted: bool +def _side_name_or_none(side: ArithmeticNode | ColumnNode | KeywordNode) -> str | None: + """The bare name a side spells, or ``None`` — a quoted label is no name.""" + return side.name if isinstance(side, NameNode) else None + + def _side_name(side: ArithmeticNode | ColumnNode) -> str | None: """The name a side of a where-comparison spells — bare or ``relation.column`` — or ``None`` where it is arithmetic.""" if isinstance(side, NameNode): diff --git a/src/mathspec/advising.py b/src/mathspec/advising.py index 91def459..35152270 100644 --- a/src/mathspec/advising.py +++ b/src/mathspec/advising.py @@ -69,6 +69,7 @@ def _given(program: Program) -> list[Advice]: for name in given.expressions ), *(Advice('given', name, _given_note('row family', name)) for name in given.constraints), + *(Advice('given', name, _given_note('mask', name)) for name in given.masks), ] diff --git a/src/mathspec/composition.py b/src/mathspec/composition.py index b6f027c1..b1d5044c 100644 --- a/src/mathspec/composition.py +++ b/src/mathspec/composition.py @@ -45,8 +45,9 @@ reads its own sum through another fragment is refused, both named. * **A given declaration is folded** into the declaration that introduces the name, once the reader is checked to say the same as the introducer or less. - A given expression's body may carry no dimension its reader does not state, - and a name read as one kind and introduced as another is refused. A + A given expression's body, and a given mask's frame, may carry no dimension + its reader does not state, and a name read as one kind and introduced as + another is refused. A reader's description fills a declaration its owner left undescribed. Two fragments that both read a name have to read it over one frame, as a set. What no fragment introduces stays under ``given:`` until a host model @@ -118,6 +119,7 @@ 'variables': 'given variable', 'constraints': 'given constraint', 'expressions': 'given expression', + 'masks': 'given mask', } #: What a fragment, a base or a patch may be given as. @@ -557,21 +559,24 @@ def _fits( """Refuse a reading that says more than the declaration it folds into. A reading states the frame its introducer declares, and every other field - it writes is the introducer's. A given expression is the one kind whose - frame may be wider than the composed body: a body over fewer dimensions - broadcasts, and the composed load refuses a row it would repeat. + it writes is the introducer's. A given expression and a given mask are the + kinds whose frame may be wider than the composed body: a body over fewer + dimensions broadcasts, and the composed load refuses a row it would repeat. """ stated = set(cast('list[str]', _mapping(reading)['dims'])) if kind == 'expressions': frame = set(_definer_frame(loaded, key)) fits = frame <= stated + elif kind == 'masks': + frame = set(next(spec.program.masks[key].dims for spec in loaded.values() if key in spec.program.masks)) + fits = frame <= stated else: frame = set(cast('list[str]', _mapping(introduced)['dims'])) fits = frame == stated fields = {f: v for f, v in _mapping(_claims(reading)).items() if f != 'dims'} if fits and all(_mapping(introduced).get(f) == v for f, v in fields.items()): return - how = f'over {sorted(frame)}' if kind == 'expressions' else f'as {introduced!r}' + how = f'over {sorted(frame)}' if kind in ('expressions', 'masks') else f'as {introduced!r}' raise LanguageError( f"fragment '{_reader_of(read, kind, key)}' reads the {GIVEN_KINDS[kind]} {key!r} as {reading!r}, where " f"'{_author_of(read, kind, key)}' introduces it {how}. A given declaration says the same as the " @@ -593,7 +598,7 @@ def _definer_frame(loaded: Mapping[str, Spec], key: str) -> frozenset[str]: return frozenset(frame) -READ_KINDS = ('parameters', 'variables', 'expressions') +READ_KINDS = ('parameters', 'variables', 'expressions', 'masks') def _same_kind(read: Mapping[str, dict[str, object]], merged: Mapping[str, object], kind: str, key: str) -> None: diff --git a/src/mathspec/exclusivity.py b/src/mathspec/exclusivity.py index 3f0964cc..45062769 100644 --- a/src/mathspec/exclusivity.py +++ b/src/mathspec/exclusivity.py @@ -30,6 +30,7 @@ DimensionPosition, ExpressionComparison, Mask, + NamedMask, Negate, Not, Or, @@ -486,6 +487,8 @@ def _evaluate(node: Predicate, cell: dict[Subject, Cell], grid: _Grid) -> bool: return _evaluate(left, cell, grid) and _evaluate(right, cell, grid) case Or(left=left, right=right): return _evaluate(left, cell, grid) or _evaluate(right, cell, grid) + case NamedMask(body=body): + return _evaluate(body, cell, grid) case _: assert_never(node) diff --git a/src/mathspec/lowering.py b/src/mathspec/lowering.py index cabeb1d8..f2605ff9 100644 --- a/src/mathspec/lowering.py +++ b/src/mathspec/lowering.py @@ -32,6 +32,7 @@ GivenTargets, Link, Mask, + MaskDeclaration, Named, ObjectiveDeclaration, Parameter, @@ -51,6 +52,7 @@ resolve_expression, resolve_expression_text, resolve_where_text, + self_existence, ) from mathspec.validation import emitted_name_errors, reference_errors @@ -128,9 +130,20 @@ def lower(schema: Spec) -> Program: terms = _terms(entries, schema, ns, errors) + masks: dict[str, MaskDeclaration] = {} + for mname, mdef in schema.masks.items(): + named_mask, refusals = ns.mask_entry(mname) + errors.extend(refusals) + if named_mask is not None: + body = Mask(named_mask.body) + frame = tuple(mdef.dims) if mdef.dims is not None else tuple(d for d in schema.dimensions if d in body.dims) + masks[mname] = MaskDeclaration(body, frame, mdef.description) + variables = {} for vname, vdef in schema.variables.items(): where = resolve_where_text(vdef.where, ns, f"Variable '{vname}'", errors, self_variable=vname) + if where is not None and (refusal := self_existence(where, vname, f"Variable '{vname}'")) is not None: + errors.append(refusal) if vdef.domain == 'binary': lower_bound, upper_bound = Constant(0.0), Constant(1.0) else: @@ -230,6 +243,7 @@ def lower(schema: Spec) -> Program: ) for name, entry in entries.items() }, + masks=masks, given=GivenTargets( parameters={ name: ParameterDeclaration(tuple(g.dims), g.dtype, g.description) @@ -244,6 +258,7 @@ def lower(schema: Spec) -> Program: expressions={ name: GivenDeclaration(tuple(g.dims), g.description) for name, g in schema.given.expressions.items() }, + masks={name: GivenDeclaration(tuple(g.dims), g.description) for name, g in schema.given.masks.items()}, ), description=schema.description, ) diff --git a/src/mathspec/program.py b/src/mathspec/program.py index fa0acc28..241f6ddd 100644 --- a/src/mathspec/program.py +++ b/src/mathspec/program.py @@ -63,8 +63,10 @@ 'GroupSum', 'Link', 'Mask', + 'MaskDeclaration', 'Multiply', 'Named', + 'NamedMask', 'Negate', 'Not', 'ObjectiveDeclaration', @@ -644,6 +646,9 @@ class GivenTargets: constraints: Mapping[str, GivenDeclaration] = Sealed({}) #: Named expressions the host model defines, by name. expressions: Mapping[str, GivenDeclaration] = Sealed({}) + #: Masks the host model defines, by name. A where reads one as the data it + #: is: a [`ParameterDefined`][] of a boolean over its frame. + masks: Mapping[str, GivenDeclaration] = Sealed({}) def __post_init__(self) -> None: for f in fields(self): @@ -651,7 +656,7 @@ def __post_init__(self) -> None: def __bool__(self) -> bool: """Whether the program reads anything it does not build.""" - return bool(self.parameters or self.variables or self.constraints or self.expressions) + return bool(self.parameters or self.variables or self.constraints or self.expressions or self.masks) @dataclass(frozen=True) @@ -720,6 +725,22 @@ class ExpressionDeclaration: adds_to: str | None = None +@dataclass(frozen=True) +class MaskDeclaration: + """A named predicate — a ``masks:`` entry, read wherever a ``where``, a ``when`` or a ``holds`` names it. + + It builds nothing of its own: every use stands as a [`NamedMask`][] with + this predicate under it, so a consumer reading a mask never looks the + name up here. + """ + + where: Mask + #: The frame the mask is read over: the ``dims:`` the entry declares, or + #: the dims its predicate carries. + dims: tuple[str, ...] + description: str | None = None + + @dataclass(frozen=True) class Link: """One link of a ``piecewise:`` block: an expression tied to the breakpoints a values parameter holds. @@ -945,6 +966,9 @@ class Program: #: named expression is outside the language is refused by every verb that #: reads the file rather than only by the one that reads the expression. expressions: Mapping[str, ExpressionDeclaration] = Sealed({}) + #: Declared ``masks:``. Each use stands as a [`NamedMask`][] where it is + #: read, so a consumer building rows needs nothing from this group. + masks: Mapping[str, MaskDeclaration] = Sealed({}) #: What this program reads and does not build ([`GivenTargets`][]), for a host model to provide. given: GivenTargets = GivenTargets() #: What the file as a whole is, as its ``description:`` says. @@ -1306,6 +1330,20 @@ class Or: right: Predicate +@dataclass(frozen=True) +class NamedMask: + """A use of a ``masks:`` entry, standing where its name was written, with the entry's predicate under it. + + Its truth is its body's: a consumer steps through it, as + [`where_children`][] does. It is kept as a node rather than written in so + the typesetter can print the symbol where the name stood and define it + once, as [`Named`][] does for an expression. + """ + + name: str + body: Predicate + + #: Every predicate resolution has typed: it names a declaration and the kind is #: settled. Resolution passes these straight through, having nothing left to #: decide about them. @@ -1324,20 +1362,21 @@ class Or: | PulledBackPredicate ) -#: The boolean connectives — the only where nodes carrying other where nodes, -#: and so the only place a walk over a predicate recurses. The grammar builds -#: these classes directly, over leaves still unresolved, so a pre-resolution -#: tree shares them — the transient impurity resolution normalizes away. +#: The boolean connectives — with [`NamedMask`][], the only where nodes +#: carrying other where nodes, and so the places a walk over a predicate +#: recurses. The grammar builds these classes directly, over leaves still +#: unresolved, so a pre-resolution tree shares them — the transient impurity +#: resolution normalizes away. Connective = Not | And | Or #: Every resolved predicate node. The parser's ``Unresolved*`` nodes are not members: they live with the #: grammar in [`mathspec._where_parser`][], and resolution rewrites them away #: before anything here is asked. -Predicate = BooleanLiteral | TypedPredicate | Connective +Predicate = BooleanLiteral | TypedPredicate | Connective | NamedMask def where_children(where: Predicate) -> tuple[Predicate, ...]: - """The predicates under *where* — a connective's operands, and nothing under a leaf. + """The predicates under *where* — a connective's operands, a named mask's body, and nothing under a leaf. What every walk over a predicate recurses through, as [`children`][] is for an expression. A leaf has nothing under it whether or not it is @@ -1347,6 +1386,8 @@ def where_children(where: Predicate) -> tuple[Predicate, ...]: return (where.operand,) if isinstance(where, (And, Or)): return (where.left, where.right) + if isinstance(where, NamedMask): + return (where.body,) return () @@ -1360,7 +1401,7 @@ def _atoms(where: Predicate) -> Iterator[TypedPredicate]: """ if isinstance(where, TypedPredicate): yield where - elif isinstance(where, BooleanLiteral | Connective): + elif isinstance(where, BooleanLiteral | Connective | NamedMask): for child in where_children(where): yield from _atoms(child) else: @@ -1477,7 +1518,8 @@ def _fold(node: Predicate) -> Predicate: none, ``NOT True`` is ``False`` and ``NOT NOT X`` is ``X``. What survives is a predicate over data, or the one literal the whole mask reduces to — the invariant [`Mask`][] applies at construction, so it holds wherever - a mask is built. + a mask is built. A [`NamedMask`][] is left whole: its body was folded + when the entry was read, and refused there where it folded to a literal. """ if isinstance(node, Not): operand = _fold(node.operand) diff --git a/src/mathspec/resolution.py b/src/mathspec/resolution.py index 3f8fcf23..738e5124 100644 --- a/src/mathspec/resolution.py +++ b/src/mathspec/resolution.py @@ -41,9 +41,11 @@ Expression, Mask, Named, + NamedMask, Predicate, Region, RelationDeclaration, + VariableDefined, carries_variable, ) @@ -53,7 +55,7 @@ from mathspec._expression_parser import ComparisonOperator, ParsedNode from mathspec._where_parser import ParsedWhere from mathspec.program import DeclaredDtype - from mathspec.spec import ExpressionBlock, Spec + from mathspec.spec import ExpressionBlock, MaskBlock, Spec #: What a name a file may write turns out to be. Answered by @@ -70,12 +72,15 @@ class Namespace: __slots__ = ( '_loading', + '_masks', '_named', 'bodies', 'constraints', 'dimensions', 'dtypes', + 'given_masks', 'leaf_dims', + 'masks', 'parameters', 'relations', 'schema', @@ -91,7 +96,12 @@ def __init__(self, schema: Spec) -> None: parameters = {**schema.parameters, **schema.given.parameters} self.variables = frozenset(variables) self.bodies = frozenset(schema.expressions) - self.parameters = frozenset(parameters) + #: The declared masks, read by name wherever a where string is. + self.masks = frozenset(schema.masks) + #: The masks this file reads under ``given:``. A where reads each as + #: the boolean data it is to this file, so each is a parameter here too. + self.given_masks = frozenset(schema.given.masks) + self.parameters = frozenset(parameters) | self.given_masks self.dimensions = frozenset(schema.dimensions) #: The declared constraint names, off the flat namespace: a bare name #: never reaches them, so a spec may name a constraint after a variable. @@ -101,6 +111,7 @@ def __init__(self, schema: Spec) -> None: #: what a where comparison checks its literal against. self.dtypes: dict[str, DeclaredDtype] = { **{p: pd.dtype for p, pd in parameters.items()}, + **{m: cast('DeclaredDtype', 'bool') for m in schema.given.masks}, **{d: dd.dtype for d, dd in schema.dimensions.items()}, } #: relation name -> its columns and key, as declared. @@ -112,14 +123,18 @@ def __init__(self, schema: Spec) -> None: #: each leaf a where names, the way a relation leaf carries ``over``. self.leaf_dims: dict[str, tuple[str, ...]] = { **{p: tuple(pd.dims) for p, pd in parameters.items()}, + **{m: tuple(md.dims) for m, md in schema.given.masks.items()}, **{v: tuple(vd.dims or ()) for v, vd in variables.items()}, } #: named expression -> its resolved node, or ``None``, and its refusals; #: filled the first time anything reads the name. self._named: dict[str, tuple[Named | None, tuple[str, ...]]] = {} - #: The named expressions waiting to be resolved, the one asked for - #: first — each above the entries it reads, so it is a cycle's chain. + #: The named expressions and masks waiting to be resolved, the one + #: asked for first — each above the entries it reads, so it is a cycle's chain. self._loading: list[str] = [] + #: mask -> its resolved node, or ``None``, and its refusals; filled + #: the first time anything reads the name. + self._masks: dict[str, tuple[NamedMask | None, tuple[str, ...]]] = {} def named(self, name: str, context: str) -> Named: """The ``expressions:`` entry *name* as the node that stands where its name is written. @@ -138,12 +153,39 @@ def named(self, name: str, context: str) -> Named: raise SchemaError(msg) return node - def cycle(self, name: str, context: str, through: Iterable[str] = ()) -> str | None: + def cycle(self, name: str, context: str, through: Iterable[str] = (), *, noun: str = 'expression') -> str | None: """The refusal for reading *name* while it is being resolved, or ``None``; *through* names the macros the read went through.""" if name not in self._loading: return None chain = ' -> '.join([*self._loading[self._loading.index(name) :], *through, name]) - return f'{context}: circular expression reference: {chain}' + return f'{context}: circular {noun} reference: {chain}' + + def mask(self, name: str, context: str) -> NamedMask: + """The ``masks:`` entry *name* as the node that stands where its name is written. + + Resolved under the entry's own context the first time it is asked + for, and read from then on, so a fault in it is reported once. + + Raises: + SchemaError: The entry reads itself, or does not load. + """ + if (refusal := self.cycle(name, context, noun='mask')) is not None: + raise SchemaError(refusal) + node, _ = self.mask_entry(name) + if node is None: + msg = f"{context}: mask '{name}' does not load. Its refusal is listed with it." + raise SchemaError(msg) + return node + + def mask_entry(self, name: str) -> tuple[NamedMask | None, tuple[str, ...]]: + """The ``masks:`` entry *name* resolved, or ``None``, with every refusal it earned.""" + if name not in self._masks: + self._loading.append(name) + errors: list[str] = [] + node = _mask(name, self.schema.masks[name], self, errors) + self._loading.pop() + self._masks[name] = (node, tuple(errors)) + return self._masks[name] def named_entry(self, name: str) -> tuple[Named | None, tuple[str, ...]]: """The ``expressions:`` entry *name* resolved, or ``None``, with every refusal it earned. @@ -228,7 +270,12 @@ def unknown(self, name: str, context: str, *, allow_dims: bool, formals: Iterabl """ shown: list[tuple[str, Iterable[str]]] = [('Formals', formals)] if formals else [] shown += ( - [('Parameters', self.parameters), ('Dimensions', self.dimensions), ('Relations', self.relations)] + [ + ('Parameters', self.parameters), + ('Dimensions', self.dimensions), + ('Relations', self.relations), + *([('Masks', self.masks)] if self.masks else []), + ] if allow_dims else [('Variables', self.variables), ('Parameters', self.parameters)] ) @@ -463,6 +510,51 @@ def _named(name: str, block: ExpressionBlock, ns: Namespace, errors: list[str]) return Named(name, Cases((*regions, left_over))) +def _mask(name: str, block: MaskBlock, ns: Namespace, errors: list[str]) -> NamedMask | None: + """One ``masks:`` entry as the node every use of it holds, or ``None`` once anything in it failed. + + A predicate the connectives decide is refused, since a name for every row + or for none narrows nothing a where could not say plainly; and a predicate + over a dimension the declared frame leaves out is refused, as an + expression's body is. + """ + context = f"Mask '{name}'" + body = resolve_where_text(block.where, ns, context, errors) + if body is None: + return None + if isinstance(body, BooleanLiteral): + errors.append( + f'{context}: the predicate {block.where!r} folds to {str(body.value).lower()}, so the mask ' + f'{"admits every row" if body.value else "admits no row"} and names nothing a where needs. ' + f'Delete it, or write the predicate over the data.' + ) + return None + carried = Mask(body).dims + if block.dims is not None and (outside := sorted(carried - set(block.dims))): + errors.append( + f'{context}: the predicate reads {outside}, which the dims {block.dims} do not name. A mask ' + f'is read over the frame it declares: add {outside} to its dims, or leave them out of the predicate.' + ) + return None + return NamedMask(name, body) + + +def self_existence(where: Predicate, variable: str, context: str) -> str | None: + """The refusal for a variable whose own where asks, through a mask, whether it exists — or ``None``. + + The where resolver refuses the bare name; a mask's predicate is resolved + under its own name, so the check runs again on the tree with every mask's + predicate in it. + """ + if not any(isinstance(atom, VariableDefined) and atom.name == variable for atom in Mask(where).atoms): + return None + return ( + f"{context}: variable '{variable}' asks whether it exists in its own where, through a mask, which " + f'nothing can answer — the mask is what decides where it exists. Test a parameter, or another ' + f'variable declared before it.' + ) + + def _constant_arm(context: str, *, value: bool) -> str: """The refusal for a case arm whose mask the connectives already decided. diff --git a/src/mathspec/spec.py b/src/mathspec/spec.py index 57fbd244..ac7d43c2 100644 --- a/src/mathspec/spec.py +++ b/src/mathspec/spec.py @@ -347,8 +347,22 @@ class GivenExpressionBlock(_StrictBlock): description: str | None = None +class GivenMaskBlock(_StrictBlock): + """A mask this file reads and another file defines. + + The frame is all this file states. A where reads the name as data over + that frame, true or false at each coordinate, since a mask reads nothing + a solve decides: the predicate is the definer's. + """ + + _label: ClassVar[str] = 'a given mask declaration' + + dims: list[str] + description: str | None = None + + class GivenBlock(_StrictBlock): - """What this file reads and does not build, by kind. Closed at the four kinds.""" + """What this file reads and does not build, by kind. Closed at the five kinds.""" _label: ClassVar[str] = 'a given block' @@ -360,10 +374,12 @@ class GivenBlock(_StrictBlock): constraints: dict[str, GivenConstraintBlock] = {} #: Named expressions another file defines ([`GivenExpressionBlock`][]). expressions: dict[str, GivenExpressionBlock] = {} + #: Masks another file defines ([`GivenMaskBlock`][]). + masks: dict[str, GivenMaskBlock] = {} def __bool__(self) -> bool: """Whether the file reads anything it does not build.""" - return bool(self.parameters or self.variables or self.constraints or self.expressions) + return bool(self.parameters or self.variables or self.constraints or self.expressions or self.masks) class ConstraintBlock(_StrictBlock): @@ -553,6 +569,55 @@ def _as_written(self) -> str | dict[str, object]: return written +class MaskBlock(_StrictBlock): + """A named predicate: one where string, read wherever a ``where:``, a ``when:`` or a ``holds:`` names it. + + Written in YAML as a bare where string, or as a mapping once it carries a + ``dims:`` or a ``description:``, and serialised back to whichever form it + was written in:: + + masks: + committable: "Generator_committable AND Generator_active" + stands: + dims: [period, generator] + where: build_year <= period_year AND period_year < build_year + lifetime + description: the generator stands in this period + + A bare mask name in a where string stands for its predicate, inside + ``count``, ``shift`` and ``at`` too. ``dims:`` declares the frame the mask + is read over; left out, the predicate decides it. A mask is a predicate, + so it is never a value in an expression. + """ + + _label: ClassVar[str] = 'a mask declaration' + + #: The predicate, in the where grammar. + where: str + dims: list[str] | None = None + description: str | None = None + + @model_validator(mode='before') + @classmethod + def _from_string(cls, data: object) -> object: + return {'where': data} if isinstance(data, str) else data + + @classmethod + @override + def __get_pydantic_json_schema__(cls, core_schema: CoreSchema, handler: GetJsonSchemaHandler) -> dict[str, object]: + """The published schema admits the bare string the one-line form is written as.""" + return _also_written_as(core_schema, handler, {'type': 'string'}) + + @model_serializer + def _as_written(self) -> str | dict[str, object]: + if self.dims is None and self.description is None: + return self.where + written: dict[str, object] = {'dims': list(self.dims)} if self.dims is not None else {} + written['where'] = self.where + if self.description is not None: + written['description'] = self.description + return written + + class AssumptionBlock(_StrictBlock): """What the spec assumes of its data: a predicate every coordinate it is checked at has to satisfy. @@ -811,7 +876,7 @@ class Spec(_StrictBlock): [`LanguageError`][] on a spec the language refuses. Holding one is the proof, so nothing downstream checks it again. - The API is the twelve declaration sections plus ``version`` and + The API is the thirteen declaration sections plus ``version`` and ``description``, three ways back out — [`to_dict`][] for the spec as data, [`to_yaml`][] for the file a reviewer reads, [`expand`][] for the spec with its formulations written out as plain rows — and [`program`][], the @@ -838,6 +903,7 @@ class is pydantic's, not a contract this package keeps. constraints: dict[str, ConstraintBlock] = {} objective: ObjectiveBlock | None = None expressions: dict[str, ExpressionBlock] = {} + masks: dict[str, MaskBlock] = {} macros: dict[str, MacroBlock] = {} piecewise: dict[str, PiecewiseBlock] = {} sos: dict[str, SosBlock] = {} @@ -981,7 +1047,7 @@ def _names_are_names(self) -> Spec: Read off the spec's own mappings rather than a list of sections, so a section added later cannot be forgotten here — every mapping a Spec - carries is keyed by a declaration name. ``given:`` nests its four + carries is keyed by a declaration name. ``given:`` nests its five mappings one level down, so they are read off [`GivenBlock`][] the same way. """ diff --git a/src/mathspec/typesetting/__init__.py b/src/mathspec/typesetting/__init__.py index 6a85ebd8..ffe441de 100644 --- a/src/mathspec/typesetting/__init__.py +++ b/src/mathspec/typesetting/__init__.py @@ -176,8 +176,8 @@ def typeset_declaration( """Render one declaration as the bare line the document prints for it. The line the whole-spec render prints for it — a named expression's - definition, a constraint, an assumption, a ``piecewise:`` curve, or a - variable's domain, quantifier included — + or a mask's definition, a constraint, an assumption, a ``piecewise:`` + curve, or a variable's domain, quantifier included — with no document, label, equation number or math delimiters around it, for a math context the caller lays out: a docstring, a table cell. A line on its own has no Definitions section beside it, so the plain named @@ -186,8 +186,8 @@ def typeset_declaration( Args: spec: Anything [`mathspec.to_spec`][] accepts, or a [`Program`][]. - name: A named expression, constraint, assumption, ``piecewise:`` - block or variable the spec declares. + name: A named expression, mask, constraint, assumption, + ``piecewise:`` block or variable the spec declares. fmt: What spells the math — a key of [`FORMATS`][]. symbols: How names print; see [`typeset`][]. inline_expressions: Substitute the plain named expressions the line uses, so it @@ -201,7 +201,7 @@ def typeset_declaration( Raises: ValueError: *fmt* names no format. LanguageError: A spec that does not compile; it does not print. - SchemaError: *name* is declared as none of the five, as two — a + SchemaError: *name* is declared as none of the six, as two — a constraint may share a variable's name — or under ``given:``, which prints in the legend rather than as a line; or a symbol table entry names nothing in the spec. @@ -213,6 +213,7 @@ def typeset_declaration( 'variable': given.variables, 'expression': given.expressions, 'constraint': given.constraints, + 'mask': given.masks, } given_kind = next((kind for kind, group in givens.items() if name in group), None) if given_kind is not None: diff --git a/src/mathspec/typesetting/format.py b/src/mathspec/typesetting/format.py index fe888114..00193b6e 100644 --- a/src/mathspec/typesetting/format.py +++ b/src/mathspec/typesetting/format.py @@ -32,6 +32,7 @@ 'plus', 'minus', 'equal', + 'iff', 'le', 'ge', 'lt', @@ -66,7 +67,8 @@ #: Every operator a walk can emit, by the name the walk uses for it, with its #: LaTeX spelling first and its Typst spelling second — one row per operator, -#: so no format can be missing one. ``such_that`` is the colon in +#: so no format can be missing one. ``iff`` is the ⟺ a mask is defined +#: with, since a predicate is not a value to equate. ``such_that`` is the colon in #: "∀ t ∈ T : condition", ``times`` sits between sets in the legend, #: ``maps_to`` is the → in a coordinate map, ``curve`` and ``hull`` are the two #: sets a ``piecewise:`` block states its links lie on, and the three @@ -78,6 +80,7 @@ 'plus': ('+', '+'), 'minus': ('-', '-'), 'equal': ('=', '='), + 'iff': (r'\iff', 'arrow.l.r.double.long'), 'le': (r'\le', '<='), 'ge': (r'\ge', '>='), 'lt': ('<', '<'), diff --git a/src/mathspec/typesetting/legend.py b/src/mathspec/typesetting/legend.py index 62c014fb..9e86cd01 100644 --- a/src/mathspec/typesetting/legend.py +++ b/src/mathspec/typesetting/legend.py @@ -111,6 +111,8 @@ def masks(mask: Mask) -> None: expressions(entry.expression) for curve in program.piecewise.values(): expressions(*(link.expression for link in curve.links)) + for mask in program.masks.values(): + masks(mask.where) for declaration in (*program.constraints.values(), *program.variables.values()): if declaration.where is not None: masks(declaration.where) @@ -133,7 +135,7 @@ def _op(self, name: OperatorName) -> str: return self.format.operators[name] def glossaries(self, noticed: Noticed, defined: Iterable[str]) -> list[tuple[str, list[Entry]]]: - """The sets, parameters, variables, given declarations and definitions, each with its symbol, its dims and its description. + """The sets, parameters, variables, given declarations, definitions and masks, each with its symbol, its dims and its description. *defined* names the expressions that print under their own symbol, so a legend row stands exactly where a symbol does. @@ -189,6 +191,14 @@ def glossaries(self, noticed: Noticed, defined: Iterable[str]) -> list[tuple[str ) for g, block in program.given.constraints.items() ), + *( + self._entry( + self.symbols.name[g], + f'{fmt.mono(g)}{self._over(list(block.dims))}, a mask another file defines', + block.description, + ) + for g, block in program.given.masks.items() + ), ] shown = set(defined) definitions = [ @@ -196,12 +206,17 @@ def glossaries(self, noticed: Noticed, defined: Iterable[str]) -> list[tuple[str for e, block in program.expressions.items() if e in shown ] + masks = [ + self._entry(self.symbols.name[m], f'{fmt.mono(m)}{self._over(list(block.dims))}', block.description) + for m, block in program.masks.items() + ] groups = ( ('Sets', sets), ('Parameters', parameters), ('Variables', variables), ('Given', given), ('Definitions', definitions), + ('Masks', masks), ) return [(title, entries) for title, entries in groups if entries] diff --git a/src/mathspec/typesetting/symbols.py b/src/mathspec/typesetting/symbols.py index da15d756..30346672 100644 --- a/src/mathspec/typesetting/symbols.py +++ b/src/mathspec/typesetting/symbols.py @@ -104,7 +104,8 @@ class Symbols: Attributes: overridden: Names the table spelled; the convention note quotes only derived symbols. - name: Each parameter's, variable's and expression's symbol. + name: Each parameter's, variable's, expression's and mask's symbol. + A mask is upright, being read off the data as a parameter is. constraint: Each constraint's symbol, the subscript ``dual(c)`` prints λ against. Off the flat namespace, like the constraints themselves — a spec may name a constraint after a variable, so this is its @@ -146,6 +147,8 @@ def symbols_for(program: Program, fmt: Format, table: SymbolTable) -> Symbols: *program.given.variables, *program.expressions, *program.given.expressions, + *program.masks, + *program.given.masks, ) declared = frozenset(names) @@ -291,6 +294,8 @@ def _declared(program: Program) -> set[str]: | set(program.given.variables) | set(program.expressions) | set(program.given.expressions) + | set(program.masks) + | set(program.given.masks) | set(program.constraints) | set(program.given.constraints) ) diff --git a/src/mathspec/typesetting/walk.py b/src/mathspec/typesetting/walk.py index 86c3fca8..2a161022 100644 --- a/src/mathspec/typesetting/walk.py +++ b/src/mathspec/typesetting/walk.py @@ -34,6 +34,7 @@ Mask, Multiply, Named, + NamedMask, Negate, Not, Or, @@ -541,9 +542,12 @@ def _where(self, node: Predicate, ctx: _Context) -> tuple[str, int]: assert not node.value, 'an always-true mask is folded away or refused before anything prints it' return self._op('false'), _ATOM + if isinstance(node, NamedMask): + return ctx.indexed(self.symbols.name[node.name], list(self.program.masks[node.name].dims)), _ATOM + if isinstance(node, ParameterDefined): indexed = ctx.indexed(self.symbols.name[node.name], list(node.dims)) - if self._parameters[node.name].dtype == 'bool': + if node.name in self.program.given.masks or self._parameters[node.name].dtype == 'bool': return indexed, _ATOM return f'{indexed} {self.format.prose(" is defined")}', comparison @@ -666,6 +670,7 @@ def equations(self) -> list[tuple[str, list[Line]]]: ('Objective', self._objective()), ('Subject to', self._constraints()), ('Definitions', self._definitions()), + ('Masks', [self.mask(name) for name in self.program.masks]), ('Variable domains', self._variables()), ('Assumptions', self._assumptions()), ] @@ -751,21 +756,38 @@ def definition(self, name: str) -> Line: condition=self._quantifier(frame, ''), ) + def mask(self, name: str) -> Line: + """The line defining one mask, ``symbol ⟺ predicate`` over its frame. + + A mask is a predicate rather than a value, so it is defined with ⟺ + where an expression is defined with =; every use prints the symbol. + """ + entry = self.program.masks[name] + frame = list(entry.dims) + ctx = self._context(frame) + return Line( + label=name, + left=ctx.indexed(self.symbols.name[name], frame), + right=f'{self._op("iff")} {self._predicate(entry.where.root, ctx)}', + condition=self._quantifier(frame, ''), + ) + def line(self, name: str) -> Line: - """The one line *name* prints as: a named expression, a constraint, an assumption, a curve, or a variable's domain. + """The one line *name* prints as: a named expression, a mask, a constraint, an assumption, a curve, or a variable's domain. An assumption is looked up where the document prints it from, so a condition a curve's method states is a line a reader can ask for before the curve is written out. Raises: - SchemaError: *name* is declared as none of the five, or as two — a + SchemaError: *name* is declared as none of the six, or as two — a constraint may share a variable's name, and one line prints one of them. """ program = self.program kinds = { 'named expression': (program.expressions, self.definition), + 'mask': (program.masks, self.mask), 'constraint': (program.constraints, self._constraint), 'assumption': (program.assumptions, self._assumption), 'curve': (program.piecewise, self._piecewise), @@ -775,7 +797,7 @@ def line(self, name: str) -> Line: if not found: everything = {n for group, _ in kinds.values() for n in group} msg = ( - f"'{name}' is not a named expression, constraint, assumption, curve or variable. " + f"'{name}' is not a named expression, mask, constraint, assumption, curve or variable. " f'{did_you_mean(name, everything)}' ) raise SchemaError(msg) diff --git a/src/mathspec/validation.py b/src/mathspec/validation.py index 3b7424ae..6008a591 100644 --- a/src/mathspec/validation.py +++ b/src/mathspec/validation.py @@ -108,6 +108,8 @@ def _flat_namespace(schema: Spec) -> list[tuple[str, Iterable[str]]]: ('given variable', schema.given.variables), ('named expression', schema.expressions), ('given expression', schema.given.expressions), + ('mask', schema.masks), + ('given mask', schema.given.masks), ('macro', schema.macros), ] @@ -155,8 +157,10 @@ def _frame_dimensions(schema: Spec) -> Iterator[str]: *(('Given variable', name, g.dims) for name, g in schema.given.variables.items()), *(('Given expression', name, g.dims) for name, g in schema.given.expressions.items()), *(('Given constraint', name, g.dims) for name, g in schema.given.constraints.items()), + *(('Given mask', name, g.dims) for name, g in schema.given.masks.items()), *(('Constraint', name, c.dims) for name, c in schema.constraints.items()), *(('Named expression', name, e.dims or []) for name, e in schema.expressions.items()), + *(('Mask', name, m.dims or []) for name, m in schema.masks.items()), ] for kind, name, dims in frames: yield from (undeclared_dimension(kind, name, d) for d in dims if d not in schema.dimensions) diff --git a/tests/test_given.py b/tests/test_given.py index a627b292..1fe6ab4c 100644 --- a/tests/test_given.py +++ b/tests/test_given.py @@ -48,11 +48,11 @@ } -def test_given_holds_four_kinds_and_refuses_a_fifth(): +def test_given_holds_five_kinds_and_refuses_a_sixth(): """The section is closed, so a kind nobody has admitted yet is the schema's own refusal.""" with pytest.raises(LanguageError) as raised: to_spec({**SUPPLY, 'given': {'macros': {'twice': {'params': ['x'], 'template': '2 * x'}}}}) - assert 'Valid keys: constraints, expressions, parameters, variables' in str(raised.value), ( + assert 'Valid keys: constraints, expressions, masks, parameters, variables' in str(raised.value), ( 'the refusal names what the block takes' ) diff --git a/tests/test_masks.py b/tests/test_masks.py new file mode 100644 index 00000000..4181718c --- /dev/null +++ b/tests/test_masks.py @@ -0,0 +1,291 @@ +# SPDX-FileCopyrightText: mathspec Contributors +# +# SPDX-License-Identifier: MIT + +"""A mask is a named predicate: written once under ``masks:``, read by name wherever a where string is (#803). + +A use stands as a `NamedMask` with the predicate under it, so every question +asked of a mask is asked of the predicate, and the typesetter still prints the +name. Another file reads a mask under ``given: masks:`` as the boolean data it +is to that file, and `merge` folds the reading into the definition. +""" + +from __future__ import annotations + +import copy +from typing import Any + +import pytest + +from mathspec import FORMATS, LanguageError, advice, merge, override, to_spec, typeset, typeset_declaration +from mathspec.program import Mask, NamedMask, ParameterDefined, VariableDefined +from tests.fixtures import SMALL_MODEL, varied + +#: A unit stands in a period between its build year and its retirement, and +#: two families of rows read that test: the core's capacity and a ramp. +DIMS: dict[str, Any] = {'period': {'dtype': 'int'}, 'generator': {'dtype': 'str'}} +FRAME = ['period', 'generator'] +STANDS = 'build_year <= period_year AND period_year < build_year + lifetime' + +CORE: dict[str, Any] = { + 'dimensions': DIMS, + 'parameters': { + 'build_year': {'dims': ['generator']}, + 'lifetime': {'dims': ['generator']}, + 'period_year': {'dims': ['period']}, + 'p_nom': {'dims': ['generator']}, + }, + 'masks': {'stands': {'dims': FRAME, 'where': STANDS, 'description': 'the unit stands in this period'}}, + 'variables': {'p': {'dims': FRAME, 'where': 'stands', 'bounds': {'lower': 0}}}, + 'constraints': {'cap': {'dims': FRAME, 'where': 'stands', 'expression': 'p <= p_nom'}}, + 'objective': {'sense': 'minimize', 'expression': 'sum(p, over=[period, generator])'}, +} + +RAMPING: dict[str, Any] = { + 'dimensions': DIMS, + 'given': {'variables': {'p': {'dims': FRAME}}, 'masks': {'stands': {'dims': FRAME}}}, + 'parameters': {'ramp': {'dims': ['generator']}}, + 'constraints': { + 'ramp_up': { + 'dims': FRAME, + 'where': 'stands AND shift(stands, along=period, offset=1)', + 'expression': 'p - shift(p, along=period, offset=1) <= ramp', + } + }, +} + + +def test_a_mask_stands_where_its_name_is_written_with_its_predicate_under_it(): + program = to_spec(CORE).program + where = program.constraints['cap'].where + assert where is not None and isinstance(where.root, NamedMask), 'the use keeps the name the file wrote' + assert where.root.body == program.masks['stands'].where.root, 'and carries the predicate the entry declares' + assert program.masks['stands'].dims == ('period', 'generator') + assert where.names_read == {'build_year', 'lifetime', 'period_year'}, 'a mask reads what its predicate reads' + assert where.dims == {'period', 'generator'} + + +@pytest.mark.parametrize( + 'patch', + [ + pytest.param({'constraints.cap.where': 'NOT stands'}, id='under-a-connective'), + pytest.param({'constraints.cap.where': 'stands AND shift(stands, along=period, offset=1)'}, id='in-shift'), + pytest.param( + {'assumptions.lives': {'holds': 'lifetime > 0', 'where': 'stands'}}, id='as-an-assumption-s-where' + ), + pytest.param({'assumptions.ever': 'count(stands, over=period) >= 1'}, id='in-count'), + pytest.param( + { + 'expressions.share': { + 'dims': FRAME, + 'cases': {'standing': {'when': 'stands', 'expression': 'p_nom'}}, + 'otherwise': 0, + } + }, + id='as-a-when', + ), + pytest.param({'masks.old': 'stands AND lifetime > 30', 'constraints.cap.where': 'old'}, id='in-another-mask'), + pytest.param( + {'expressions.half': 'p_nom / 2', 'masks.big': 'half > 10', 'constraints.cap.where': 'big'}, + id='reading-a-data-only-expression', + ), + pytest.param({'masks.stands': STANDS}, id='the-one-line-form'), + ], +) +def test_a_mask_is_read_wherever_a_where_string_is(patch): + spec = to_spec(varied(CORE, **patch)) + assert to_spec(spec.to_yaml()).program == spec.program, 'the file reads back to the same program' + + +def test_the_one_line_form_writes_back_as_written(): + spec = to_spec(varied(CORE, **{'masks.stands': STANDS})) + assert spec.to_dict()['masks'] == {'stands': STANDS}, 'a mask with no frame and no words is one where string' + + +def test_a_variable_that_asks_through_a_mask_whether_it_exists_is_refused(): + """The where resolver refused the bare name only: a mask's predicate is read under the mask's own name.""" + with pytest.raises(LanguageError, match=r"variable 'p' asks whether it exists in its own where, through a mask"): + to_spec(varied(CORE, **{'masks.live': 'p', 'variables.p.where': 'live'})) + + +def test_a_mask_may_ask_whether_another_variable_exists(): + spec = to_spec(varied(CORE, **{'masks.dispatched': 'p', 'constraints.cap.where': 'dispatched'})) + where = spec.program.constraints['cap'].where + assert where is not None and VariableDefined('p', ('period', 'generator')) in where.atoms + + +@pytest.mark.parametrize( + ('patch', 'says'), + [ + pytest.param({'masks': {'a': 'b', 'b': 'a'}}, 'circular mask reference: a -> b -> a', id='a-cycle'), + pytest.param({'masks': {'a': 'a'}}, 'circular mask reference: a -> a', id='a-mask-reading-itself'), + pytest.param({'masks': {'a': 'True'}}, 'folds to true, so the mask admits every row', id='always-true'), + pytest.param({'masks': {'a': 'NOT True'}}, 'folds to false, so the mask admits no row', id='always-false'), + pytest.param( + {'masks': {'a': {'dims': ['h'], 'where': 'c > 0'}}}, + "the predicate reads ['g'], which the dims ['h'] do not name", + id='a-predicate-wider-than-its-frame', + ), + pytest.param( + {'masks': {'a': 'c > 0'}, 'expressions.e': 'c * a'}, + "'a' is a mask, which is true or false where it is read, and not a number", + id='a-mask-in-arithmetic', + ), + pytest.param( + {'masks': {'a': 'c > 0'}, 'variables.p.where': 'a == 1'}, + "'a' is a mask, which is true or false where it is read, and not a number", + id='a-mask-compared', + ), + pytest.param( + {'given.masks.m': {'dims': ['g']}, 'expressions.e': 'c * m'}, + "'m' is a mask, which is true or false where it is read, and not a number", + id='a-given-mask-in-arithmetic', + ), + pytest.param( + {'given.expressions.ge': {'dims': ['g']}, 'masks': {'a': 'ge > 0'}}, + "where references the given expression 'ge'", + id='a-mask-reading-a-given-expression', + ), + pytest.param({'masks': {'c': 'flag'}}, "Mask 'c' collides with the parameter", id='a-name-taken'), + pytest.param({'masks': {'a': 'nope'}}, "Mask 'a': 'nope' not found", id='an-unknown-name'), + pytest.param( + {'masks': {'a': 'c > 0'}, 'variables.r.where': 'a'}, + "outside the frame ['h']", + id='a-use-over-a-frame-the-predicate-leaves', + ), + ], +) +def test_what_a_mask_may_not_be_is_refused_at_load(patch, says): + with pytest.raises(LanguageError, match=None) as raised: + to_spec(varied(SMALL_MODEL, **patch)) + assert says in str(raised.value), 'the refusal names the mask and what to write instead' + + +def test_cases_split_by_a_mask_are_proved_apart_through_its_predicate(): + """The overlap check evaluates a when on a grid of the data it reads, and a mask's data is its predicate's.""" + case = { + 'dims': ['g'], + 'cases': {'on': {'when': 'a', 'expression': 1}, 'off': {'when': 'flag', 'expression': 2}}, + 'otherwise': 0, + } + with pytest.raises(LanguageError, match=r"cases 'on' and 'off' both claim the value where flag is true"): + to_spec(varied(SMALL_MODEL, **{'masks.a': 'flag', 'expressions.e': case})) + apart = varied(case, **{'cases.off.when': 'NOT a'}) + assert to_spec(varied(SMALL_MODEL, **{'masks.a': 'flag', 'expressions.e': apart})) + + +# --------------------------------------------------------------------------- +# another file's mask +# --------------------------------------------------------------------------- + + +def test_a_fragment_reads_another_file_s_mask_as_boolean_data(): + """#803's ramp read `active == 1` under `given: expressions:`, which a where may not read.""" + program = to_spec(RAMPING).program + where = program.constraints['ramp_up'].where + assert where is not None and ParameterDefined('stands', ('period', 'generator')) in where.atoms, ( + 'alone, the reading is data the host provides' + ) + assert sorted(program.given.masks) == ['stands'] + + +def test_check_notes_the_mask_a_fragment_reads(): + """Every other given kind had a note saying who provides it, and a given mask had none.""" + notes = {(note.kind, note.subject): note.text for note in advice(RAMPING)} + assert "mask 'stands' is read here and declared elsewhere" in notes[('given', 'stands')] + + +@pytest.mark.parametrize( + 'fragments', + [ + pytest.param([CORE, RAMPING], id='the-definer-first'), + pytest.param([RAMPING, CORE], id='the-reader-first'), + ], +) +def test_merge_folds_the_reading_into_the_mask_another_fragment_defines(fragments): + program = merge(fragments).program + assert not program.given, 'the reading is spent once the definer is in the composition' + where = program.constraints['ramp_up'].where + assert where is not None and isinstance(where.conjuncts[0], NamedMask), 'the composed row reads the mask by name' + + +@pytest.mark.parametrize( + ('reader', 'says'), + [ + pytest.param( + varied(RAMPING, **{'given.masks.stands.dims': ['generator'], 'constraints.ramp_up.where': 'stands'}), + "reads the given mask 'stands'", + id='a-reader-that-states-less-than-the-frame', + ), + pytest.param( + varied( + RAMPING, + **{'given.masks': {}, 'given.parameters.stands': {'dims': FRAME, 'dtype': 'bool'}}, + ), + "reads 'stands' as a given parameter, where '#1' introduces it under 'masks:'", + id='a-mask-read-as-a-parameter', + ), + ], +) +def test_a_reading_the_definer_does_not_answer_is_refused(reader, says): + with pytest.raises(LanguageError) as raised: + merge([CORE, reader]) + assert says in str(raised.value) + + +def test_a_reader_may_state_a_frame_wider_than_the_mask_s(): + """A mask over fewer dimensions holds alike along the rest, as a given expression's narrower body does. + + The one-line definer has no ``dims:`` in its block, so the frame is read + off the definer's program rather than off the block it wrote. + """ + definer = varied(CORE, **{'masks.stands': 'lifetime > 0'}) + program = merge([definer, varied(RAMPING, **{'constraints.ramp_up.where': 'stands'})]).program + assert program.masks['stands'].dims == ('generator',), 'the definer decides the frame' + assert not program.given + + +def test_two_fragments_that_define_one_mask_collide(): + other = {'dimensions': DIMS, 'parameters': {'retired': {'dims': ['generator'], 'dtype': 'bool'}}} + with pytest.raises(LanguageError, match=r"fragments '#1' and '#2' both declare the mask 'stands'"): + merge([CORE, {**other, 'masks': {'stands': 'NOT retired'}}]) + + +def test_a_patch_rewrites_a_mask_s_predicate(): + laid = override(CORE, [{'masks': {'stands': {'where': 'lifetime > 0'}}}]) + assert laid.masks['stands'].where == 'lifetime > 0' + assert laid.masks['stands'].dims == FRAME, 'a patch says only what it changes' + + +# --------------------------------------------------------------------------- +# typesetting +# --------------------------------------------------------------------------- + + +@pytest.mark.parametrize('fmt', sorted(FORMATS)) +def test_a_mask_prints_its_symbol_where_it_is_read_and_its_predicate_once(fmt): + printed = typeset(CORE, fmt) + defined = typeset_declaration(CORE, 'stands', fmt) + assert printed.count(FORMATS[fmt].operators['iff']) == 1, 'the predicate prints once, as the definition' + assert defined.split()[0].replace('\\_', '_').count('stands') == 1, 'the definition opens with the symbol' + assert FORMATS[fmt].operators['iff'] in defined, 'a predicate is defined with iff, not equated' + assert 'the unit stands in this period' in printed, 'the legend carries its words' + + +@pytest.mark.parametrize('fmt', sorted(FORMATS)) +def test_a_given_mask_prints_in_the_legend_and_no_line_of_its_own(fmt): + assert 'a mask another file defines' in typeset(RAMPING, fmt) + with pytest.raises(LanguageError, match=r"'stands' is a given mask, and a given declaration prints no line"): + typeset_declaration(RAMPING, 'stands', fmt) + + +def test_the_fixtures_are_never_mutated(): + before = copy.deepcopy((CORE, RAMPING)) + merge([CORE, RAMPING]) + assert before == (CORE, RAMPING) + + +def test_a_mask_with_no_declared_frame_takes_the_dims_its_predicate_reads(): + spec = to_spec(varied(SMALL_MODEL, **{'masks.a': 'c > 0 AND flag'})) + assert spec.program.masks['a'].dims == ('g',) + assert spec.program.masks['a'].where == Mask(spec.program.masks['a'].where.root), 'the predicate is folded' diff --git a/tests/typesetting/golden/latex.out b/tests/typesetting/golden/latex.out index b6b5e665..6843ae70 100644 --- a/tests/typesetting/golden/latex.out +++ b/tests/typesetting/golden/latex.out @@ -67,6 +67,12 @@ \item[{$\mathrm{startup\_cost}$}] \texttt{startup\_cost} over $\mathcal{T} \times \mathcal{G}$ --- what starting a unit in this snapshot costs, which the horizon's edge changes \end{description} +\paragraph{Masks} +\begin{description} +\item[{$\mathrm{dispatchable}$}] \texttt{dispatchable} over $\mathcal{G}$ --- a unit that dispatches and cannot be turned down +\item[{$\mathrm{priced\_dispatch}$}] \texttt{priced\_dispatch} over $\mathcal{G}$ +\end{description} + \noindent Upright is what the data supplies --- a parameter such as $\mathrm{p}^{\mathrm{max}}$, a coordinate map, a label --- and italic is what the solver chooses, such as $p$. An index is italic too, being what a quantifier chooses, and a set is script. \noindent $t \ominus k$ denotes cyclic translation: index $t-k$ taken modulo the size of the dimension (\texttt{roll}). Plain $t-k$ (\texttt{shift}) has no wraparound --- terms translated past the edge are simply absent. @@ -136,6 +142,7 @@ \text{run\_start} && \mathit{slack}_{t} & \le \mathrm{load}_{t,b} && \forall\, t \in \mathcal{T},\ b \in \mathcal{B} \,:\, \mathrm{load}_{t,b} \text{ is defined} \wedge \neg \left( \mathrm{load}_{t - 1,b} \text{ is defined} \right) \\ \text{zoned} && \theta_{b} & \le \mathrm{budget} && \forall\, b \in \mathcal{B} \,:\, \mathrm{zone\_cap}_{\mathrm{zone\_of}(b)} \text{ is defined} \\ \text{capped} && p_{t,g} & \le \mathrm{p}^{\mathrm{max}}_{g} && \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{spend}^{\mathrm{cap}}_{g} > 0 \vee \neg \mathrm{is\_flexible}_{g} \\ +\text{masked} && p_{t,g} & \le \mathrm{p}^{\mathrm{max}}_{g} && \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{priced\_dispatch}_{g} \wedge \mathrm{pos}(t) > 0 \\ \text{fuel\_curve} && \left( p_{t,g},\ \mathit{fuel}_{t,g},\ \mathit{heat}_{t,g} \right) & \in \mathit{on}_{t,g} \cdot \mathrm{pwl}_{a \in \mathcal{A}}(\mathrm{bp\_x}_{g,a},\ \mathrm{bp\_y}_{g,a},\ \mathrm{bp\_heat}_{g,a}) && \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \\ \text{cost\_curve} && \mathit{op\_cost}_{t,g} & \ge \begin{cases} \mathit{warm}_{t,g} & \text{if } \mathrm{is\_flexible}_{g} \\ 1 & \text{otherwise} \end{cases} \cdot \mathrm{pwl}_{a \in \mathcal{A} \,:\, \mathrm{bp\_run}_{g,a}}(\mathrm{bp\_x}_{g,a},\ \mathrm{bp\_y}_{g,a})(p_{t,g}) && \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \\ \text{hull\_curve} && \left( p_{t,g},\ \mathit{fuel}_{t,g} \right) & \in \mathrm{conv}_{a \in \mathcal{A}}(\mathrm{bp\_x}_{g,a},\ \mathrm{bp\_heat}_{g,a}) && \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \\ @@ -154,6 +161,12 @@ \text{startup\_cost} && \mathrm{startup\_cost}_{t,g} & = \begin{cases} \mathrm{cost}_{g} & \text{if } \mathrm{pos}(t) = 0 \\ \mathrm{cost}_{g} \cdot 2 & \text{if } \mathrm{pos}(t) > 0 \wedge \mathrm{season\_of}(t) = \text{'}\mathrm{winter}\text{'} \\ 0 & \text{otherwise} \end{cases} && \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \end{align} +\paragraph{Masks} +\begin{align} +\text{dispatchable} && \mathrm{dispatchable}_{g} & \iff \mathrm{p}^{\mathrm{max}}_{g} > 0 \wedge \neg \mathrm{is\_flexible}_{g} && \forall\, g \in \mathcal{G} \\ +\text{priced\_dispatch} && \mathrm{priced\_dispatch}_{g} & \iff \mathrm{dispatchable}_{g} \wedge \mathrm{spend}^{\mathrm{cap}}_{g} > 0 && \forall\, g \in \mathcal{G} +\end{align} + \paragraph{Variable domains} \begin{align} \text{p} && \mathrm{p}^{\mathrm{min}}_{g} \le p_{t,g} & \le \mathrm{p}^{\mathrm{max}}_{g} && \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{p}^{\mathrm{max}}_{g} > 0 \wedge \neg \mathrm{is\_flexible}_{g} \vee \mathrm{p}^{\mathrm{min}}_{g} > 0 \\ diff --git a/tests/typesetting/golden/markdown.out b/tests/typesetting/golden/markdown.out index 09d67bd6..adbd356b 100644 --- a/tests/typesetting/golden/markdown.out +++ b/tests/typesetting/golden/markdown.out @@ -66,6 +66,13 @@ every character a notation escapes, set as text: link\_to, 100% & \#1 costs \$5 | $`\mathit{marginal\_price}`$ | `marginal_price` over $`\mathcal{T} \times \mathcal{B}`$ | | $`\mathrm{startup\_cost}`$ | `startup_cost` over $`\mathcal{T} \times \mathcal{G}`$ — what starting a unit in this snapshot costs, which the horizon's edge changes | +#### Masks + +| Symbol | Meaning | +|---|---| +| $`\mathrm{dispatchable}`$ | `dispatchable` over $`\mathcal{G}`$ — a unit that dispatches and cannot be turned down | +| $`\mathrm{priced\_dispatch}`$ | `priced_dispatch` over $`\mathcal{G}`$ | + Upright is what the data supplies — a parameter such as $`\mathrm{p}^{\mathrm{max}}`$, a coordinate map, a label — and italic is what the solver chooses, such as $`p`$. An index is italic too, being what a quantifier chooses, and a set is script. $`t \ominus k`$ denotes cyclic translation: index $`t-k`$ taken modulo the size of the dimension (`roll`). Plain $`t-k`$ (`shift`) has no wraparound — terms translated past the edge are simply absent. @@ -376,6 +383,12 @@ p_{t,g} \le \mathrm{p}^{\mathrm{max}}_{g} \qquad \forall\, t \in \mathcal{T},\ g p_{t,g} \le \mathrm{p}^{\mathrm{max}}_{g} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{spend}^{\mathrm{cap}}_{g} > 0 \vee \neg \mathrm{is\_flexible}_{g} ``` +**`masked`** + +```math +p_{t,g} \le \mathrm{p}^{\mathrm{max}}_{g} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{priced\_dispatch}_{g} \wedge \mathrm{pos}(t) > 0 +``` + **`fuel_curve`** ```math @@ -450,6 +463,20 @@ p_{t,g} \le \mathrm{p}^{\mathrm{max}}_{g} \qquad \forall\, t \in \mathcal{T},\ g \mathrm{startup\_cost}_{t,g} = \begin{cases} \mathrm{cost}_{g} & \text{if } \mathrm{pos}(t) = 0 \\ \mathrm{cost}_{g} \cdot 2 & \text{if } \mathrm{pos}(t) > 0 \wedge \mathrm{season\_of}(t) = \text{'}\mathrm{winter}\text{'} \\ 0 & \text{otherwise} \end{cases} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} ``` +#### Masks + +**`dispatchable`** + +```math +\mathrm{dispatchable}_{g} \iff \mathrm{p}^{\mathrm{max}}_{g} > 0 \wedge \neg \mathrm{is\_flexible}_{g} \qquad \forall\, g \in \mathcal{G} +``` + +**`priced_dispatch`** + +```math +\mathrm{priced\_dispatch}_{g} \iff \mathrm{dispatchable}_{g} \wedge \mathrm{spend}^{\mathrm{cap}}_{g} > 0 \qquad \forall\, g \in \mathcal{G} +``` + #### Variable domains **`p`** diff --git a/tests/typesetting/golden/model.yaml b/tests/typesetting/golden/model.yaml index fcdce15e..74f8306b 100644 --- a/tests/typesetting/golden/model.yaml +++ b/tests/typesetting/golden/model.yaml @@ -159,6 +159,13 @@ expressions: winter: { when: "position(snapshot) > 0 and season_of == 'winter'", expression: cost * 2 } otherwise: 0 +masks: + dispatchable: # a named predicate with its frame and its words: a use prints the symbol, the predicate prints once + dims: [generator] + where: "p_max > 0 AND NOT is_flexible" + description: a unit that dispatches and cannot be turned down + priced_dispatch: "dispatchable AND spend_cap > 0" # the one-line form, reading another mask and a data-only entry + constraints: budgeted: # names the plain expression: its symbol prints here, its definition once below dims: [snapshot] @@ -327,6 +334,10 @@ constraints: dims: [snapshot, generator] where: "spend_cap > 0 OR NOT is_flexible" expression: p <= p_max + masked: # a mask as the where, beside a condition of the constraint's own + dims: [snapshot, generator] + where: "priced_dispatch AND position(snapshot) > 0" + expression: p <= p_max assumptions: # what the data has to satisfy for the answer to mean anything bounds_do_not_cross: "p_min <= p_max" # two parameters, which is arithmetic like any other diff --git a/tests/typesetting/golden/typst.out b/tests/typesetting/golden/typst.out index 8ffd07dd..df565055 100644 --- a/tests/typesetting/golden/typst.out +++ b/tests/typesetting/golden/typst.out @@ -55,6 +55,10 @@ every character a notation escapes, set as text: link\_to, 100% & \#1 costs \$5 / $italic("marginal_price")$: `marginal_price` over $cal(T) times cal(B)$ / $upright("startup_cost")$: `startup_cost` over $cal(T) times cal(G)$ --- what starting a unit in this snapshot costs, which the horizon's edge changes +== Masks +/ $upright("dispatchable")$: `dispatchable` over $cal(G)$ --- a unit that dispatches and cannot be turned down +/ $upright("priced_dispatch")$: `priced_dispatch` over $cal(G)$ + Upright is what the data supplies --- a parameter such as $upright("p")^(upright("max"))$, a coordinate map, a label --- and italic is what the solver chooses, such as $p$. An index is italic too, being what a quantifier chooses, and a set is script. $t minus.o k$ denotes cyclic translation: index $t-k$ taken modulo the size of the dimension (`roll`). Plain $t-k$ (`shift`) has no wraparound --- terms translated past the edge are simply absent. @@ -123,6 +127,7 @@ $ upright("budgeted") & italic("spend")_(t) & <= upright("budget") & forall t in upright("run_start") & italic("slack")_(t) & <= upright("load")_(t,b) & forall t in cal(T), b in cal(B) colon upright("load")_(t,b) upright(" is defined") and not (upright("load")_(t - 1,b) upright(" is defined")) \ upright("zoned") & theta_(b) & <= upright("budget") & forall b in cal(B) colon upright("zone_cap")_(upright("zone_of")(b)) upright(" is defined") \ upright("capped") & p_(t,g) & <= upright("p")^(upright("max"))_(g) & forall t in cal(T), g in cal(G) colon upright("spend")^(upright("cap"))_(g) > 0 or not upright("is_flexible")_(g) \ + upright("masked") & p_(t,g) & <= upright("p")^(upright("max"))_(g) & forall t in cal(T), g in cal(G) colon upright("priced_dispatch")_(g) and upright("pos")(t) > 0 \ upright("fuel_curve") & (p_(t,g), italic("fuel")_(t,g), italic("heat")_(t,g)) & in italic("on")_(t,g) dot upright("pwl")_(a in cal(A))(upright("bp_x")_(g,a), upright("bp_y")_(g,a), upright("bp_heat")_(g,a)) & forall t in cal(T), g in cal(G) \ upright("cost_curve") & italic("op_cost")_(t,g) & >= cases(italic("warm")_(t,g) & upright("if ") upright("is_flexible")_(g), 1 & upright("otherwise")) dot upright("pwl")_(a in cal(A) colon upright("bp_run")_(g,a))(upright("bp_x")_(g,a), upright("bp_y")_(g,a))(p_(t,g)) & forall t in cal(T), g in cal(G) \ upright("hull_curve") & (p_(t,g), italic("fuel")_(t,g)) & in upright("conv")_(a in cal(A))(upright("bp_x")_(g,a), upright("bp_heat")_(g,a)) & forall t in cal(T), g in cal(G) \ @@ -139,6 +144,11 @@ $ upright("spend_cap") & upright("spend")^(upright("cap"))_(g) & = upright("cost upright("marginal_price") & italic("marginal_price")_(t,b) & = lambda_(upright("balance"),t,b) & forall t in cal(T), b in cal(B) \ upright("startup_cost") & upright("startup_cost")_(t,g) & = cases(upright("cost")_(g) & upright("if ") upright("pos")(t) = 0, upright("cost")_(g) dot 2 & upright("if ") upright("pos")(t) > 0 and upright("season_of")(t) = upright("'winter'"), 0 & upright("otherwise")) & forall t in cal(T), g in cal(G) $ +== Masks +#set math.equation(numbering: "(1)") +$ upright("dispatchable") & upright("dispatchable")_(g) & arrow.l.r.double.long upright("p")^(upright("max"))_(g) > 0 and not upright("is_flexible")_(g) & forall g in cal(G) \ + upright("priced_dispatch") & upright("priced_dispatch")_(g) & arrow.l.r.double.long upright("dispatchable")_(g) and upright("spend")^(upright("cap"))_(g) > 0 & forall g in cal(G) $ + == Variable domains #set math.equation(numbering: "(1)") $ upright("p") & upright("p")^(upright("min"))_(g) <= p_(t,g) & <= upright("p")^(upright("max"))_(g) & forall t in cal(T), g in cal(G) colon upright("p")^(upright("max"))_(g) > 0 and not upright("is_flexible")_(g) or upright("p")^(upright("min"))_(g) > 0 \ diff --git a/tests/typesetting/test_declaration.py b/tests/typesetting/test_declaration.py index 005eecce..3213c376 100644 --- a/tests/typesetting/test_declaration.py +++ b/tests/typesetting/test_declaration.py @@ -161,7 +161,7 @@ def test_a_substituted_term_that_opens_with_a_minus_prints_as_a_subtraction(): pytest.param( PLAIN, 'spent', - r"'spent' is not a named expression, constraint, assumption, curve or variable.*spend", + r"'spent' is not a named expression, mask, constraint, assumption, curve or variable.*spend", id='a-near-miss', ), pytest.param(PLAIN, 'objective', r"'objective' is not a named expression", id='the-objective-has-no-name'), diff --git a/tools/notation.py b/tools/notation.py index 826772f5..ec6513f5 100644 --- a/tools/notation.py +++ b/tools/notation.py @@ -51,7 +51,7 @@ #: and ``parameters`` are absent on purpose: they declare no equation, and what #: they print is the legend, which the page shows once rather than a row at a #: time. -BLOCKS = ('objective', 'constraints', 'expressions', 'variables', 'piecewise', 'sos', 'assumptions') +BLOCKS = ('objective', 'constraints', 'expressions', 'masks', 'variables', 'piecewise', 'sos', 'assumptions') #: The page's sections in the order of the language reference, and in each the #: fixture's declarations under the construct they show. The declaration name @@ -114,6 +114,11 @@ 'lcoe': 'Reported expression', 'marginal_price': 'Dual of a constraint', }, + 'Named masks': { + 'dispatchable': 'Named mask', + 'priced_dispatch': 'Mask that reads a mask', + 'masked': 'Mask in a condition', + }, 'Shifts': { 'ramp': 'Cyclic and acyclic shift', 'edges': 'Filled and forward shifts', From 1af86cf05dd4ee618c03104bf6fe4447318db6fb Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 1 Oct 2026 14:52:59 +0000 Subject: [PATCH 09/12] docs: the changelog line links #821 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01PBmqeqBrqpQC6MVoSrguHK --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a405acc..3b6eb6ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ it releases that version ([RELEASING.md](https://github.com/energy-models/mathsp ## Upcoming version -- feat(language): a where predicate is named once under `masks:`, and another file reads it under `given: masks:` ([#PR](https://github.com/energy-models/mathspec/pull/PR)) +- feat(language): a where predicate is named once under `masks:`, and another file reads it under `given: masks:` ([#821](https://github.com/energy-models/mathspec/pull/821)) ## 0.2.1 (2026-10-01) From 1bb06900a51efcdb93b06f728b50061c35ec56a9 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 1 Oct 2026 14:58:36 +0000 Subject: [PATCH 10/12] test: the canonical section order names masks:, after expressions: Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01PBmqeqBrqpQC6MVoSrguHK --- tests/test_canonical.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_canonical.py b/tests/test_canonical.py index 2b645f47..e539b3ba 100644 --- a/tests/test_canonical.py +++ b/tests/test_canonical.py @@ -149,6 +149,7 @@ def test_the_declarations_of_a_section_are_sorted_by_name(): 'constraints', 'objective', 'expressions', + 'masks', 'macros', 'piecewise', 'sos', From ed5622f1cf18c18fc481e4a734b1859d4d70bc14 Mon Sep 17 00:00:00 2001 From: Felix <117816358+FBumann@users.noreply.github.com> Date: Fri, 2 Oct 2026 08:51:47 +0200 Subject: [PATCH 11/12] docs(pypsa): the pypsa spec names its repeated row conditions as masks, and its topic files read them under given: masks: (#825) * docs(pypsa): the pypsa spec names its repeated row conditions as masks, and its topic files read them under given: masks: Sixteen masks name the conditions examples/pypsa.yaml wrote out at every row: per committable class C, C_committed (26 sites), C_com_ext (8), C_maint_ext (5) and C_ramps_from_previous (6); StorageUnit_fix and _ext (6 each); Line_lossy and Transformer_lossy (4 each). 165 where strings change. Each where, with every mask expanded, equals the one it replaces: 352 declarations and every cased expression were compared conjunct by conjunct. tools/pypsa_split.py places a mask in the topic its name names and writes given: masks: for every other topic that reads it. All 24 fragments load alone and merge to the one file's canonical form. The symbol table spells the masks as on^{qualifier} and prev, and the gallery's table cut includes masks. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01PBmqeqBrqpQC6MVoSrguHK * docs: the changelog line links #825 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01PBmqeqBrqpQC6MVoSrguHK --------- Co-authored-by: Claude --- CHANGELOG.md | 1 + docs/examples/pypsa.md | 607 +++++++++---------- docs/examples/pypsa/generator.md | 21 + docs/examples/pypsa/generator_commitment.md | 123 ++-- docs/examples/pypsa/generator_maintenance.md | 82 ++- docs/examples/pypsa/generator_ramping.md | 63 +- docs/examples/pypsa/line.md | 37 +- docs/examples/pypsa/link.md | 21 + docs/examples/pypsa/link_commitment.md | 123 ++-- docs/examples/pypsa/link_maintenance.md | 81 ++- docs/examples/pypsa/link_ramping.md | 67 +- docs/examples/pypsa/process.md | 21 + docs/examples/pypsa/process_commitment.md | 123 ++-- docs/examples/pypsa/process_maintenance.md | 82 ++- docs/examples/pypsa/process_ramping.md | 65 +- docs/examples/pypsa/storage_unit.md | 81 ++- docs/examples/pypsa/transformer.md | 37 +- examples/pypsa.yaml | 469 ++++++++------ examples/pypsa/generator.yaml | 7 + examples/pypsa/generator_commitment.yaml | 62 +- examples/pypsa/generator_maintenance.yaml | 43 +- examples/pypsa/generator_ramping.yaml | 36 +- examples/pypsa/line.yaml | 15 +- examples/pypsa/link.yaml | 7 + examples/pypsa/link_commitment.yaml | 62 +- examples/pypsa/link_maintenance.yaml | 42 +- examples/pypsa/link_ramping.yaml | 40 +- examples/pypsa/process.yaml | 7 + examples/pypsa/process_commitment.yaml | 62 +- examples/pypsa/process_maintenance.yaml | 43 +- examples/pypsa/process_ramping.yaml | 38 +- examples/pypsa/storage_unit.yaml | 36 +- examples/pypsa/transformer.yaml | 15 +- examples/symbols/pypsa.yaml | 16 + tools/gallery.py | 2 + tools/pypsa_split.py | 18 +- 36 files changed, 1610 insertions(+), 1045 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b6eb6ee..14ba12a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ it releases that version ([RELEASING.md](https://github.com/energy-models/mathsp ## Upcoming version +- docs(pypsa): the pypsa spec names its repeated row conditions as masks, and its topic files read them under `given: masks:` ([#825](https://github.com/energy-models/mathspec/pull/825)) - feat(language): a where predicate is named once under `masks:`, and another file reads it under `given: masks:` ([#821](https://github.com/energy-models/mathspec/pull/821)) ## 0.2.1 (2026-10-01) diff --git a/docs/examples/pypsa.md b/docs/examples/pypsa.md index be933fe2..883f72b3 100644 --- a/docs/examples/pypsa.md +++ b/docs/examples/pypsa.md @@ -5323,6 +5323,27 @@ A plain `n.optimize()`, and its multi-period and stochastic classes, in one file | $`\mathit{Transformer\_angle\_sum}`$ | `Transformer_angle_sum` over $`\Xi \times \mathcal{T} \times \mathcal{C}`$ | | $`\mathit{Cycle\_angle\_sum}`$ | `Cycle_angle_sum` over $`\Xi \times \mathcal{T} \times \mathcal{C}`$ — the voltage angle differences around a cycle: every branch flow times its cycle weight, and every transformer phase shift | +#### Masks + +| Symbol | Meaning | +|---|---| +| $`\mathrm{on}^{\mathrm{com,ext}}`$ | `Generator_com_ext` over $`\mathcal{T} \times \mathcal{G}`$ — a committable generator with an extendable, non-modular build that stands in the snapshot's period — PyPSA's `com-ext` rows, whose status is relaxed against the chosen build | +| $`\mathrm{on}^{\mathrm{mnt,ext}}`$ | `Generator_maint_ext` over $`\mathcal{T} \times \mathcal{G}`$ — a maintainable generator with an extendable build, unless it is committable and modular, that stands in the snapshot's period — the maintenance rows against the chosen build | +| $`\mathrm{on}^{\mathrm{com}}`$ | `Generator_committed` over $`\mathcal{T} \times \mathcal{G}`$ — a committable generator that stands in the snapshot's period — every unit-commitment row's set | +| $`\mathrm{prev}`$ | `Generator_ramps_from_previous` over $`\Xi \times \mathcal{T} \times \mathcal{G}`$ — a snapshot whose ramp reads a previous output — any snapshot but its period's first, and the horizon's first where the generator comes in off or carries an initial output | +| $`\mathrm{on}^{f,\mathrm{com,ext}}`$ | `Link_com_ext` over $`\mathcal{T} \times \mathcal{L}`$ — a committable link with an extendable, non-modular build that stands in the snapshot's period — PyPSA's `com-ext` rows, whose status is relaxed against the chosen build | +| $`\mathrm{on}^{f,\mathrm{mnt,ext}}`$ | `Link_maint_ext` over $`\mathcal{T} \times \mathcal{L}`$ — a maintainable link with an extendable build, unless it is committable and modular, that stands in the snapshot's period — the maintenance rows against the chosen build | +| $`\mathrm{on}^{f,\mathrm{com}}`$ | `Link_committed` over $`\mathcal{T} \times \mathcal{L}`$ — a committable link that stands in the snapshot's period — every unit-commitment row's set | +| $`\mathrm{prev}^{f}`$ | `Link_ramps_from_previous` over $`\Xi \times \mathcal{T} \times \mathcal{L}`$ — a snapshot whose ramp reads a previous output — any snapshot but its period's first, and the horizon's first where the link comes in off or carries an initial output | +| $`\mathrm{on}^{z,\mathrm{com,ext}}`$ | `Process_com_ext` over $`\mathcal{T} \times \mathcal{J}`$ — a committable process with an extendable, non-modular build that stands in the snapshot's period — PyPSA's `com-ext` rows, whose status is relaxed against the chosen build | +| $`\mathrm{on}^{z,\mathrm{mnt,ext}}`$ | `Process_maint_ext` over $`\mathcal{T} \times \mathcal{J}`$ — a maintainable process with an extendable build, unless it is committable and modular, that stands in the snapshot's period — the maintenance rows against the chosen build | +| $`\mathrm{on}^{z,\mathrm{com}}`$ | `Process_committed` over $`\mathcal{T} \times \mathcal{J}`$ — a committable process that stands in the snapshot's period — every unit-commitment row's set | +| $`\mathrm{prev}^{z}`$ | `Process_ramps_from_previous` over $`\Xi \times \mathcal{T} \times \mathcal{J}`$ — a snapshot whose ramp reads a previous output — any snapshot but its period's first, and the horizon's first where the process comes in off or carries an initial output | +| $`\mathrm{on}^{h,\mathrm{fix}}`$ | `StorageUnit_fix` over $`\mathcal{T} \times \mathcal{S}`$ — a storage unit with a fixed build that stands in the snapshot's period — PyPSA's `fix` rows | +| $`\mathrm{on}^{h,\mathrm{ext}}`$ | `StorageUnit_ext` over $`\mathcal{T} \times \mathcal{S}`$ — a storage unit with an extendable build that stands in the snapshot's period — PyPSA's `ext` rows | +| $`\mathrm{on}^{s,\mathrm{lossy}}`$ | `Line_lossy` over $`\mathcal{T} \times \mathcal{K}`$ — a line that stands in the snapshot's period, where the run models transmission losses | +| $`\mathrm{on}^{\sigma,\mathrm{lossy}}`$ | `Transformer_lossy` over $`\mathcal{T} \times \mathcal{M}`$ — a transformer that stands in the snapshot's period, where the run models transmission losses | + Upright is what the data supplies — a parameter such as $`\mathrm{Transformer\_phase\_shift\_varying}`$, a coordinate map, a label — and italic is what the solver chooses, such as $`\mathit{Transformer\_phase\_shift}`$. An index is italic too, being what a quantifier chooses, and a set is script. $`t \ominus k`$ denotes cyclic translation: index $`t-k`$ taken modulo the size of the dimension (`roll`). Plain $`t-k`$ (`shift`) has no wraparound — terms translated past the edge are simply absent. @@ -5725,12 +5746,12 @@ Z_{j} = \mathrm{z}^{\mathrm{nom,set}}_{\xi,j} \qquad \forall\, \xi \in \Xi,\ j \ StorageUnit_fix_p_dispatch_lower: description: "`StorageUnit-fix-p_dispatch-lower` — dispatch is non-negative" dims: [scenario, snapshot, storage_unit] - where: not StorageUnit_p_nom_extendable AND StorageUnit_active + where: StorageUnit_fix expression: StorageUnit_p_dispatch >= 0 ``` ```math -h^{+}_{\xi,t,s} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \neg \mathrm{ext}^{h}_{s} \wedge \mathrm{on}^{h}_{t,s} +h^{+}_{\xi,t,s} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \mathrm{on}^{h,\mathrm{fix}}_{t,s} ``` ### `StorageUnit-fix-p_dispatch-upper` @@ -5741,12 +5762,12 @@ h^{+}_{\xi,t,s} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \ StorageUnit_fix_p_dispatch_upper: description: "`StorageUnit-fix-p_dispatch-upper` — a fixed unit dispatches at most its nominal power" dims: [scenario, snapshot, storage_unit] - where: not StorageUnit_p_nom_extendable AND StorageUnit_active + where: StorageUnit_fix expression: StorageUnit_p_dispatch <= StorageUnit_p_max_pu * StorageUnit_p_nom ``` ```math -h^{+}_{\xi,t,s} \le \overline{\mathrm{h}}_{\xi,t,s} \cdot \mathrm{h}^{\mathrm{nom}}_{\xi,s} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \neg \mathrm{ext}^{h}_{s} \wedge \mathrm{on}^{h}_{t,s} +h^{+}_{\xi,t,s} \le \overline{\mathrm{h}}_{\xi,t,s} \cdot \mathrm{h}^{\mathrm{nom}}_{\xi,s} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \mathrm{on}^{h,\mathrm{fix}}_{t,s} ``` ### `StorageUnit-fix-p_store-lower` @@ -5757,12 +5778,12 @@ h^{+}_{\xi,t,s} \le \overline{\mathrm{h}}_{\xi,t,s} \cdot \mathrm{h}^{\mathrm{no StorageUnit_fix_p_store_lower: description: "`StorageUnit-fix-p_store-lower` — storing is non-negative" dims: [scenario, snapshot, storage_unit] - where: not StorageUnit_p_nom_extendable AND StorageUnit_active + where: StorageUnit_fix expression: StorageUnit_p_store >= 0 ``` ```math -h^{-}_{\xi,t,s} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \neg \mathrm{ext}^{h}_{s} \wedge \mathrm{on}^{h}_{t,s} +h^{-}_{\xi,t,s} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \mathrm{on}^{h,\mathrm{fix}}_{t,s} ``` ### `StorageUnit-fix-p_store-upper` @@ -5775,12 +5796,12 @@ StorageUnit_fix_p_store_upper: `StorageUnit-fix-p_store-upper` — a fixed unit stores at most its nominal power, the minimum-per-unit column carrying that cap negated dims: [scenario, snapshot, storage_unit] - where: not StorageUnit_p_nom_extendable AND StorageUnit_active + where: StorageUnit_fix expression: StorageUnit_p_store <= -StorageUnit_p_min_pu * StorageUnit_p_nom ``` ```math -h^{-}_{\xi,t,s} \le -\underline{\mathrm{h}}_{\xi,t,s} \cdot \mathrm{h}^{\mathrm{nom}}_{\xi,s} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \neg \mathrm{ext}^{h}_{s} \wedge \mathrm{on}^{h}_{t,s} +h^{-}_{\xi,t,s} \le -\underline{\mathrm{h}}_{\xi,t,s} \cdot \mathrm{h}^{\mathrm{nom}}_{\xi,s} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \mathrm{on}^{h,\mathrm{fix}}_{t,s} ``` ### `StorageUnit-fix-state_of_charge-lower` @@ -5791,12 +5812,12 @@ h^{-}_{\xi,t,s} \le -\underline{\mathrm{h}}_{\xi,t,s} \cdot \mathrm{h}^{\mathrm{ StorageUnit_fix_state_of_charge_lower: description: "`StorageUnit-fix-state_of_charge-lower` — charge is non-negative" dims: [scenario, snapshot, storage_unit] - where: not StorageUnit_p_nom_extendable AND StorageUnit_active + where: StorageUnit_fix expression: StorageUnit_state_of_charge >= 0 ``` ```math -\mathit{soc}_{\xi,t,s} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \neg \mathrm{ext}^{h}_{s} \wedge \mathrm{on}^{h}_{t,s} +\mathit{soc}_{\xi,t,s} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \mathrm{on}^{h,\mathrm{fix}}_{t,s} ``` ### `StorageUnit-fix-state_of_charge-upper` @@ -5807,12 +5828,12 @@ StorageUnit_fix_state_of_charge_lower: StorageUnit_fix_state_of_charge_upper: description: "`StorageUnit-fix-state_of_charge-upper` — a fixed unit holds at most its hours at nominal power" dims: [scenario, snapshot, storage_unit] - where: not StorageUnit_p_nom_extendable AND StorageUnit_active + where: StorageUnit_fix expression: StorageUnit_state_of_charge <= StorageUnit_max_hours * StorageUnit_p_nom ``` ```math -\mathit{soc}_{\xi,t,s} \le \mathrm{T}^{h}_{\xi,s} \cdot \mathrm{h}^{\mathrm{nom}}_{\xi,s} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \neg \mathrm{ext}^{h}_{s} \wedge \mathrm{on}^{h}_{t,s} +\mathit{soc}_{\xi,t,s} \le \mathrm{T}^{h}_{\xi,s} \cdot \mathrm{h}^{\mathrm{nom}}_{\xi,s} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \mathrm{on}^{h,\mathrm{fix}}_{t,s} ``` ### `Generator-com-p-lower` @@ -5823,12 +5844,12 @@ StorageUnit_fix_state_of_charge_upper: Generator_com_p_lower: description: "`Generator-com-p-lower` — a committed unit outputs at least its minimum; off, at least nothing" dims: [scenario, snapshot, generator] - where: Generator_committable AND not Generator_p_nom_extendable AND Generator_active + where: Generator_committed AND NOT Generator_p_nom_extendable expression: Generator_p >= Generator_p_min_pu * Generator_p_nom * (Generator_status - Generator_maintenance_pu * Generator_maintenance_status) ``` ```math -p_{\xi,t,g} \ge \underline{\mathrm{p}}_{\xi,t,g} \cdot \mathrm{p}^{\mathrm{nom}}_{\xi,g} \cdot \left( u_{\xi,t,g} - \gamma_{\xi,g} \cdot \mu^{u}_{\xi,t,g} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \neg \mathrm{ext}_{g} \wedge \mathrm{on}_{t,g} +p_{\xi,t,g} \ge \underline{\mathrm{p}}_{\xi,t,g} \cdot \mathrm{p}^{\mathrm{nom}}_{\xi,g} \cdot \left( u_{\xi,t,g} - \gamma_{\xi,g} \cdot \mu^{u}_{\xi,t,g} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com}}_{t,g} \wedge \neg \mathrm{ext}_{g} ``` ### `Generator-com-p-upper` @@ -5839,12 +5860,12 @@ p_{\xi,t,g} \ge \underline{\mathrm{p}}_{\xi,t,g} \cdot \mathrm{p}^{\mathrm{nom}} Generator_com_p_upper: description: "`Generator-com-p-upper` — a committed unit outputs at most what is available; off, at most nothing" dims: [scenario, snapshot, generator] - where: Generator_committable AND not Generator_p_nom_extendable AND Generator_active + where: Generator_committed AND NOT Generator_p_nom_extendable expression: Generator_p <= Generator_p_max_pu * Generator_p_nom * (Generator_status - Generator_maintenance_pu * Generator_maintenance_status) ``` ```math -p_{\xi,t,g} \le \overline{\mathrm{p}}_{\xi,t,g} \cdot \mathrm{p}^{\mathrm{nom}}_{\xi,g} \cdot \left( u_{\xi,t,g} - \gamma_{\xi,g} \cdot \mu^{u}_{\xi,t,g} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \neg \mathrm{ext}_{g} \wedge \mathrm{on}_{t,g} +p_{\xi,t,g} \le \overline{\mathrm{p}}_{\xi,t,g} \cdot \mathrm{p}^{\mathrm{nom}}_{\xi,g} \cdot \left( u_{\xi,t,g} - \gamma_{\xi,g} \cdot \mu^{u}_{\xi,t,g} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com}}_{t,g} \wedge \neg \mathrm{ext}_{g} ``` ### `Generator-com-transition-start-up` @@ -5855,12 +5876,12 @@ p_{\xi,t,g} \le \overline{\mathrm{p}}_{\xi,t,g} \cdot \mathrm{p}^{\mathrm{nom}}_ 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" dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_active + where: Generator_committed expression: Generator_start_up >= Generator_status - Generator_previous_status ``` ```math -\mathit{up}_{\xi,t,g} \ge u_{\xi,t,g} - \overleftarrow{u}_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \mathrm{on}_{t,g} +\mathit{up}_{\xi,t,g} \ge u_{\xi,t,g} - \overleftarrow{u}_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com}}_{t,g} ``` ### `Generator-com-transition-shut-down` @@ -5871,12 +5892,12 @@ Generator_com_transition_start_up: 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" dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_active + where: Generator_committed expression: Generator_shut_down >= Generator_previous_status - Generator_status ``` ```math -\mathit{dn}_{\xi,t,g} \ge \overleftarrow{u}_{\xi,t,g} - u_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \mathrm{on}_{t,g} +\mathit{dn}_{\xi,t,g} \ge \overleftarrow{u}_{\xi,t,g} - u_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com}}_{t,g} ``` ### `Generator-com-up-time` @@ -5890,12 +5911,12 @@ Generator_com_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 dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_min_up_time > 0 AND position(snapshot) > 0 AND Generator_active + where: Generator_committed AND Generator_min_up_time > 0 AND position(snapshot) > 0 expression: sum_back(Generator_start_up, along=snapshot, window=Generator_min_up_time) <= Generator_status ``` ```math -\sum_{t' \in \mathcal{T} \,:\, 0 \le t - t' < \mathrm{UT}} \mathit{up}_{\xi,t',g} \le u_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \mathrm{UT}_{\xi,g} > 0 \wedge \mathrm{pos}(t) > 0 \wedge \mathrm{on}_{t,g} +\sum_{t' \in \mathcal{T} \,:\, 0 \le t - t' < \mathrm{UT}} \mathit{up}_{\xi,t',g} \le u_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com}}_{t,g} \wedge \mathrm{UT}_{\xi,g} > 0 \wedge \mathrm{pos}(t) > 0 ``` ### `Generator-com-down-time` @@ -5909,12 +5930,12 @@ Generator_com_down_time: time is still off. The first snapshot's share of the window is the brought-in down time's, which the must-stay-down mask carries dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_min_down_time > 0 AND position(snapshot) > 0 AND Generator_active + where: Generator_committed AND Generator_min_down_time > 0 AND position(snapshot) > 0 expression: sum_back(Generator_shut_down, along=snapshot, window=Generator_min_down_time) <= 1 - Generator_status ``` ```math -\sum_{t' \in \mathcal{T} \,:\, 0 \le t - t' < \mathrm{DT}} \mathit{dn}_{\xi,t',g} \le 1 - u_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \mathrm{DT}_{\xi,g} > 0 \wedge \mathrm{pos}(t) > 0 \wedge \mathrm{on}_{t,g} +\sum_{t' \in \mathcal{T} \,:\, 0 \le t - t' < \mathrm{DT}} \mathit{dn}_{\xi,t',g} \le 1 - u_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com}}_{t,g} \wedge \mathrm{DT}_{\xi,g} > 0 \wedge \mathrm{pos}(t) > 0 ``` ### `Generator-com-status-min_up_time_must_stay_up` @@ -5925,12 +5946,12 @@ Generator_com_down_time: 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" dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_must_stay_up AND Generator_active + where: Generator_committed AND Generator_must_stay_up expression: Generator_status == 1 ``` ```math -u_{\xi,t,g} = 1 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \mathrm{hold}_{\xi,t,g} \wedge \mathrm{on}_{t,g} +u_{\xi,t,g} = 1 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com}}_{t,g} \wedge \mathrm{hold}_{\xi,t,g} ``` ### `Generator-com-status-min_down_time_must_stay_up` @@ -5943,12 +5964,12 @@ Generator_com_status_must_stay_down: `Generator-com-status-min_down_time_must_stay_up` — a unit still serving the down time it brought in stays off; PyPSA names the row `_must_stay_up` dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_must_stay_down AND Generator_active + where: Generator_committed AND Generator_must_stay_down expression: Generator_status == 0 ``` ```math -u_{\xi,t,g} = 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \mathrm{rest}_{\xi,t,g} \wedge \mathrm{on}_{t,g} +u_{\xi,t,g} = 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com}}_{t,g} \wedge \mathrm{rest}_{\xi,t,g} ``` ### `Generator-p-ramp_limit_up-run-bigM` @@ -5963,10 +5984,9 @@ Generator_p_ramp_limit_up_run_big_m: releases the row in the snapshot it turns on dims: [scenario, snapshot, generator] where: >- - Generator_committable AND Generator_p_nom_extendable AND NOT (Generator_p_nom_mod > 0) + Generator_com_ext AND (Generator_ramp_limit_up OR Generator_ramp_limit_start_up) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Generator_status_initial == 0 OR Generator_p_init))) - AND Generator_active + AND Generator_ramps_from_previous expression: >- Generator_p - Generator_previous_p <= Generator_ramp_up_rate * Generator_p_nom_ext @@ -5974,7 +5994,7 @@ Generator_p_ramp_limit_up_run_big_m: ``` ```math -p_{\xi,t,g} - \overleftarrow{p}_{\xi,t,g} \le \widetilde{\mathrm{ru}}_{\xi,t,g} \cdot P_{g} + \mathrm{M}_{\xi,g} - \mathrm{M}_{\xi,g} \cdot \overleftarrow{u}_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \mathrm{ext}_{g} \wedge \neg \left( \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) \wedge \left( \mathrm{ru}_{\xi,t,g} \text{ is defined} \vee \mathrm{ru}^{\mathrm{up}}_{\xi,g} \text{ is defined} \right) \wedge \left( \mathrm{pos}_{\mathrm{snapshot\_period}(t)}(t) > 0 \vee \mathrm{pos}(t) = 0 \wedge \left( \mathrm{u}^{0}_{\xi,g} = 0 \vee \mathrm{p}^{0}_{\xi,g} \text{ is defined} \right) \right) \wedge \mathrm{on}_{t,g} +p_{\xi,t,g} - \overleftarrow{p}_{\xi,t,g} \le \widetilde{\mathrm{ru}}_{\xi,t,g} \cdot P_{g} + \mathrm{M}_{\xi,g} - \mathrm{M}_{\xi,g} \cdot \overleftarrow{u}_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com,ext}}_{t,g} \wedge \left( \mathrm{ru}_{\xi,t,g} \text{ is defined} \vee \mathrm{ru}^{\mathrm{up}}_{\xi,g} \text{ is defined} \right) \wedge \mathrm{prev}_{\xi,t,g} ``` ### `Generator-p-ramp_limit_up-start-bigM` @@ -5989,10 +6009,9 @@ Generator_p_ramp_limit_up_start_big_m: the chosen build; the big M releases the row everywhere else dims: [scenario, snapshot, generator] where: >- - Generator_committable AND Generator_p_nom_extendable AND NOT (Generator_p_nom_mod > 0) + Generator_com_ext AND (Generator_ramp_limit_up OR Generator_ramp_limit_start_up) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Generator_status_initial == 0 OR Generator_p_init))) - AND Generator_active + AND Generator_ramps_from_previous expression: >- Generator_p - Generator_previous_p <= Generator_start_up_rate * Generator_p_nom_ext @@ -6000,7 +6019,7 @@ Generator_p_ramp_limit_up_start_big_m: ``` ```math -p_{\xi,t,g} - \overleftarrow{p}_{\xi,t,g} \le \widetilde{\mathrm{ru}}^{\mathrm{up}}_{\xi,g} \cdot P_{g} + \mathrm{M}_{\xi,g} - \mathrm{M}_{\xi,g} \cdot \mathit{up}_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \mathrm{ext}_{g} \wedge \neg \left( \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) \wedge \left( \mathrm{ru}_{\xi,t,g} \text{ is defined} \vee \mathrm{ru}^{\mathrm{up}}_{\xi,g} \text{ is defined} \right) \wedge \left( \mathrm{pos}_{\mathrm{snapshot\_period}(t)}(t) > 0 \vee \mathrm{pos}(t) = 0 \wedge \left( \mathrm{u}^{0}_{\xi,g} = 0 \vee \mathrm{p}^{0}_{\xi,g} \text{ is defined} \right) \right) \wedge \mathrm{on}_{t,g} +p_{\xi,t,g} - \overleftarrow{p}_{\xi,t,g} \le \widetilde{\mathrm{ru}}^{\mathrm{up}}_{\xi,g} \cdot P_{g} + \mathrm{M}_{\xi,g} - \mathrm{M}_{\xi,g} \cdot \mathit{up}_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com,ext}}_{t,g} \wedge \left( \mathrm{ru}_{\xi,t,g} \text{ is defined} \vee \mathrm{ru}^{\mathrm{up}}_{\xi,g} \text{ is defined} \right) \wedge \mathrm{prev}_{\xi,t,g} ``` ### `Generator-p-ramp_limit_down-run-bigM` @@ -6015,10 +6034,9 @@ Generator_p_ramp_limit_down_run_big_m: releases the row in the snapshot it turns off dims: [scenario, snapshot, generator] where: >- - Generator_committable AND Generator_p_nom_extendable AND NOT (Generator_p_nom_mod > 0) + Generator_com_ext AND (Generator_ramp_limit_down OR Generator_ramp_limit_shut_down) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Generator_status_initial == 0 OR Generator_p_init))) - AND Generator_active + AND Generator_ramps_from_previous expression: >- Generator_previous_p - Generator_p <= Generator_ramp_down_rate * Generator_p_nom_ext @@ -6026,7 +6044,7 @@ Generator_p_ramp_limit_down_run_big_m: ``` ```math -\overleftarrow{p}_{\xi,t,g} - p_{\xi,t,g} \le \widetilde{\mathrm{rd}}_{\xi,t,g} \cdot P_{g} + \mathrm{M}_{\xi,g} - \mathrm{M}_{\xi,g} \cdot u_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \mathrm{ext}_{g} \wedge \neg \left( \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) \wedge \left( \mathrm{rd}_{\xi,t,g} \text{ is defined} \vee \mathrm{rd}^{\mathrm{dn}}_{\xi,g} \text{ is defined} \right) \wedge \left( \mathrm{pos}_{\mathrm{snapshot\_period}(t)}(t) > 0 \vee \mathrm{pos}(t) = 0 \wedge \left( \mathrm{u}^{0}_{\xi,g} = 0 \vee \mathrm{p}^{0}_{\xi,g} \text{ is defined} \right) \right) \wedge \mathrm{on}_{t,g} +\overleftarrow{p}_{\xi,t,g} - p_{\xi,t,g} \le \widetilde{\mathrm{rd}}_{\xi,t,g} \cdot P_{g} + \mathrm{M}_{\xi,g} - \mathrm{M}_{\xi,g} \cdot u_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com,ext}}_{t,g} \wedge \left( \mathrm{rd}_{\xi,t,g} \text{ is defined} \vee \mathrm{rd}^{\mathrm{dn}}_{\xi,g} \text{ is defined} \right) \wedge \mathrm{prev}_{\xi,t,g} ``` ### `Generator-p-ramp_limit_down-shut-bigM` @@ -6041,10 +6059,9 @@ Generator_p_ramp_limit_down_shut_big_m: the chosen build; the big M releases the row everywhere else dims: [scenario, snapshot, generator] where: >- - Generator_committable AND Generator_p_nom_extendable AND NOT (Generator_p_nom_mod > 0) + Generator_com_ext AND (Generator_ramp_limit_down OR Generator_ramp_limit_shut_down) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Generator_status_initial == 0 OR Generator_p_init))) - AND Generator_active + AND Generator_ramps_from_previous expression: >- Generator_previous_p - Generator_p <= Generator_shut_down_rate * Generator_p_nom_ext @@ -6052,7 +6069,7 @@ Generator_p_ramp_limit_down_shut_big_m: ``` ```math -\overleftarrow{p}_{\xi,t,g} - p_{\xi,t,g} \le \widetilde{\mathrm{rd}}^{\mathrm{dn}}_{\xi,g} \cdot P_{g} + \mathrm{M}_{\xi,g} - \mathrm{M}_{\xi,g} \cdot \mathit{dn}_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \mathrm{ext}_{g} \wedge \neg \left( \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) \wedge \left( \mathrm{rd}_{\xi,t,g} \text{ is defined} \vee \mathrm{rd}^{\mathrm{dn}}_{\xi,g} \text{ is defined} \right) \wedge \left( \mathrm{pos}_{\mathrm{snapshot\_period}(t)}(t) > 0 \vee \mathrm{pos}(t) = 0 \wedge \left( \mathrm{u}^{0}_{\xi,g} = 0 \vee \mathrm{p}^{0}_{\xi,g} \text{ is defined} \right) \right) \wedge \mathrm{on}_{t,g} +\overleftarrow{p}_{\xi,t,g} - p_{\xi,t,g} \le \widetilde{\mathrm{rd}}^{\mathrm{dn}}_{\xi,g} \cdot P_{g} + \mathrm{M}_{\xi,g} - \mathrm{M}_{\xi,g} \cdot \mathit{dn}_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com,ext}}_{t,g} \wedge \left( \mathrm{rd}_{\xi,t,g} \text{ is defined} \vee \mathrm{rd}^{\mathrm{dn}}_{\xi,g} \text{ is defined} \right) \wedge \mathrm{prev}_{\xi,t,g} ``` ### `Generator-p_nom_modularity` @@ -6081,12 +6098,12 @@ Generator_com_ext_p_upper_cap: `Generator-com-ext-p-upper-cap` — a committed extendable unit outputs at most what is available of the chosen build, whatever its status dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_p_nom_extendable AND NOT (Generator_p_nom_mod > 0) AND Generator_active + where: Generator_com_ext expression: Generator_p <= Generator_p_max_pu * (Generator_p_nom_ext - Generator_maintenance_pu * Generator_maintenance_capacity) ``` ```math -p_{\xi,t,g} \le \overline{\mathrm{p}}_{\xi,t,g} \cdot \left( P_{g} - \gamma_{\xi,g} \cdot \mu^{\mathrm{nom}}_{\xi,t,g} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \mathrm{ext}_{g} \wedge \neg \left( \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) \wedge \mathrm{on}_{t,g} +p_{\xi,t,g} \le \overline{\mathrm{p}}_{\xi,t,g} \cdot \left( P_{g} - \gamma_{\xi,g} \cdot \mu^{\mathrm{nom}}_{\xi,t,g} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com,ext}}_{t,g} ``` ### `Generator-com-ext-p-upper-bigM` @@ -6097,12 +6114,12 @@ p_{\xi,t,g} \le \overline{\mathrm{p}}_{\xi,t,g} \cdot \left( P_{g} - \gamma_{\xi 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" dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_p_nom_extendable AND NOT (Generator_p_nom_mod > 0) AND Generator_active + where: Generator_com_ext expression: Generator_p <= Generator_big_m * Generator_status ``` ```math -p_{\xi,t,g} \le \mathrm{M}_{\xi,g} \cdot u_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \mathrm{ext}_{g} \wedge \neg \left( \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) \wedge \mathrm{on}_{t,g} +p_{\xi,t,g} \le \mathrm{M}_{\xi,g} \cdot u_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com,ext}}_{t,g} ``` ### `Generator-com-ext-p-lower` @@ -6115,7 +6132,7 @@ Generator_com_ext_p_lower: `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 dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_p_nom_extendable AND NOT (Generator_p_nom_mod > 0) AND Generator_active + where: Generator_com_ext expression: >- Generator_p >= Generator_p_min_pu * (Generator_p_nom_ext - Generator_maintenance_pu * Generator_maintenance_capacity) @@ -6123,7 +6140,7 @@ Generator_com_ext_p_lower: ``` ```math -p_{\xi,t,g} \ge \underline{\mathrm{p}}_{\xi,t,g} \cdot \left( P_{g} - \gamma_{\xi,g} \cdot \mu^{\mathrm{nom}}_{\xi,t,g} \right) + \mathrm{M}_{\xi,g} \cdot u_{\xi,t,g} - \mathrm{M}_{\xi,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \mathrm{ext}_{g} \wedge \neg \left( \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) \wedge \mathrm{on}_{t,g} +p_{\xi,t,g} \ge \underline{\mathrm{p}}_{\xi,t,g} \cdot \left( P_{g} - \gamma_{\xi,g} \cdot \mu^{\mathrm{nom}}_{\xi,t,g} \right) + \mathrm{M}_{\xi,g} \cdot u_{\xi,t,g} - \mathrm{M}_{\xi,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com,ext}}_{t,g} ``` ### `Generator-com-ext-p-lower-nonneg` @@ -6137,14 +6154,12 @@ Generator_com_ext_p_lower_nonneg: negative, output is also plainly non-negative, a row the big-M lower cannot assert while the unit is off dims: [scenario, snapshot, generator] - where: >- - Generator_committable AND Generator_p_nom_extendable - AND Generator_p_min_pu_nonneg AND NOT (Generator_p_nom_mod > 0) AND Generator_active + where: Generator_com_ext AND Generator_p_min_pu_nonneg expression: Generator_p >= 0 ``` ```math -p_{\xi,t,g} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \mathrm{ext}_{g} \wedge \mathrm{nonneg}_{g} \wedge \neg \left( \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) \wedge \mathrm{on}_{t,g} +p_{\xi,t,g} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com,ext}}_{t,g} \wedge \mathrm{nonneg}_{g} ``` ### `Generator-com-mod-p-lower` @@ -6157,12 +6172,12 @@ Generator_com_mod_p_lower: `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 dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_p_nom_mod > 0 AND Generator_active + where: Generator_committed AND Generator_p_nom_mod > 0 expression: Generator_p >= Generator_p_min_pu * Generator_p_nom_mod * (Generator_status - Generator_maintenance_pu * Generator_maintenance_status) ``` ```math -p_{\xi,t,g} \ge \underline{\mathrm{p}}_{\xi,t,g} \cdot \mathrm{p}^{\mathrm{mod}}_{g} \cdot \left( u_{\xi,t,g} - \gamma_{\xi,g} \cdot \mu^{u}_{\xi,t,g} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 \wedge \mathrm{on}_{t,g} +p_{\xi,t,g} \ge \underline{\mathrm{p}}_{\xi,t,g} \cdot \mathrm{p}^{\mathrm{mod}}_{g} \cdot \left( u_{\xi,t,g} - \gamma_{\xi,g} \cdot \mu^{u}_{\xi,t,g} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com}}_{t,g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 ``` ### `Generator-com-mod-p-upper` @@ -6175,12 +6190,12 @@ Generator_com_mod_p_upper: `Generator-com-mod-p-upper` — a committed modular unit outputs at most one module's share, whether the build is fixed or a decision dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_p_nom_mod > 0 AND Generator_active + where: Generator_committed AND Generator_p_nom_mod > 0 expression: Generator_p <= Generator_p_max_pu * Generator_p_nom_mod * (Generator_status - Generator_maintenance_pu * Generator_maintenance_status) ``` ```math -p_{\xi,t,g} \le \overline{\mathrm{p}}_{\xi,t,g} \cdot \mathrm{p}^{\mathrm{mod}}_{g} \cdot \left( u_{\xi,t,g} - \gamma_{\xi,g} \cdot \mu^{u}_{\xi,t,g} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 \wedge \mathrm{on}_{t,g} +p_{\xi,t,g} \le \overline{\mathrm{p}}_{\xi,t,g} \cdot \mathrm{p}^{\mathrm{mod}}_{g} \cdot \left( u_{\xi,t,g} - \gamma_{\xi,g} \cdot \mu^{u}_{\xi,t,g} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com}}_{t,g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 ``` ### `Generator-status-p-fixed-upper` @@ -6194,12 +6209,12 @@ Generator_status_p_fixed_upper: 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 dims: [scenario, snapshot, generator] - where: Generator_committable AND NOT (Generator_p_nom_extendable AND Generator_p_nom_mod > 0) AND Generator_active + where: Generator_committed AND NOT (Generator_p_nom_extendable AND Generator_p_nom_mod > 0) expression: Generator_status <= Generator_modules_installed ``` ```math -u_{\xi,t,g} \le \mathrm{N}^{\mathrm{fix}}_{\xi,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \neg \left( \mathrm{ext}_{g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) \wedge \mathrm{on}_{t,g} +u_{\xi,t,g} \le \mathrm{N}^{\mathrm{fix}}_{\xi,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com}}_{t,g} \wedge \neg \left( \mathrm{ext}_{g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) ``` ### `Generator-start_up-p-fixed-upper` @@ -6213,12 +6228,12 @@ Generator_start_up_p_fixed_upper: 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 dims: [scenario, snapshot, generator] - where: Generator_committable AND NOT (Generator_p_nom_extendable AND Generator_p_nom_mod > 0) AND Generator_active + where: Generator_committed AND NOT (Generator_p_nom_extendable AND Generator_p_nom_mod > 0) expression: Generator_start_up <= Generator_modules_installed ``` ```math -\mathit{up}_{\xi,t,g} \le \mathrm{N}^{\mathrm{fix}}_{\xi,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \neg \left( \mathrm{ext}_{g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) \wedge \mathrm{on}_{t,g} +\mathit{up}_{\xi,t,g} \le \mathrm{N}^{\mathrm{fix}}_{\xi,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com}}_{t,g} \wedge \neg \left( \mathrm{ext}_{g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) ``` ### `Generator-shut_down-p-fixed-upper` @@ -6232,12 +6247,12 @@ Generator_shut_down_p_fixed_upper: 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 dims: [scenario, snapshot, generator] - where: Generator_committable AND NOT (Generator_p_nom_extendable AND Generator_p_nom_mod > 0) AND Generator_active + where: Generator_committed AND NOT (Generator_p_nom_extendable AND Generator_p_nom_mod > 0) expression: Generator_shut_down <= Generator_modules_installed ``` ```math -\mathit{dn}_{\xi,t,g} \le \mathrm{N}^{\mathrm{fix}}_{\xi,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \neg \left( \mathrm{ext}_{g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) \wedge \mathrm{on}_{t,g} +\mathit{dn}_{\xi,t,g} \le \mathrm{N}^{\mathrm{fix}}_{\xi,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com}}_{t,g} \wedge \neg \left( \mathrm{ext}_{g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) ``` ### `Generator-status-p_nom-variable-upper` @@ -6248,12 +6263,12 @@ Generator_shut_down_p_fixed_upper: Generator_status_p_nom_variable_upper: description: "`Generator-status-p_nom-variable-upper` — a modular unit is on only where a module is built" dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_p_nom_extendable AND Generator_p_nom_mod > 0 AND Generator_active + where: Generator_committed AND Generator_p_nom_extendable AND Generator_p_nom_mod > 0 expression: Generator_status <= Generator_n_mod ``` ```math -u_{\xi,t,g} \le N_{g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \mathrm{ext}_{g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 \wedge \mathrm{on}_{t,g} +u_{\xi,t,g} \le N_{g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com}}_{t,g} \wedge \mathrm{ext}_{g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 ``` ### `Generator-start_up-p_nom-variable-upper` @@ -6264,12 +6279,12 @@ u_{\xi,t,g} \le N_{g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \ 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" dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_p_nom_extendable AND Generator_p_nom_mod > 0 AND Generator_active + where: Generator_committed AND Generator_p_nom_extendable AND Generator_p_nom_mod > 0 expression: Generator_start_up <= Generator_n_mod ``` ```math -\mathit{up}_{\xi,t,g} \le N_{g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \mathrm{ext}_{g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 \wedge \mathrm{on}_{t,g} +\mathit{up}_{\xi,t,g} \le N_{g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com}}_{t,g} \wedge \mathrm{ext}_{g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 ``` ### `Generator-shut_down-p_nom-variable-upper` @@ -6280,12 +6295,12 @@ Generator_start_up_p_nom_variable_upper: 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" dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_p_nom_extendable AND Generator_p_nom_mod > 0 AND Generator_active + where: Generator_committed AND Generator_p_nom_extendable AND Generator_p_nom_mod > 0 expression: Generator_shut_down <= Generator_n_mod ``` ```math -\mathit{dn}_{\xi,t,g} \le N_{g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \mathrm{ext}_{g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 \wedge \mathrm{on}_{t,g} +\mathit{dn}_{\xi,t,g} \le N_{g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com}}_{t,g} \wedge \mathrm{ext}_{g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 ``` ### `Generator-maint-event-count` @@ -6349,12 +6364,12 @@ Generator_maintcap_upper: `Generator-maintcap_upper` — the build taken off is at most the chosen build in maintenance, and at most the build less its floor out of it dims: [scenario, snapshot, generator] - where: Generator_maintainable AND Generator_p_nom_extendable AND NOT (Generator_committable AND Generator_p_nom_mod > 0) AND Generator_active + where: Generator_maint_ext expression: Generator_maintenance_capacity <= Generator_p_nom_ext - Generator_p_nom_min * (1 - Generator_maintenance) ``` ```math -\mu^{\mathrm{nom}}_{\xi,t,g} \le P_{g} - \underline{\mathrm{p}}^{\mathrm{nom}}_{\xi,g} \cdot \left( 1 - \mu_{\xi,t,g} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{mnt}_{g} \wedge \mathrm{ext}_{g} \wedge \neg \left( \mathrm{com}_{g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) \wedge \mathrm{on}_{t,g} +\mu^{\mathrm{nom}}_{\xi,t,g} \le P_{g} - \underline{\mathrm{p}}^{\mathrm{nom}}_{\xi,g} \cdot \left( 1 - \mu_{\xi,t,g} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{mnt,ext}}_{t,g} ``` ### `Generator-maintcap_upper_nommax` @@ -6365,12 +6380,12 @@ Generator_maintcap_upper: Generator_maintcap_upper_nommax: description: "`Generator-maintcap_upper_nommax` — out of maintenance, no build is taken off" dims: [scenario, snapshot, generator] - where: Generator_maintainable AND Generator_p_nom_extendable AND NOT (Generator_committable AND Generator_p_nom_mod > 0) AND Generator_active + where: Generator_maint_ext expression: Generator_maintenance_capacity <= Generator_p_nom_max * Generator_maintenance ``` ```math -\mu^{\mathrm{nom}}_{\xi,t,g} \le \overline{\mathrm{p}}^{\mathrm{nom}}_{\xi,g} \cdot \mu_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{mnt}_{g} \wedge \mathrm{ext}_{g} \wedge \neg \left( \mathrm{com}_{g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) \wedge \mathrm{on}_{t,g} +\mu^{\mathrm{nom}}_{\xi,t,g} \le \overline{\mathrm{p}}^{\mathrm{nom}}_{\xi,g} \cdot \mu_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{mnt,ext}}_{t,g} ``` ### `Generator-maintcap_lower_nommax` @@ -6381,12 +6396,12 @@ Generator_maintcap_upper_nommax: Generator_maintcap_lower_nommax: description: "`Generator-maintcap_lower_nommax` — in maintenance, the whole chosen build is taken off" dims: [scenario, snapshot, generator] - where: Generator_maintainable AND Generator_p_nom_extendable AND NOT (Generator_committable AND Generator_p_nom_mod > 0) AND Generator_active + where: Generator_maint_ext expression: Generator_maintenance_capacity >= Generator_p_nom_ext - Generator_p_nom_max * (1 - Generator_maintenance) ``` ```math -\mu^{\mathrm{nom}}_{\xi,t,g} \ge P_{g} - \overline{\mathrm{p}}^{\mathrm{nom}}_{\xi,g} \cdot \left( 1 - \mu_{\xi,t,g} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{mnt}_{g} \wedge \mathrm{ext}_{g} \wedge \neg \left( \mathrm{com}_{g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) \wedge \mathrm{on}_{t,g} +\mu^{\mathrm{nom}}_{\xi,t,g} \ge P_{g} - \overline{\mathrm{p}}^{\mathrm{nom}}_{\xi,g} \cdot \left( 1 - \mu_{\xi,t,g} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{mnt,ext}}_{t,g} ``` ### `Generator-maintcap_lower_nommin` @@ -6397,12 +6412,12 @@ Generator_maintcap_lower_nommax: Generator_maintcap_lower_nommin: description: "`Generator-maintcap_lower_nommin` — in maintenance, at least the floor of the build is taken off" dims: [scenario, snapshot, generator] - where: Generator_maintainable AND Generator_p_nom_extendable AND NOT (Generator_committable AND Generator_p_nom_mod > 0) AND Generator_active AND Generator_p_nom_min > 0 + where: Generator_maint_ext AND Generator_p_nom_min > 0 expression: Generator_maintenance_capacity >= Generator_p_nom_min * Generator_maintenance ``` ```math -\mu^{\mathrm{nom}}_{\xi,t,g} \ge \underline{\mathrm{p}}^{\mathrm{nom}}_{\xi,g} \cdot \mu_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{mnt}_{g} \wedge \mathrm{ext}_{g} \wedge \neg \left( \mathrm{com}_{g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) \wedge \mathrm{on}_{t,g} \wedge \underline{\mathrm{p}}^{\mathrm{nom}}_{\xi,g} > 0 +\mu^{\mathrm{nom}}_{\xi,t,g} \ge \underline{\mathrm{p}}^{\mathrm{nom}}_{\xi,g} \cdot \mu_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{mnt,ext}}_{t,g} \wedge \underline{\mathrm{p}}^{\mathrm{nom}}_{\xi,g} > 0 ``` ### `Generator-maint-status-le-status` @@ -6413,12 +6428,12 @@ Generator_maintcap_lower_nommin: Generator_maint_status_le_status: description: "`Generator-maint-status-le-status` — the status in maintenance is at most the status" dims: [scenario, snapshot, generator] - where: Generator_maintainable AND Generator_committable AND NOT Generator_p_nom_extendable AND Generator_active + where: Generator_maintainable AND Generator_committed AND NOT Generator_p_nom_extendable expression: Generator_maintenance_status <= Generator_status ``` ```math -\mu^{u}_{\xi,t,g} \le u_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{mnt}_{g} \wedge \mathrm{com}_{g} \wedge \neg \mathrm{ext}_{g} \wedge \mathrm{on}_{t,g} +\mu^{u}_{\xi,t,g} \le u_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{mnt}_{g} \wedge \mathrm{on}^{\mathrm{com}}_{t,g} \wedge \neg \mathrm{ext}_{g} ``` ### `Generator-maint-status-le-maint` @@ -6429,12 +6444,12 @@ Generator_maint_status_le_status: Generator_maint_status_le_maint: description: "`Generator-maint-status-le-maint` — out of maintenance, the status in maintenance is zero" dims: [scenario, snapshot, generator] - where: Generator_maintainable AND Generator_committable AND NOT Generator_p_nom_extendable AND Generator_active + where: Generator_maintainable AND Generator_committed AND NOT Generator_p_nom_extendable expression: Generator_maintenance_status <= Generator_maintenance ``` ```math -\mu^{u}_{\xi,t,g} \le \mu_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{mnt}_{g} \wedge \mathrm{com}_{g} \wedge \neg \mathrm{ext}_{g} \wedge \mathrm{on}_{t,g} +\mu^{u}_{\xi,t,g} \le \mu_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{mnt}_{g} \wedge \mathrm{on}^{\mathrm{com}}_{t,g} \wedge \neg \mathrm{ext}_{g} ``` ### `Generator-maint-status-lb` @@ -6445,12 +6460,12 @@ Generator_maint_status_le_maint: Generator_maint_status_lb: description: "`Generator-maint-status-lb` — on and in maintenance, the status in maintenance is one" dims: [scenario, snapshot, generator] - where: Generator_maintainable AND Generator_committable AND NOT Generator_p_nom_extendable AND Generator_active + where: Generator_maintainable AND Generator_committed AND NOT Generator_p_nom_extendable expression: Generator_maintenance_status >= Generator_status + Generator_maintenance - 1 ``` ```math -\mu^{u}_{\xi,t,g} \ge u_{\xi,t,g} + \mu_{\xi,t,g} - 1 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{mnt}_{g} \wedge \mathrm{com}_{g} \wedge \neg \mathrm{ext}_{g} \wedge \mathrm{on}_{t,g} +\mu^{u}_{\xi,t,g} \ge u_{\xi,t,g} + \mu_{\xi,t,g} - 1 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{mnt}_{g} \wedge \mathrm{on}^{\mathrm{com}}_{t,g} \wedge \neg \mathrm{ext}_{g} ``` ### `Generator-maint-modstatus-le-status` @@ -6461,12 +6476,12 @@ Generator_maint_status_lb: Generator_maint_modstatus_le_status: description: "`Generator-maint-modstatus-le-status` — the modules on in maintenance are at most the modules on" dims: [scenario, snapshot, generator] - where: Generator_maintainable AND Generator_committable AND Generator_p_nom_mod > 0 AND Generator_active + where: Generator_maintainable AND Generator_committed AND Generator_p_nom_mod > 0 expression: Generator_maintenance_status <= Generator_status ``` ```math -\mu^{u}_{\xi,t,g} \le u_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{mnt}_{g} \wedge \mathrm{com}_{g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 \wedge \mathrm{on}_{t,g} +\mu^{u}_{\xi,t,g} \le u_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{mnt}_{g} \wedge \mathrm{on}^{\mathrm{com}}_{t,g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 ``` ### `Generator-maint-modstatus-le-maint` @@ -6479,12 +6494,12 @@ Generator_maint_modstatus_le_maint: `Generator-maint-modstatus-le-maint` — out of maintenance, no module is on in maintenance; in it, at most the modules the build cap holds dims: [scenario, snapshot, generator] - where: Generator_maintainable AND Generator_committable AND Generator_p_nom_mod > 0 AND Generator_active + where: Generator_maintainable AND Generator_committed AND Generator_p_nom_mod > 0 expression: Generator_maintenance_status <= Generator_p_nom_max / Generator_p_nom_mod * Generator_maintenance ``` ```math -\mu^{u}_{\xi,t,g} \le \frac{\overline{\mathrm{p}}^{\mathrm{nom}}_{\xi,g}}{\mathrm{p}^{\mathrm{mod}}_{g}} \cdot \mu_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{mnt}_{g} \wedge \mathrm{com}_{g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 \wedge \mathrm{on}_{t,g} +\mu^{u}_{\xi,t,g} \le \frac{\overline{\mathrm{p}}^{\mathrm{nom}}_{\xi,g}}{\mathrm{p}^{\mathrm{mod}}_{g}} \cdot \mu_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{mnt}_{g} \wedge \mathrm{on}^{\mathrm{com}}_{t,g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 ``` ### `Generator-maint-modstatus-lb` @@ -6495,12 +6510,12 @@ Generator_maint_modstatus_le_maint: Generator_maint_modstatus_lb: description: "`Generator-maint-modstatus-lb` — in maintenance, every module on is on in maintenance" dims: [scenario, snapshot, generator] - where: Generator_maintainable AND Generator_committable AND Generator_p_nom_mod > 0 AND Generator_active + where: Generator_maintainable AND Generator_committed AND Generator_p_nom_mod > 0 expression: Generator_maintenance_status >= Generator_status - Generator_p_nom_max / Generator_p_nom_mod * (1 - Generator_maintenance) ``` ```math -\mu^{u}_{\xi,t,g} \ge u_{\xi,t,g} - \frac{\overline{\mathrm{p}}^{\mathrm{nom}}_{\xi,g}}{\mathrm{p}^{\mathrm{mod}}_{g}} \cdot \left( 1 - \mu_{\xi,t,g} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{mnt}_{g} \wedge \mathrm{com}_{g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 \wedge \mathrm{on}_{t,g} +\mu^{u}_{\xi,t,g} \ge u_{\xi,t,g} - \frac{\overline{\mathrm{p}}^{\mathrm{nom}}_{\xi,g}}{\mathrm{p}^{\mathrm{mod}}_{g}} \cdot \left( 1 - \mu_{\xi,t,g} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{mnt}_{g} \wedge \mathrm{on}^{\mathrm{com}}_{t,g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 ``` ### `Link-com-p-lower` @@ -6511,12 +6526,12 @@ Generator_maint_modstatus_lb: Link_com_p_lower: description: "`Link-com-p-lower` — a committed link flows at least its minimum; off, at least nothing" dims: [scenario, snapshot, link] - where: Link_committable AND not Link_p_nom_extendable AND Link_active + where: Link_committed AND NOT Link_p_nom_extendable expression: Link_p >= Link_p_min_pu * Link_p_nom * (Link_status - Link_maintenance_pu * Link_maintenance_status) ``` ```math -f_{\xi,t,l} \ge \underline{\mathrm{f}}_{\xi,t,l} \cdot \mathrm{f}^{\mathrm{nom}}_{\xi,l} \cdot \left( u^{f}_{\xi,t,l} - \gamma^{f}_{\xi,l} \cdot \mu^{f,u}_{\xi,t,l} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \neg \mathrm{ext}^{f}_{l} \wedge \mathrm{on}^{f}_{t,l} +f_{\xi,t,l} \ge \underline{\mathrm{f}}_{\xi,t,l} \cdot \mathrm{f}^{\mathrm{nom}}_{\xi,l} \cdot \left( u^{f}_{\xi,t,l} - \gamma^{f}_{\xi,l} \cdot \mu^{f,u}_{\xi,t,l} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com}}_{t,l} \wedge \neg \mathrm{ext}^{f}_{l} ``` ### `Link-com-p-upper` @@ -6527,12 +6542,12 @@ f_{\xi,t,l} \ge \underline{\mathrm{f}}_{\xi,t,l} \cdot \mathrm{f}^{\mathrm{nom}} Link_com_p_upper: description: "`Link-com-p-upper` — a committed link flows at most what is available; off, at most nothing" dims: [scenario, snapshot, link] - where: Link_committable AND not Link_p_nom_extendable AND Link_active + where: Link_committed AND NOT Link_p_nom_extendable expression: Link_p <= Link_p_max_pu * Link_p_nom * (Link_status - Link_maintenance_pu * Link_maintenance_status) ``` ```math -f_{\xi,t,l} \le \overline{\mathrm{f}}_{\xi,t,l} \cdot \mathrm{f}^{\mathrm{nom}}_{\xi,l} \cdot \left( u^{f}_{\xi,t,l} - \gamma^{f}_{\xi,l} \cdot \mu^{f,u}_{\xi,t,l} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \neg \mathrm{ext}^{f}_{l} \wedge \mathrm{on}^{f}_{t,l} +f_{\xi,t,l} \le \overline{\mathrm{f}}_{\xi,t,l} \cdot \mathrm{f}^{\mathrm{nom}}_{\xi,l} \cdot \left( u^{f}_{\xi,t,l} - \gamma^{f}_{\xi,l} \cdot \mu^{f,u}_{\xi,t,l} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com}}_{t,l} \wedge \neg \mathrm{ext}^{f}_{l} ``` ### `Link-com-transition-start-up` @@ -6543,12 +6558,12 @@ f_{\xi,t,l} \le \overline{\mathrm{f}}_{\xi,t,l} \cdot \mathrm{f}^{\mathrm{nom}}_ Link_com_transition_start_up: description: "`Link-com-transition-start-up` — turning on is a start, counted against the state the link carried into the snapshot" dims: [scenario, snapshot, link] - where: Link_committable AND Link_active + where: Link_committed expression: Link_start_up >= Link_status - Link_previous_status ``` ```math -\mathit{up}^{f}_{\xi,t,l} \ge u^{f}_{\xi,t,l} - \overleftarrow{u}^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \mathrm{on}^{f}_{t,l} +\mathit{up}^{f}_{\xi,t,l} \ge u^{f}_{\xi,t,l} - \overleftarrow{u}^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com}}_{t,l} ``` ### `Link-com-transition-shut-down` @@ -6559,12 +6574,12 @@ Link_com_transition_start_up: Link_com_transition_shut_down: description: "`Link-com-transition-shut-down` — turning off is a stop, counted against the state the link carried into the snapshot" dims: [scenario, snapshot, link] - where: Link_committable AND Link_active + where: Link_committed expression: Link_shut_down >= Link_previous_status - Link_status ``` ```math -\mathit{dn}^{f}_{\xi,t,l} \ge \overleftarrow{u}^{f}_{\xi,t,l} - u^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \mathrm{on}^{f}_{t,l} +\mathit{dn}^{f}_{\xi,t,l} \ge \overleftarrow{u}^{f}_{\xi,t,l} - u^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com}}_{t,l} ``` ### `Link-com-up-time` @@ -6578,12 +6593,12 @@ Link_com_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 dims: [scenario, snapshot, link] - where: Link_committable AND Link_min_up_time > 0 AND position(snapshot) > 0 AND Link_active + where: Link_committed AND Link_min_up_time > 0 AND position(snapshot) > 0 expression: sum_back(Link_start_up, along=snapshot, window=Link_min_up_time) <= Link_status ``` ```math -\sum_{t' \in \mathcal{T} \,:\, 0 \le t - t' < \mathrm{UT}^{f}} \mathit{up}^{f}_{\xi,t',l} \le u^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \mathrm{UT}^{f}_{\xi,l} > 0 \wedge \mathrm{pos}(t) > 0 \wedge \mathrm{on}^{f}_{t,l} +\sum_{t' \in \mathcal{T} \,:\, 0 \le t - t' < \mathrm{UT}^{f}} \mathit{up}^{f}_{\xi,t',l} \le u^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com}}_{t,l} \wedge \mathrm{UT}^{f}_{\xi,l} > 0 \wedge \mathrm{pos}(t) > 0 ``` ### `Link-com-down-time` @@ -6597,12 +6612,12 @@ Link_com_down_time: time is still off. The first snapshot's share of the window is the brought-in down time's, which the must-stay-down mask carries dims: [scenario, snapshot, link] - where: Link_committable AND Link_min_down_time > 0 AND position(snapshot) > 0 AND Link_active + where: Link_committed AND Link_min_down_time > 0 AND position(snapshot) > 0 expression: sum_back(Link_shut_down, along=snapshot, window=Link_min_down_time) <= 1 - Link_status ``` ```math -\sum_{t' \in \mathcal{T} \,:\, 0 \le t - t' < \mathrm{DT}^{f}} \mathit{dn}^{f}_{\xi,t',l} \le 1 - u^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \mathrm{DT}^{f}_{\xi,l} > 0 \wedge \mathrm{pos}(t) > 0 \wedge \mathrm{on}^{f}_{t,l} +\sum_{t' \in \mathcal{T} \,:\, 0 \le t - t' < \mathrm{DT}^{f}} \mathit{dn}^{f}_{\xi,t',l} \le 1 - u^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com}}_{t,l} \wedge \mathrm{DT}^{f}_{\xi,l} > 0 \wedge \mathrm{pos}(t) > 0 ``` ### `Link-com-status-min_up_time_must_stay_up` @@ -6613,12 +6628,12 @@ Link_com_down_time: Link_com_status_must_stay_up: description: "`Link-com-status-min_up_time_must_stay_up` — a link still serving the up time it brought in stays on" dims: [scenario, snapshot, link] - where: Link_committable AND Link_must_stay_up AND Link_active + where: Link_committed AND Link_must_stay_up expression: Link_status == 1 ``` ```math -u^{f}_{\xi,t,l} = 1 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \mathrm{hold}^{f}_{\xi,t,l} \wedge \mathrm{on}^{f}_{t,l} +u^{f}_{\xi,t,l} = 1 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com}}_{t,l} \wedge \mathrm{hold}^{f}_{\xi,t,l} ``` ### `Link-com-status-min_down_time_must_stay_up` @@ -6631,12 +6646,12 @@ Link_com_status_must_stay_down: `Link-com-status-min_down_time_must_stay_up` — a link still serving the down time it brought in stays off; PyPSA names the row `_must_stay_up` dims: [scenario, snapshot, link] - where: Link_committable AND Link_must_stay_down AND Link_active + where: Link_committed AND Link_must_stay_down expression: Link_status == 0 ``` ```math -u^{f}_{\xi,t,l} = 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \mathrm{rest}^{f}_{\xi,t,l} \wedge \mathrm{on}^{f}_{t,l} +u^{f}_{\xi,t,l} = 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com}}_{t,l} \wedge \mathrm{rest}^{f}_{\xi,t,l} ``` ### `Link-p-ramp_limit_up-run-bigM` @@ -6650,11 +6665,7 @@ Link_p_ramp_limit_up_run_big_m: raises flow no faster than its limit of the chosen build; the big M releases the row in the snapshot it turns on dims: [scenario, snapshot, link] - where: >- - Link_committable AND Link_p_nom_extendable AND NOT (Link_p_nom_mod > 0) - AND (Link_ramp_limit_up OR Link_ramp_limit_start_up) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Link_status_initial == 0 OR Link_p_init))) - AND Link_active + where: Link_com_ext AND (Link_ramp_limit_up OR Link_ramp_limit_start_up) AND Link_ramps_from_previous expression: >- Link_p - Link_previous_p <= Link_ramp_up_rate * Link_p_nom_ext @@ -6662,7 +6673,7 @@ Link_p_ramp_limit_up_run_big_m: ``` ```math -f_{\xi,t,l} - \overleftarrow{f}_{\xi,t,l} \le \widetilde{\mathrm{ru}}^{f}_{\xi,t,l} \cdot F_{l} + \mathrm{M}^{f}_{\xi,l} - \mathrm{M}^{f}_{\xi,l} \cdot \overleftarrow{u}^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \mathrm{ext}^{f}_{l} \wedge \neg \left( \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) \wedge \left( \mathrm{ru}^{f}_{\xi,t,l} \text{ is defined} \vee \mathrm{ru}^{f,\mathrm{up}}_{\xi,l} \text{ is defined} \right) \wedge \left( \mathrm{pos}_{\mathrm{snapshot\_period}(t)}(t) > 0 \vee \mathrm{pos}(t) = 0 \wedge \left( \mathrm{u}^{f,0}_{\xi,l} = 0 \vee \mathrm{f}^{0}_{\xi,l} \text{ is defined} \right) \right) \wedge \mathrm{on}^{f}_{t,l} +f_{\xi,t,l} - \overleftarrow{f}_{\xi,t,l} \le \widetilde{\mathrm{ru}}^{f}_{\xi,t,l} \cdot F_{l} + \mathrm{M}^{f}_{\xi,l} - \mathrm{M}^{f}_{\xi,l} \cdot \overleftarrow{u}^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com,ext}}_{t,l} \wedge \left( \mathrm{ru}^{f}_{\xi,t,l} \text{ is defined} \vee \mathrm{ru}^{f,\mathrm{up}}_{\xi,l} \text{ is defined} \right) \wedge \mathrm{prev}^{f}_{\xi,t,l} ``` ### `Link-p-ramp_limit_up-start-bigM` @@ -6676,11 +6687,7 @@ Link_p_ramp_limit_up_start_big_m: committed extendable link ramps no further than its start-up ramp of the chosen build; the big M releases the row everywhere else dims: [scenario, snapshot, link] - where: >- - Link_committable AND Link_p_nom_extendable AND NOT (Link_p_nom_mod > 0) - AND (Link_ramp_limit_up OR Link_ramp_limit_start_up) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Link_status_initial == 0 OR Link_p_init))) - AND Link_active + where: Link_com_ext AND (Link_ramp_limit_up OR Link_ramp_limit_start_up) AND Link_ramps_from_previous expression: >- Link_p - Link_previous_p <= Link_start_up_rate * Link_p_nom_ext @@ -6688,7 +6695,7 @@ Link_p_ramp_limit_up_start_big_m: ``` ```math -f_{\xi,t,l} - \overleftarrow{f}_{\xi,t,l} \le \widetilde{\mathrm{ru}}^{f,\mathrm{up}}_{\xi,l} \cdot F_{l} + \mathrm{M}^{f}_{\xi,l} - \mathrm{M}^{f}_{\xi,l} \cdot \mathit{up}^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \mathrm{ext}^{f}_{l} \wedge \neg \left( \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) \wedge \left( \mathrm{ru}^{f}_{\xi,t,l} \text{ is defined} \vee \mathrm{ru}^{f,\mathrm{up}}_{\xi,l} \text{ is defined} \right) \wedge \left( \mathrm{pos}_{\mathrm{snapshot\_period}(t)}(t) > 0 \vee \mathrm{pos}(t) = 0 \wedge \left( \mathrm{u}^{f,0}_{\xi,l} = 0 \vee \mathrm{f}^{0}_{\xi,l} \text{ is defined} \right) \right) \wedge \mathrm{on}^{f}_{t,l} +f_{\xi,t,l} - \overleftarrow{f}_{\xi,t,l} \le \widetilde{\mathrm{ru}}^{f,\mathrm{up}}_{\xi,l} \cdot F_{l} + \mathrm{M}^{f}_{\xi,l} - \mathrm{M}^{f}_{\xi,l} \cdot \mathit{up}^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com,ext}}_{t,l} \wedge \left( \mathrm{ru}^{f}_{\xi,t,l} \text{ is defined} \vee \mathrm{ru}^{f,\mathrm{up}}_{\xi,l} \text{ is defined} \right) \wedge \mathrm{prev}^{f}_{\xi,t,l} ``` ### `Link-p-ramp_limit_down-run-bigM` @@ -6702,11 +6709,7 @@ Link_p_ramp_limit_down_run_big_m: lowers flow no faster than its limit of the chosen build; the big M releases the row in the snapshot it turns off dims: [scenario, snapshot, link] - where: >- - Link_committable AND Link_p_nom_extendable AND NOT (Link_p_nom_mod > 0) - AND (Link_ramp_limit_down OR Link_ramp_limit_shut_down) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Link_status_initial == 0 OR Link_p_init))) - AND Link_active + where: Link_com_ext AND (Link_ramp_limit_down OR Link_ramp_limit_shut_down) AND Link_ramps_from_previous expression: >- Link_previous_p - Link_p <= Link_ramp_down_rate * Link_p_nom_ext @@ -6714,7 +6717,7 @@ Link_p_ramp_limit_down_run_big_m: ``` ```math -\overleftarrow{f}_{\xi,t,l} - f_{\xi,t,l} \le \widetilde{\mathrm{rd}}^{f}_{\xi,t,l} \cdot F_{l} + \mathrm{M}^{f}_{\xi,l} - \mathrm{M}^{f}_{\xi,l} \cdot u^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \mathrm{ext}^{f}_{l} \wedge \neg \left( \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) \wedge \left( \mathrm{rd}^{f}_{\xi,t,l} \text{ is defined} \vee \mathrm{rd}^{f,\mathrm{dn}}_{\xi,l} \text{ is defined} \right) \wedge \left( \mathrm{pos}_{\mathrm{snapshot\_period}(t)}(t) > 0 \vee \mathrm{pos}(t) = 0 \wedge \left( \mathrm{u}^{f,0}_{\xi,l} = 0 \vee \mathrm{f}^{0}_{\xi,l} \text{ is defined} \right) \right) \wedge \mathrm{on}^{f}_{t,l} +\overleftarrow{f}_{\xi,t,l} - f_{\xi,t,l} \le \widetilde{\mathrm{rd}}^{f}_{\xi,t,l} \cdot F_{l} + \mathrm{M}^{f}_{\xi,l} - \mathrm{M}^{f}_{\xi,l} \cdot u^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com,ext}}_{t,l} \wedge \left( \mathrm{rd}^{f}_{\xi,t,l} \text{ is defined} \vee \mathrm{rd}^{f,\mathrm{dn}}_{\xi,l} \text{ is defined} \right) \wedge \mathrm{prev}^{f}_{\xi,t,l} ``` ### `Link-p-ramp_limit_down-shut-bigM` @@ -6728,11 +6731,7 @@ Link_p_ramp_limit_down_shut_big_m: a committed extendable link ramps no further than its shut-down ramp of the chosen build; the big M releases the row everywhere else dims: [scenario, snapshot, link] - where: >- - Link_committable AND Link_p_nom_extendable AND NOT (Link_p_nom_mod > 0) - AND (Link_ramp_limit_down OR Link_ramp_limit_shut_down) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Link_status_initial == 0 OR Link_p_init))) - AND Link_active + where: Link_com_ext AND (Link_ramp_limit_down OR Link_ramp_limit_shut_down) AND Link_ramps_from_previous expression: >- Link_previous_p - Link_p <= Link_shut_down_rate * Link_p_nom_ext @@ -6740,7 +6739,7 @@ Link_p_ramp_limit_down_shut_big_m: ``` ```math -\overleftarrow{f}_{\xi,t,l} - f_{\xi,t,l} \le \widetilde{\mathrm{rd}}^{f,\mathrm{dn}}_{\xi,l} \cdot F_{l} + \mathrm{M}^{f}_{\xi,l} - \mathrm{M}^{f}_{\xi,l} \cdot \mathit{dn}^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \mathrm{ext}^{f}_{l} \wedge \neg \left( \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) \wedge \left( \mathrm{rd}^{f}_{\xi,t,l} \text{ is defined} \vee \mathrm{rd}^{f,\mathrm{dn}}_{\xi,l} \text{ is defined} \right) \wedge \left( \mathrm{pos}_{\mathrm{snapshot\_period}(t)}(t) > 0 \vee \mathrm{pos}(t) = 0 \wedge \left( \mathrm{u}^{f,0}_{\xi,l} = 0 \vee \mathrm{f}^{0}_{\xi,l} \text{ is defined} \right) \right) \wedge \mathrm{on}^{f}_{t,l} +\overleftarrow{f}_{\xi,t,l} - f_{\xi,t,l} \le \widetilde{\mathrm{rd}}^{f,\mathrm{dn}}_{\xi,l} \cdot F_{l} + \mathrm{M}^{f}_{\xi,l} - \mathrm{M}^{f}_{\xi,l} \cdot \mathit{dn}^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com,ext}}_{t,l} \wedge \left( \mathrm{rd}^{f}_{\xi,t,l} \text{ is defined} \vee \mathrm{rd}^{f,\mathrm{dn}}_{\xi,l} \text{ is defined} \right) \wedge \mathrm{prev}^{f}_{\xi,t,l} ``` ### `Link-p_nom_modularity` @@ -6769,12 +6768,12 @@ Link_com_ext_p_upper_cap: `Link-com-ext-p-upper-cap` — a committed extendable link flows at most what is available of the chosen build, whatever its status dims: [scenario, snapshot, link] - where: Link_committable AND Link_p_nom_extendable AND NOT (Link_p_nom_mod > 0) AND Link_active + where: Link_com_ext expression: Link_p <= Link_p_max_pu * (Link_p_nom_ext - Link_maintenance_pu * Link_maintenance_capacity) ``` ```math -f_{\xi,t,l} \le \overline{\mathrm{f}}_{\xi,t,l} \cdot \left( F_{l} - \gamma^{f}_{\xi,l} \cdot \mu^{f,\mathrm{nom}}_{\xi,t,l} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \mathrm{ext}^{f}_{l} \wedge \neg \left( \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) \wedge \mathrm{on}^{f}_{t,l} +f_{\xi,t,l} \le \overline{\mathrm{f}}_{\xi,t,l} \cdot \left( F_{l} - \gamma^{f}_{\xi,l} \cdot \mu^{f,\mathrm{nom}}_{\xi,t,l} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com,ext}}_{t,l} ``` ### `Link-com-ext-p-upper-bigM` @@ -6785,12 +6784,12 @@ f_{\xi,t,l} \le \overline{\mathrm{f}}_{\xi,t,l} \cdot \left( F_{l} - \gamma^{f}_ Link_com_ext_p_upper_big_m: description: "`Link-com-ext-p-upper-bigM` — off, a link flows nothing; on, the big M is no bound" dims: [scenario, snapshot, link] - where: Link_committable AND Link_p_nom_extendable AND NOT (Link_p_nom_mod > 0) AND Link_active + where: Link_com_ext expression: Link_p <= Link_big_m * Link_status ``` ```math -f_{\xi,t,l} \le \mathrm{M}^{f}_{\xi,l} \cdot u^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \mathrm{ext}^{f}_{l} \wedge \neg \left( \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) \wedge \mathrm{on}^{f}_{t,l} +f_{\xi,t,l} \le \mathrm{M}^{f}_{\xi,l} \cdot u^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com,ext}}_{t,l} ``` ### `Link-com-ext-p-lower` @@ -6803,7 +6802,7 @@ Link_com_ext_p_lower: `Link-com-ext-p-lower` — a committed extendable link flows at least its minimum of the chosen build; off, the big M releases the row dims: [scenario, snapshot, link] - where: Link_committable AND Link_p_nom_extendable AND NOT (Link_p_nom_mod > 0) AND Link_active + where: Link_com_ext expression: >- Link_p >= Link_p_min_pu * (Link_p_nom_ext - Link_maintenance_pu * Link_maintenance_capacity) @@ -6811,7 +6810,7 @@ Link_com_ext_p_lower: ``` ```math -f_{\xi,t,l} \ge \underline{\mathrm{f}}_{\xi,t,l} \cdot \left( F_{l} - \gamma^{f}_{\xi,l} \cdot \mu^{f,\mathrm{nom}}_{\xi,t,l} \right) + \mathrm{M}^{f}_{\xi,l} \cdot u^{f}_{\xi,t,l} - \mathrm{M}^{f}_{\xi,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \mathrm{ext}^{f}_{l} \wedge \neg \left( \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) \wedge \mathrm{on}^{f}_{t,l} +f_{\xi,t,l} \ge \underline{\mathrm{f}}_{\xi,t,l} \cdot \left( F_{l} - \gamma^{f}_{\xi,l} \cdot \mu^{f,\mathrm{nom}}_{\xi,t,l} \right) + \mathrm{M}^{f}_{\xi,l} \cdot u^{f}_{\xi,t,l} - \mathrm{M}^{f}_{\xi,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com,ext}}_{t,l} ``` ### `Link-com-ext-p-lower-nonneg` @@ -6825,14 +6824,12 @@ Link_com_ext_p_lower_nonneg: negative, flow is also plainly non-negative, a row the big-M lower cannot assert while the link is off dims: [scenario, snapshot, link] - where: >- - Link_committable AND Link_p_nom_extendable - AND Link_p_min_pu_nonneg AND NOT (Link_p_nom_mod > 0) AND Link_active + where: Link_com_ext AND Link_p_min_pu_nonneg expression: Link_p >= 0 ``` ```math -f_{\xi,t,l} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \mathrm{ext}^{f}_{l} \wedge \mathrm{nonneg}^{f}_{l} \wedge \neg \left( \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) \wedge \mathrm{on}^{f}_{t,l} +f_{\xi,t,l} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com,ext}}_{t,l} \wedge \mathrm{nonneg}^{f}_{l} ``` ### `Link-com-mod-p-lower` @@ -6845,12 +6842,12 @@ Link_com_mod_p_lower: `Link-com-mod-p-lower` — a committed modular link flows at least its minimum of one module, whether the build is fixed or a decision dims: [scenario, snapshot, link] - where: Link_committable AND Link_p_nom_mod > 0 AND Link_active + where: Link_committed AND Link_p_nom_mod > 0 expression: Link_p >= Link_p_min_pu * Link_p_nom_mod * (Link_status - Link_maintenance_pu * Link_maintenance_status) ``` ```math -f_{\xi,t,l} \ge \underline{\mathrm{f}}_{\xi,t,l} \cdot \mathrm{f}^{\mathrm{mod}}_{l} \cdot \left( u^{f}_{\xi,t,l} - \gamma^{f}_{\xi,l} \cdot \mu^{f,u}_{\xi,t,l} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 \wedge \mathrm{on}^{f}_{t,l} +f_{\xi,t,l} \ge \underline{\mathrm{f}}_{\xi,t,l} \cdot \mathrm{f}^{\mathrm{mod}}_{l} \cdot \left( u^{f}_{\xi,t,l} - \gamma^{f}_{\xi,l} \cdot \mu^{f,u}_{\xi,t,l} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com}}_{t,l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 ``` ### `Link-com-mod-p-upper` @@ -6863,12 +6860,12 @@ Link_com_mod_p_upper: `Link-com-mod-p-upper` — a committed modular link flows at most one module's share, whether the build is fixed or a decision dims: [scenario, snapshot, link] - where: Link_committable AND Link_p_nom_mod > 0 AND Link_active + where: Link_committed AND Link_p_nom_mod > 0 expression: Link_p <= Link_p_max_pu * Link_p_nom_mod * (Link_status - Link_maintenance_pu * Link_maintenance_status) ``` ```math -f_{\xi,t,l} \le \overline{\mathrm{f}}_{\xi,t,l} \cdot \mathrm{f}^{\mathrm{mod}}_{l} \cdot \left( u^{f}_{\xi,t,l} - \gamma^{f}_{\xi,l} \cdot \mu^{f,u}_{\xi,t,l} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 \wedge \mathrm{on}^{f}_{t,l} +f_{\xi,t,l} \le \overline{\mathrm{f}}_{\xi,t,l} \cdot \mathrm{f}^{\mathrm{mod}}_{l} \cdot \left( u^{f}_{\xi,t,l} - \gamma^{f}_{\xi,l} \cdot \mu^{f,u}_{\xi,t,l} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com}}_{t,l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 ``` ### `Link-status-p-fixed-upper` @@ -6882,12 +6879,12 @@ Link_status_p_fixed_upper: 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 dims: [scenario, snapshot, link] - where: Link_committable AND NOT (Link_p_nom_extendable AND Link_p_nom_mod > 0) AND Link_active + where: Link_committed AND NOT (Link_p_nom_extendable AND Link_p_nom_mod > 0) expression: Link_status <= Link_modules_installed ``` ```math -u^{f}_{\xi,t,l} \le \mathrm{N}^{f,\mathrm{fix}}_{\xi,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \neg \left( \mathrm{ext}^{f}_{l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) \wedge \mathrm{on}^{f}_{t,l} +u^{f}_{\xi,t,l} \le \mathrm{N}^{f,\mathrm{fix}}_{\xi,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com}}_{t,l} \wedge \neg \left( \mathrm{ext}^{f}_{l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) ``` ### `Link-start_up-p-fixed-upper` @@ -6901,12 +6898,12 @@ Link_start_up_p_fixed_upper: 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 dims: [scenario, snapshot, link] - where: Link_committable AND NOT (Link_p_nom_extendable AND Link_p_nom_mod > 0) AND Link_active + where: Link_committed AND NOT (Link_p_nom_extendable AND Link_p_nom_mod > 0) expression: Link_start_up <= Link_modules_installed ``` ```math -\mathit{up}^{f}_{\xi,t,l} \le \mathrm{N}^{f,\mathrm{fix}}_{\xi,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \neg \left( \mathrm{ext}^{f}_{l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) \wedge \mathrm{on}^{f}_{t,l} +\mathit{up}^{f}_{\xi,t,l} \le \mathrm{N}^{f,\mathrm{fix}}_{\xi,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com}}_{t,l} \wedge \neg \left( \mathrm{ext}^{f}_{l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) ``` ### `Link-shut_down-p-fixed-upper` @@ -6920,12 +6917,12 @@ Link_shut_down_p_fixed_upper: 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 dims: [scenario, snapshot, link] - where: Link_committable AND NOT (Link_p_nom_extendable AND Link_p_nom_mod > 0) AND Link_active + where: Link_committed AND NOT (Link_p_nom_extendable AND Link_p_nom_mod > 0) expression: Link_shut_down <= Link_modules_installed ``` ```math -\mathit{dn}^{f}_{\xi,t,l} \le \mathrm{N}^{f,\mathrm{fix}}_{\xi,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \neg \left( \mathrm{ext}^{f}_{l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) \wedge \mathrm{on}^{f}_{t,l} +\mathit{dn}^{f}_{\xi,t,l} \le \mathrm{N}^{f,\mathrm{fix}}_{\xi,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com}}_{t,l} \wedge \neg \left( \mathrm{ext}^{f}_{l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) ``` ### `Link-status-p_nom-variable-upper` @@ -6936,12 +6933,12 @@ Link_shut_down_p_fixed_upper: Link_status_p_nom_variable_upper: description: "`Link-status-p_nom-variable-upper` — a modular link is on only where a module is built" dims: [scenario, snapshot, link] - where: Link_committable AND Link_p_nom_extendable AND Link_p_nom_mod > 0 AND Link_active + where: Link_committed AND Link_p_nom_extendable AND Link_p_nom_mod > 0 expression: Link_status <= Link_n_mod ``` ```math -u^{f}_{\xi,t,l} \le N^{f}_{l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \mathrm{ext}^{f}_{l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 \wedge \mathrm{on}^{f}_{t,l} +u^{f}_{\xi,t,l} \le N^{f}_{l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com}}_{t,l} \wedge \mathrm{ext}^{f}_{l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 ``` ### `Link-start_up-p_nom-variable-upper` @@ -6952,12 +6949,12 @@ u^{f}_{\xi,t,l} \le N^{f}_{l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ Link_start_up_p_nom_variable_upper: description: "`Link-start_up-p_nom-variable-upper` — a modular link starts only where a module is built" dims: [scenario, snapshot, link] - where: Link_committable AND Link_p_nom_extendable AND Link_p_nom_mod > 0 AND Link_active + where: Link_committed AND Link_p_nom_extendable AND Link_p_nom_mod > 0 expression: Link_start_up <= Link_n_mod ``` ```math -\mathit{up}^{f}_{\xi,t,l} \le N^{f}_{l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \mathrm{ext}^{f}_{l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 \wedge \mathrm{on}^{f}_{t,l} +\mathit{up}^{f}_{\xi,t,l} \le N^{f}_{l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com}}_{t,l} \wedge \mathrm{ext}^{f}_{l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 ``` ### `Link-shut_down-p_nom-variable-upper` @@ -6968,12 +6965,12 @@ Link_start_up_p_nom_variable_upper: Link_shut_down_p_nom_variable_upper: description: "`Link-shut_down-p_nom-variable-upper` — a modular link stops only where a module is built" dims: [scenario, snapshot, link] - where: Link_committable AND Link_p_nom_extendable AND Link_p_nom_mod > 0 AND Link_active + where: Link_committed AND Link_p_nom_extendable AND Link_p_nom_mod > 0 expression: Link_shut_down <= Link_n_mod ``` ```math -\mathit{dn}^{f}_{\xi,t,l} \le N^{f}_{l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \mathrm{ext}^{f}_{l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 \wedge \mathrm{on}^{f}_{t,l} +\mathit{dn}^{f}_{\xi,t,l} \le N^{f}_{l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com}}_{t,l} \wedge \mathrm{ext}^{f}_{l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 ``` ### `Link-maint-event-count` @@ -7037,12 +7034,12 @@ Link_maintcap_upper: `Link-maintcap_upper` — the build taken off is at most the chosen build in maintenance, and at most the build less its floor out of it dims: [scenario, snapshot, link] - where: Link_maintainable AND Link_p_nom_extendable AND NOT (Link_committable AND Link_p_nom_mod > 0) AND Link_active + where: Link_maint_ext expression: Link_maintenance_capacity <= Link_p_nom_ext - Link_p_nom_min * (1 - Link_maintenance) ``` ```math -\mu^{f,\mathrm{nom}}_{\xi,t,l} \le F_{l} - \underline{\mathrm{f}}^{\mathrm{nom}}_{\xi,l} \cdot \left( 1 - \mu^{f}_{\xi,t,l} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{mnt}^{f}_{l} \wedge \mathrm{ext}^{f}_{l} \wedge \neg \left( \mathrm{com}^{f}_{l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) \wedge \mathrm{on}^{f}_{t,l} +\mu^{f,\mathrm{nom}}_{\xi,t,l} \le F_{l} - \underline{\mathrm{f}}^{\mathrm{nom}}_{\xi,l} \cdot \left( 1 - \mu^{f}_{\xi,t,l} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{mnt,ext}}_{t,l} ``` ### `Link-maintcap_upper_nommax` @@ -7053,12 +7050,12 @@ Link_maintcap_upper: Link_maintcap_upper_nommax: description: "`Link-maintcap_upper_nommax` — out of maintenance, no build is taken off" dims: [scenario, snapshot, link] - where: Link_maintainable AND Link_p_nom_extendable AND NOT (Link_committable AND Link_p_nom_mod > 0) AND Link_active + where: Link_maint_ext expression: Link_maintenance_capacity <= Link_p_nom_max * Link_maintenance ``` ```math -\mu^{f,\mathrm{nom}}_{\xi,t,l} \le \overline{\mathrm{f}}^{\mathrm{nom}}_{\xi,l} \cdot \mu^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{mnt}^{f}_{l} \wedge \mathrm{ext}^{f}_{l} \wedge \neg \left( \mathrm{com}^{f}_{l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) \wedge \mathrm{on}^{f}_{t,l} +\mu^{f,\mathrm{nom}}_{\xi,t,l} \le \overline{\mathrm{f}}^{\mathrm{nom}}_{\xi,l} \cdot \mu^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{mnt,ext}}_{t,l} ``` ### `Link-maintcap_lower_nommax` @@ -7069,12 +7066,12 @@ Link_maintcap_upper_nommax: Link_maintcap_lower_nommax: description: "`Link-maintcap_lower_nommax` — in maintenance, the whole chosen build is taken off" dims: [scenario, snapshot, link] - where: Link_maintainable AND Link_p_nom_extendable AND NOT (Link_committable AND Link_p_nom_mod > 0) AND Link_active + where: Link_maint_ext expression: Link_maintenance_capacity >= Link_p_nom_ext - Link_p_nom_max * (1 - Link_maintenance) ``` ```math -\mu^{f,\mathrm{nom}}_{\xi,t,l} \ge F_{l} - \overline{\mathrm{f}}^{\mathrm{nom}}_{\xi,l} \cdot \left( 1 - \mu^{f}_{\xi,t,l} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{mnt}^{f}_{l} \wedge \mathrm{ext}^{f}_{l} \wedge \neg \left( \mathrm{com}^{f}_{l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) \wedge \mathrm{on}^{f}_{t,l} +\mu^{f,\mathrm{nom}}_{\xi,t,l} \ge F_{l} - \overline{\mathrm{f}}^{\mathrm{nom}}_{\xi,l} \cdot \left( 1 - \mu^{f}_{\xi,t,l} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{mnt,ext}}_{t,l} ``` ### `Link-maintcap_lower_nommin` @@ -7085,12 +7082,12 @@ Link_maintcap_lower_nommax: Link_maintcap_lower_nommin: description: "`Link-maintcap_lower_nommin` — in maintenance, at least the floor of the build is taken off" dims: [scenario, snapshot, link] - where: Link_maintainable AND Link_p_nom_extendable AND NOT (Link_committable AND Link_p_nom_mod > 0) AND Link_active AND Link_p_nom_min > 0 + where: Link_maint_ext AND Link_p_nom_min > 0 expression: Link_maintenance_capacity >= Link_p_nom_min * Link_maintenance ``` ```math -\mu^{f,\mathrm{nom}}_{\xi,t,l} \ge \underline{\mathrm{f}}^{\mathrm{nom}}_{\xi,l} \cdot \mu^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{mnt}^{f}_{l} \wedge \mathrm{ext}^{f}_{l} \wedge \neg \left( \mathrm{com}^{f}_{l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) \wedge \mathrm{on}^{f}_{t,l} \wedge \underline{\mathrm{f}}^{\mathrm{nom}}_{\xi,l} > 0 +\mu^{f,\mathrm{nom}}_{\xi,t,l} \ge \underline{\mathrm{f}}^{\mathrm{nom}}_{\xi,l} \cdot \mu^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{mnt,ext}}_{t,l} \wedge \underline{\mathrm{f}}^{\mathrm{nom}}_{\xi,l} > 0 ``` ### `Link-maint-status-le-status` @@ -7101,12 +7098,12 @@ Link_maintcap_lower_nommin: Link_maint_status_le_status: description: "`Link-maint-status-le-status` — the status in maintenance is at most the status" dims: [scenario, snapshot, link] - where: Link_maintainable AND Link_committable AND NOT Link_p_nom_extendable AND Link_active + where: Link_maintainable AND Link_committed AND NOT Link_p_nom_extendable expression: Link_maintenance_status <= Link_status ``` ```math -\mu^{f,u}_{\xi,t,l} \le u^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{mnt}^{f}_{l} \wedge \mathrm{com}^{f}_{l} \wedge \neg \mathrm{ext}^{f}_{l} \wedge \mathrm{on}^{f}_{t,l} +\mu^{f,u}_{\xi,t,l} \le u^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{mnt}^{f}_{l} \wedge \mathrm{on}^{f,\mathrm{com}}_{t,l} \wedge \neg \mathrm{ext}^{f}_{l} ``` ### `Link-maint-status-le-maint` @@ -7117,12 +7114,12 @@ Link_maint_status_le_status: Link_maint_status_le_maint: description: "`Link-maint-status-le-maint` — out of maintenance, the status in maintenance is zero" dims: [scenario, snapshot, link] - where: Link_maintainable AND Link_committable AND NOT Link_p_nom_extendable AND Link_active + where: Link_maintainable AND Link_committed AND NOT Link_p_nom_extendable expression: Link_maintenance_status <= Link_maintenance ``` ```math -\mu^{f,u}_{\xi,t,l} \le \mu^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{mnt}^{f}_{l} \wedge \mathrm{com}^{f}_{l} \wedge \neg \mathrm{ext}^{f}_{l} \wedge \mathrm{on}^{f}_{t,l} +\mu^{f,u}_{\xi,t,l} \le \mu^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{mnt}^{f}_{l} \wedge \mathrm{on}^{f,\mathrm{com}}_{t,l} \wedge \neg \mathrm{ext}^{f}_{l} ``` ### `Link-maint-status-lb` @@ -7133,12 +7130,12 @@ Link_maint_status_le_maint: Link_maint_status_lb: description: "`Link-maint-status-lb` — on and in maintenance, the status in maintenance is one" dims: [scenario, snapshot, link] - where: Link_maintainable AND Link_committable AND NOT Link_p_nom_extendable AND Link_active + where: Link_maintainable AND Link_committed AND NOT Link_p_nom_extendable expression: Link_maintenance_status >= Link_status + Link_maintenance - 1 ``` ```math -\mu^{f,u}_{\xi,t,l} \ge u^{f}_{\xi,t,l} + \mu^{f}_{\xi,t,l} - 1 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{mnt}^{f}_{l} \wedge \mathrm{com}^{f}_{l} \wedge \neg \mathrm{ext}^{f}_{l} \wedge \mathrm{on}^{f}_{t,l} +\mu^{f,u}_{\xi,t,l} \ge u^{f}_{\xi,t,l} + \mu^{f}_{\xi,t,l} - 1 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{mnt}^{f}_{l} \wedge \mathrm{on}^{f,\mathrm{com}}_{t,l} \wedge \neg \mathrm{ext}^{f}_{l} ``` ### `Link-maint-modstatus-le-status` @@ -7149,12 +7146,12 @@ Link_maint_status_lb: Link_maint_modstatus_le_status: description: "`Link-maint-modstatus-le-status` — the modules on in maintenance are at most the modules on" dims: [scenario, snapshot, link] - where: Link_maintainable AND Link_committable AND Link_p_nom_mod > 0 AND Link_active + where: Link_maintainable AND Link_committed AND Link_p_nom_mod > 0 expression: Link_maintenance_status <= Link_status ``` ```math -\mu^{f,u}_{\xi,t,l} \le u^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{mnt}^{f}_{l} \wedge \mathrm{com}^{f}_{l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 \wedge \mathrm{on}^{f}_{t,l} +\mu^{f,u}_{\xi,t,l} \le u^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{mnt}^{f}_{l} \wedge \mathrm{on}^{f,\mathrm{com}}_{t,l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 ``` ### `Link-maint-modstatus-le-maint` @@ -7167,12 +7164,12 @@ Link_maint_modstatus_le_maint: `Link-maint-modstatus-le-maint` — out of maintenance, no module is on in maintenance; in it, at most the modules the build cap holds dims: [scenario, snapshot, link] - where: Link_maintainable AND Link_committable AND Link_p_nom_mod > 0 AND Link_active + where: Link_maintainable AND Link_committed AND Link_p_nom_mod > 0 expression: Link_maintenance_status <= Link_p_nom_max / Link_p_nom_mod * Link_maintenance ``` ```math -\mu^{f,u}_{\xi,t,l} \le \frac{\overline{\mathrm{f}}^{\mathrm{nom}}_{\xi,l}}{\mathrm{f}^{\mathrm{mod}}_{l}} \cdot \mu^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{mnt}^{f}_{l} \wedge \mathrm{com}^{f}_{l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 \wedge \mathrm{on}^{f}_{t,l} +\mu^{f,u}_{\xi,t,l} \le \frac{\overline{\mathrm{f}}^{\mathrm{nom}}_{\xi,l}}{\mathrm{f}^{\mathrm{mod}}_{l}} \cdot \mu^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{mnt}^{f}_{l} \wedge \mathrm{on}^{f,\mathrm{com}}_{t,l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 ``` ### `Link-maint-modstatus-lb` @@ -7183,12 +7180,12 @@ Link_maint_modstatus_le_maint: Link_maint_modstatus_lb: description: "`Link-maint-modstatus-lb` — in maintenance, every module on is on in maintenance" dims: [scenario, snapshot, link] - where: Link_maintainable AND Link_committable AND Link_p_nom_mod > 0 AND Link_active + where: Link_maintainable AND Link_committed AND Link_p_nom_mod > 0 expression: Link_maintenance_status >= Link_status - Link_p_nom_max / Link_p_nom_mod * (1 - Link_maintenance) ``` ```math -\mu^{f,u}_{\xi,t,l} \ge u^{f}_{\xi,t,l} - \frac{\overline{\mathrm{f}}^{\mathrm{nom}}_{\xi,l}}{\mathrm{f}^{\mathrm{mod}}_{l}} \cdot \left( 1 - \mu^{f}_{\xi,t,l} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{mnt}^{f}_{l} \wedge \mathrm{com}^{f}_{l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 \wedge \mathrm{on}^{f}_{t,l} +\mu^{f,u}_{\xi,t,l} \ge u^{f}_{\xi,t,l} - \frac{\overline{\mathrm{f}}^{\mathrm{nom}}_{\xi,l}}{\mathrm{f}^{\mathrm{mod}}_{l}} \cdot \left( 1 - \mu^{f}_{\xi,t,l} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{mnt}^{f}_{l} \wedge \mathrm{on}^{f,\mathrm{com}}_{t,l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 ``` ### `Process-com-p-lower` @@ -7199,12 +7196,12 @@ Link_maint_modstatus_lb: Process_com_p_lower: description: "`Process-com-p-lower` — a committed process runs at least its minimum; off, at least nothing" dims: [scenario, snapshot, process] - where: Process_committable AND not Process_p_nom_extendable AND Process_active + where: Process_committed AND NOT Process_p_nom_extendable expression: Process_p >= Process_p_min_pu * Process_p_nom * (Process_status - Process_maintenance_pu * Process_maintenance_status) ``` ```math -z_{\xi,t,j} \ge \underline{\mathrm{z}}_{\xi,t,j} \cdot \mathrm{z}^{\mathrm{nom}}_{\xi,j} \cdot \left( u^{z}_{\xi,t,j} - \gamma^{z}_{\xi,j} \cdot \mu^{z,u}_{\xi,t,j} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \neg \mathrm{ext}^{z}_{j} \wedge \mathrm{on}^{z}_{t,j} +z_{\xi,t,j} \ge \underline{\mathrm{z}}_{\xi,t,j} \cdot \mathrm{z}^{\mathrm{nom}}_{\xi,j} \cdot \left( u^{z}_{\xi,t,j} - \gamma^{z}_{\xi,j} \cdot \mu^{z,u}_{\xi,t,j} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com}}_{t,j} \wedge \neg \mathrm{ext}^{z}_{j} ``` ### `Process-com-p-upper` @@ -7215,12 +7212,12 @@ z_{\xi,t,j} \ge \underline{\mathrm{z}}_{\xi,t,j} \cdot \mathrm{z}^{\mathrm{nom}} Process_com_p_upper: description: "`Process-com-p-upper` — a committed process runs at most what is available; off, at most nothing" dims: [scenario, snapshot, process] - where: Process_committable AND not Process_p_nom_extendable AND Process_active + where: Process_committed AND NOT Process_p_nom_extendable expression: Process_p <= Process_p_max_pu * Process_p_nom * (Process_status - Process_maintenance_pu * Process_maintenance_status) ``` ```math -z_{\xi,t,j} \le \overline{\mathrm{z}}_{\xi,t,j} \cdot \mathrm{z}^{\mathrm{nom}}_{\xi,j} \cdot \left( u^{z}_{\xi,t,j} - \gamma^{z}_{\xi,j} \cdot \mu^{z,u}_{\xi,t,j} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \neg \mathrm{ext}^{z}_{j} \wedge \mathrm{on}^{z}_{t,j} +z_{\xi,t,j} \le \overline{\mathrm{z}}_{\xi,t,j} \cdot \mathrm{z}^{\mathrm{nom}}_{\xi,j} \cdot \left( u^{z}_{\xi,t,j} - \gamma^{z}_{\xi,j} \cdot \mu^{z,u}_{\xi,t,j} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com}}_{t,j} \wedge \neg \mathrm{ext}^{z}_{j} ``` ### `Process-com-transition-start-up` @@ -7231,12 +7228,12 @@ z_{\xi,t,j} \le \overline{\mathrm{z}}_{\xi,t,j} \cdot \mathrm{z}^{\mathrm{nom}}_ Process_com_transition_start_up: description: "`Process-com-transition-start-up` — turning on is a start, counted against the state the process carried into the snapshot" dims: [scenario, snapshot, process] - where: Process_committable AND Process_active + where: Process_committed expression: Process_start_up >= Process_status - Process_previous_status ``` ```math -\mathit{up}^{z}_{\xi,t,j} \ge u^{z}_{\xi,t,j} - \overleftarrow{u}^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \mathrm{on}^{z}_{t,j} +\mathit{up}^{z}_{\xi,t,j} \ge u^{z}_{\xi,t,j} - \overleftarrow{u}^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com}}_{t,j} ``` ### `Process-com-transition-shut-down` @@ -7247,12 +7244,12 @@ Process_com_transition_start_up: Process_com_transition_shut_down: description: "`Process-com-transition-shut-down` — turning off is a stop, counted against the state the process carried into the snapshot" dims: [scenario, snapshot, process] - where: Process_committable AND Process_active + where: Process_committed expression: Process_shut_down >= Process_previous_status - Process_status ``` ```math -\mathit{dn}^{z}_{\xi,t,j} \ge \overleftarrow{u}^{z}_{\xi,t,j} - u^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \mathrm{on}^{z}_{t,j} +\mathit{dn}^{z}_{\xi,t,j} \ge \overleftarrow{u}^{z}_{\xi,t,j} - u^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com}}_{t,j} ``` ### `Process-com-up-time` @@ -7266,12 +7263,12 @@ Process_com_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 dims: [scenario, snapshot, process] - where: Process_committable AND Process_min_up_time > 0 AND position(snapshot) > 0 AND Process_active + where: Process_committed AND Process_min_up_time > 0 AND position(snapshot) > 0 expression: sum_back(Process_start_up, along=snapshot, window=Process_min_up_time) <= Process_status ``` ```math -\sum_{t' \in \mathcal{T} \,:\, 0 \le t - t' < \mathrm{UT}^{z}} \mathit{up}^{z}_{\xi,t',j} \le u^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \mathrm{UT}^{z}_{\xi,j} > 0 \wedge \mathrm{pos}(t) > 0 \wedge \mathrm{on}^{z}_{t,j} +\sum_{t' \in \mathcal{T} \,:\, 0 \le t - t' < \mathrm{UT}^{z}} \mathit{up}^{z}_{\xi,t',j} \le u^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com}}_{t,j} \wedge \mathrm{UT}^{z}_{\xi,j} > 0 \wedge \mathrm{pos}(t) > 0 ``` ### `Process-com-down-time` @@ -7285,12 +7282,12 @@ Process_com_down_time: time is still off. The first snapshot's share of the window is the brought-in down time's, which the must-stay-down mask carries dims: [scenario, snapshot, process] - where: Process_committable AND Process_min_down_time > 0 AND position(snapshot) > 0 AND Process_active + where: Process_committed AND Process_min_down_time > 0 AND position(snapshot) > 0 expression: sum_back(Process_shut_down, along=snapshot, window=Process_min_down_time) <= 1 - Process_status ``` ```math -\sum_{t' \in \mathcal{T} \,:\, 0 \le t - t' < \mathrm{DT}^{z}} \mathit{dn}^{z}_{\xi,t',j} \le 1 - u^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \mathrm{DT}^{z}_{\xi,j} > 0 \wedge \mathrm{pos}(t) > 0 \wedge \mathrm{on}^{z}_{t,j} +\sum_{t' \in \mathcal{T} \,:\, 0 \le t - t' < \mathrm{DT}^{z}} \mathit{dn}^{z}_{\xi,t',j} \le 1 - u^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com}}_{t,j} \wedge \mathrm{DT}^{z}_{\xi,j} > 0 \wedge \mathrm{pos}(t) > 0 ``` ### `Process-com-status-min_up_time_must_stay_up` @@ -7301,12 +7298,12 @@ Process_com_down_time: Process_com_status_must_stay_up: description: "`Process-com-status-min_up_time_must_stay_up` — a process still serving the up time it brought in stays on" dims: [scenario, snapshot, process] - where: Process_committable AND Process_must_stay_up AND Process_active + where: Process_committed AND Process_must_stay_up expression: Process_status == 1 ``` ```math -u^{z}_{\xi,t,j} = 1 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \mathrm{hold}^{z}_{\xi,t,j} \wedge \mathrm{on}^{z}_{t,j} +u^{z}_{\xi,t,j} = 1 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com}}_{t,j} \wedge \mathrm{hold}^{z}_{\xi,t,j} ``` ### `Process-com-status-min_down_time_must_stay_up` @@ -7319,12 +7316,12 @@ Process_com_status_must_stay_down: `Process-com-status-min_down_time_must_stay_up` — a process still serving the down time it brought in stays off; PyPSA names the row `_must_stay_up` dims: [scenario, snapshot, process] - where: Process_committable AND Process_must_stay_down AND Process_active + where: Process_committed AND Process_must_stay_down expression: Process_status == 0 ``` ```math -u^{z}_{\xi,t,j} = 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \mathrm{rest}^{z}_{\xi,t,j} \wedge \mathrm{on}^{z}_{t,j} +u^{z}_{\xi,t,j} = 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com}}_{t,j} \wedge \mathrm{rest}^{z}_{\xi,t,j} ``` ### `Process-p-ramp_limit_up-run-bigM` @@ -7338,11 +7335,7 @@ Process_p_ramp_limit_up_run_big_m: raises internal power no faster than its limit of the chosen build; the big M releases the row in the snapshot it turns on dims: [scenario, snapshot, process] - where: >- - Process_committable AND Process_p_nom_extendable AND NOT (Process_p_nom_mod > 0) - AND (Process_ramp_limit_up OR Process_ramp_limit_start_up) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Process_status_initial == 0 OR Process_p_init))) - AND Process_active + where: Process_com_ext AND (Process_ramp_limit_up OR Process_ramp_limit_start_up) AND Process_ramps_from_previous expression: >- Process_p - Process_previous_p <= Process_ramp_up_rate * Process_p_nom_ext @@ -7350,7 +7343,7 @@ Process_p_ramp_limit_up_run_big_m: ``` ```math -z_{\xi,t,j} - \overleftarrow{z}_{\xi,t,j} \le \widetilde{\mathrm{ru}}^{z}_{\xi,t,j} \cdot Z_{j} + \mathrm{M}^{z}_{\xi,j} - \mathrm{M}^{z}_{\xi,j} \cdot \overleftarrow{u}^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \mathrm{ext}^{z}_{j} \wedge \neg \left( \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) \wedge \left( \mathrm{ru}^{z}_{\xi,t,j} \text{ is defined} \vee \mathrm{ru}^{z,\mathrm{up}}_{\xi,j} \text{ is defined} \right) \wedge \left( \mathrm{pos}_{\mathrm{snapshot\_period}(t)}(t) > 0 \vee \mathrm{pos}(t) = 0 \wedge \left( \mathrm{u}^{z,0}_{\xi,j} = 0 \vee \mathrm{z}^{0}_{\xi,j} \text{ is defined} \right) \right) \wedge \mathrm{on}^{z}_{t,j} +z_{\xi,t,j} - \overleftarrow{z}_{\xi,t,j} \le \widetilde{\mathrm{ru}}^{z}_{\xi,t,j} \cdot Z_{j} + \mathrm{M}^{z}_{\xi,j} - \mathrm{M}^{z}_{\xi,j} \cdot \overleftarrow{u}^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com,ext}}_{t,j} \wedge \left( \mathrm{ru}^{z}_{\xi,t,j} \text{ is defined} \vee \mathrm{ru}^{z,\mathrm{up}}_{\xi,j} \text{ is defined} \right) \wedge \mathrm{prev}^{z}_{\xi,t,j} ``` ### `Process-p-ramp_limit_up-start-bigM` @@ -7364,11 +7357,7 @@ Process_p_ramp_limit_up_start_big_m: committed extendable process ramps no further than its start-up ramp of the chosen build; the big M releases the row everywhere else dims: [scenario, snapshot, process] - where: >- - Process_committable AND Process_p_nom_extendable AND NOT (Process_p_nom_mod > 0) - AND (Process_ramp_limit_up OR Process_ramp_limit_start_up) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Process_status_initial == 0 OR Process_p_init))) - AND Process_active + where: Process_com_ext AND (Process_ramp_limit_up OR Process_ramp_limit_start_up) AND Process_ramps_from_previous expression: >- Process_p - Process_previous_p <= Process_start_up_rate * Process_p_nom_ext @@ -7376,7 +7365,7 @@ Process_p_ramp_limit_up_start_big_m: ``` ```math -z_{\xi,t,j} - \overleftarrow{z}_{\xi,t,j} \le \widetilde{\mathrm{ru}}^{z,\mathrm{up}}_{\xi,j} \cdot Z_{j} + \mathrm{M}^{z}_{\xi,j} - \mathrm{M}^{z}_{\xi,j} \cdot \mathit{up}^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \mathrm{ext}^{z}_{j} \wedge \neg \left( \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) \wedge \left( \mathrm{ru}^{z}_{\xi,t,j} \text{ is defined} \vee \mathrm{ru}^{z,\mathrm{up}}_{\xi,j} \text{ is defined} \right) \wedge \left( \mathrm{pos}_{\mathrm{snapshot\_period}(t)}(t) > 0 \vee \mathrm{pos}(t) = 0 \wedge \left( \mathrm{u}^{z,0}_{\xi,j} = 0 \vee \mathrm{z}^{0}_{\xi,j} \text{ is defined} \right) \right) \wedge \mathrm{on}^{z}_{t,j} +z_{\xi,t,j} - \overleftarrow{z}_{\xi,t,j} \le \widetilde{\mathrm{ru}}^{z,\mathrm{up}}_{\xi,j} \cdot Z_{j} + \mathrm{M}^{z}_{\xi,j} - \mathrm{M}^{z}_{\xi,j} \cdot \mathit{up}^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com,ext}}_{t,j} \wedge \left( \mathrm{ru}^{z}_{\xi,t,j} \text{ is defined} \vee \mathrm{ru}^{z,\mathrm{up}}_{\xi,j} \text{ is defined} \right) \wedge \mathrm{prev}^{z}_{\xi,t,j} ``` ### `Process-p-ramp_limit_down-run-bigM` @@ -7391,10 +7380,9 @@ Process_p_ramp_limit_down_run_big_m: releases the row in the snapshot it turns off dims: [scenario, snapshot, process] where: >- - Process_committable AND Process_p_nom_extendable AND NOT (Process_p_nom_mod > 0) + Process_com_ext AND (Process_ramp_limit_down OR Process_ramp_limit_shut_down) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Process_status_initial == 0 OR Process_p_init))) - AND Process_active + AND Process_ramps_from_previous expression: >- Process_previous_p - Process_p <= Process_ramp_down_rate * Process_p_nom_ext @@ -7402,7 +7390,7 @@ Process_p_ramp_limit_down_run_big_m: ``` ```math -\overleftarrow{z}_{\xi,t,j} - z_{\xi,t,j} \le \widetilde{\mathrm{rd}}^{z}_{\xi,t,j} \cdot Z_{j} + \mathrm{M}^{z}_{\xi,j} - \mathrm{M}^{z}_{\xi,j} \cdot u^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \mathrm{ext}^{z}_{j} \wedge \neg \left( \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) \wedge \left( \mathrm{rd}^{z}_{\xi,t,j} \text{ is defined} \vee \mathrm{rd}^{z,\mathrm{dn}}_{\xi,j} \text{ is defined} \right) \wedge \left( \mathrm{pos}_{\mathrm{snapshot\_period}(t)}(t) > 0 \vee \mathrm{pos}(t) = 0 \wedge \left( \mathrm{u}^{z,0}_{\xi,j} = 0 \vee \mathrm{z}^{0}_{\xi,j} \text{ is defined} \right) \right) \wedge \mathrm{on}^{z}_{t,j} +\overleftarrow{z}_{\xi,t,j} - z_{\xi,t,j} \le \widetilde{\mathrm{rd}}^{z}_{\xi,t,j} \cdot Z_{j} + \mathrm{M}^{z}_{\xi,j} - \mathrm{M}^{z}_{\xi,j} \cdot u^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com,ext}}_{t,j} \wedge \left( \mathrm{rd}^{z}_{\xi,t,j} \text{ is defined} \vee \mathrm{rd}^{z,\mathrm{dn}}_{\xi,j} \text{ is defined} \right) \wedge \mathrm{prev}^{z}_{\xi,t,j} ``` ### `Process-p-ramp_limit_down-shut-bigM` @@ -7417,10 +7405,9 @@ Process_p_ramp_limit_down_shut_big_m: the chosen build; the big M releases the row everywhere else dims: [scenario, snapshot, process] where: >- - Process_committable AND Process_p_nom_extendable AND NOT (Process_p_nom_mod > 0) + Process_com_ext AND (Process_ramp_limit_down OR Process_ramp_limit_shut_down) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Process_status_initial == 0 OR Process_p_init))) - AND Process_active + AND Process_ramps_from_previous expression: >- Process_previous_p - Process_p <= Process_shut_down_rate * Process_p_nom_ext @@ -7428,7 +7415,7 @@ Process_p_ramp_limit_down_shut_big_m: ``` ```math -\overleftarrow{z}_{\xi,t,j} - z_{\xi,t,j} \le \widetilde{\mathrm{rd}}^{z,\mathrm{dn}}_{\xi,j} \cdot Z_{j} + \mathrm{M}^{z}_{\xi,j} - \mathrm{M}^{z}_{\xi,j} \cdot \mathit{dn}^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \mathrm{ext}^{z}_{j} \wedge \neg \left( \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) \wedge \left( \mathrm{rd}^{z}_{\xi,t,j} \text{ is defined} \vee \mathrm{rd}^{z,\mathrm{dn}}_{\xi,j} \text{ is defined} \right) \wedge \left( \mathrm{pos}_{\mathrm{snapshot\_period}(t)}(t) > 0 \vee \mathrm{pos}(t) = 0 \wedge \left( \mathrm{u}^{z,0}_{\xi,j} = 0 \vee \mathrm{z}^{0}_{\xi,j} \text{ is defined} \right) \right) \wedge \mathrm{on}^{z}_{t,j} +\overleftarrow{z}_{\xi,t,j} - z_{\xi,t,j} \le \widetilde{\mathrm{rd}}^{z,\mathrm{dn}}_{\xi,j} \cdot Z_{j} + \mathrm{M}^{z}_{\xi,j} - \mathrm{M}^{z}_{\xi,j} \cdot \mathit{dn}^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com,ext}}_{t,j} \wedge \left( \mathrm{rd}^{z}_{\xi,t,j} \text{ is defined} \vee \mathrm{rd}^{z,\mathrm{dn}}_{\xi,j} \text{ is defined} \right) \wedge \mathrm{prev}^{z}_{\xi,t,j} ``` ### `Process-p_nom_modularity` @@ -7457,12 +7444,12 @@ Process_com_ext_p_upper_cap: `Process-com-ext-p-upper-cap` — a committed extendable process runs at most what is available of the chosen build, whatever its status dims: [scenario, snapshot, process] - where: Process_committable AND Process_p_nom_extendable AND NOT (Process_p_nom_mod > 0) AND Process_active + where: Process_com_ext expression: Process_p <= Process_p_max_pu * (Process_p_nom_ext - Process_maintenance_pu * Process_maintenance_capacity) ``` ```math -z_{\xi,t,j} \le \overline{\mathrm{z}}_{\xi,t,j} \cdot \left( Z_{j} - \gamma^{z}_{\xi,j} \cdot \mu^{z,\mathrm{nom}}_{\xi,t,j} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \mathrm{ext}^{z}_{j} \wedge \neg \left( \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) \wedge \mathrm{on}^{z}_{t,j} +z_{\xi,t,j} \le \overline{\mathrm{z}}_{\xi,t,j} \cdot \left( Z_{j} - \gamma^{z}_{\xi,j} \cdot \mu^{z,\mathrm{nom}}_{\xi,t,j} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com,ext}}_{t,j} ``` ### `Process-com-ext-p-upper-bigM` @@ -7473,12 +7460,12 @@ z_{\xi,t,j} \le \overline{\mathrm{z}}_{\xi,t,j} \cdot \left( Z_{j} - \gamma^{z}_ Process_com_ext_p_upper_big_m: description: "`Process-com-ext-p-upper-bigM` — off, a process does not run; on, the big M is no bound" dims: [scenario, snapshot, process] - where: Process_committable AND Process_p_nom_extendable AND NOT (Process_p_nom_mod > 0) AND Process_active + where: Process_com_ext expression: Process_p <= Process_big_m * Process_status ``` ```math -z_{\xi,t,j} \le \mathrm{M}^{z}_{\xi,j} \cdot u^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \mathrm{ext}^{z}_{j} \wedge \neg \left( \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) \wedge \mathrm{on}^{z}_{t,j} +z_{\xi,t,j} \le \mathrm{M}^{z}_{\xi,j} \cdot u^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com,ext}}_{t,j} ``` ### `Process-com-ext-p-lower` @@ -7491,7 +7478,7 @@ Process_com_ext_p_lower: `Process-com-ext-p-lower` — a committed extendable process runs at least its minimum of the chosen build; off, the big M releases the row dims: [scenario, snapshot, process] - where: Process_committable AND Process_p_nom_extendable AND NOT (Process_p_nom_mod > 0) AND Process_active + where: Process_com_ext expression: >- Process_p >= Process_p_min_pu * (Process_p_nom_ext - Process_maintenance_pu * Process_maintenance_capacity) @@ -7499,7 +7486,7 @@ Process_com_ext_p_lower: ``` ```math -z_{\xi,t,j} \ge \underline{\mathrm{z}}_{\xi,t,j} \cdot \left( Z_{j} - \gamma^{z}_{\xi,j} \cdot \mu^{z,\mathrm{nom}}_{\xi,t,j} \right) + \mathrm{M}^{z}_{\xi,j} \cdot u^{z}_{\xi,t,j} - \mathrm{M}^{z}_{\xi,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \mathrm{ext}^{z}_{j} \wedge \neg \left( \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) \wedge \mathrm{on}^{z}_{t,j} +z_{\xi,t,j} \ge \underline{\mathrm{z}}_{\xi,t,j} \cdot \left( Z_{j} - \gamma^{z}_{\xi,j} \cdot \mu^{z,\mathrm{nom}}_{\xi,t,j} \right) + \mathrm{M}^{z}_{\xi,j} \cdot u^{z}_{\xi,t,j} - \mathrm{M}^{z}_{\xi,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com,ext}}_{t,j} ``` ### `Process-com-ext-p-lower-nonneg` @@ -7513,14 +7500,12 @@ Process_com_ext_p_lower_nonneg: negative, internal power is also plainly non-negative, a row the big-M lower cannot assert while the process is off dims: [scenario, snapshot, process] - where: >- - Process_committable AND Process_p_nom_extendable - AND Process_p_min_pu_nonneg AND NOT (Process_p_nom_mod > 0) AND Process_active + where: Process_com_ext AND Process_p_min_pu_nonneg expression: Process_p >= 0 ``` ```math -z_{\xi,t,j} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \mathrm{ext}^{z}_{j} \wedge \mathrm{nonneg}^{z}_{j} \wedge \neg \left( \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) \wedge \mathrm{on}^{z}_{t,j} +z_{\xi,t,j} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com,ext}}_{t,j} \wedge \mathrm{nonneg}^{z}_{j} ``` ### `Process-com-mod-p-lower` @@ -7533,12 +7518,12 @@ Process_com_mod_p_lower: `Process-com-mod-p-lower` — a committed modular process runs at least its minimum of one module, whether the build is fixed or a decision dims: [scenario, snapshot, process] - where: Process_committable AND Process_p_nom_mod > 0 AND Process_active + where: Process_committed AND Process_p_nom_mod > 0 expression: Process_p >= Process_p_min_pu * Process_p_nom_mod * (Process_status - Process_maintenance_pu * Process_maintenance_status) ``` ```math -z_{\xi,t,j} \ge \underline{\mathrm{z}}_{\xi,t,j} \cdot \mathrm{z}^{\mathrm{mod}}_{j} \cdot \left( u^{z}_{\xi,t,j} - \gamma^{z}_{\xi,j} \cdot \mu^{z,u}_{\xi,t,j} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 \wedge \mathrm{on}^{z}_{t,j} +z_{\xi,t,j} \ge \underline{\mathrm{z}}_{\xi,t,j} \cdot \mathrm{z}^{\mathrm{mod}}_{j} \cdot \left( u^{z}_{\xi,t,j} - \gamma^{z}_{\xi,j} \cdot \mu^{z,u}_{\xi,t,j} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com}}_{t,j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 ``` ### `Process-com-mod-p-upper` @@ -7551,12 +7536,12 @@ Process_com_mod_p_upper: `Process-com-mod-p-upper` — a committed modular process runs at most one module's share, whether the build is fixed or a decision dims: [scenario, snapshot, process] - where: Process_committable AND Process_p_nom_mod > 0 AND Process_active + where: Process_committed AND Process_p_nom_mod > 0 expression: Process_p <= Process_p_max_pu * Process_p_nom_mod * (Process_status - Process_maintenance_pu * Process_maintenance_status) ``` ```math -z_{\xi,t,j} \le \overline{\mathrm{z}}_{\xi,t,j} \cdot \mathrm{z}^{\mathrm{mod}}_{j} \cdot \left( u^{z}_{\xi,t,j} - \gamma^{z}_{\xi,j} \cdot \mu^{z,u}_{\xi,t,j} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 \wedge \mathrm{on}^{z}_{t,j} +z_{\xi,t,j} \le \overline{\mathrm{z}}_{\xi,t,j} \cdot \mathrm{z}^{\mathrm{mod}}_{j} \cdot \left( u^{z}_{\xi,t,j} - \gamma^{z}_{\xi,j} \cdot \mu^{z,u}_{\xi,t,j} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com}}_{t,j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 ``` ### `Process-status-p-fixed-upper` @@ -7570,12 +7555,12 @@ Process_status_p_fixed_upper: 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 dims: [scenario, snapshot, process] - where: Process_committable AND NOT (Process_p_nom_extendable AND Process_p_nom_mod > 0) AND Process_active + where: Process_committed AND NOT (Process_p_nom_extendable AND Process_p_nom_mod > 0) expression: Process_status <= Process_modules_installed ``` ```math -u^{z}_{\xi,t,j} \le \mathrm{N}^{z,\mathrm{fix}}_{\xi,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \neg \left( \mathrm{ext}^{z}_{j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) \wedge \mathrm{on}^{z}_{t,j} +u^{z}_{\xi,t,j} \le \mathrm{N}^{z,\mathrm{fix}}_{\xi,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com}}_{t,j} \wedge \neg \left( \mathrm{ext}^{z}_{j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) ``` ### `Process-start_up-p-fixed-upper` @@ -7589,12 +7574,12 @@ Process_start_up_p_fixed_upper: 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 dims: [scenario, snapshot, process] - where: Process_committable AND NOT (Process_p_nom_extendable AND Process_p_nom_mod > 0) AND Process_active + where: Process_committed AND NOT (Process_p_nom_extendable AND Process_p_nom_mod > 0) expression: Process_start_up <= Process_modules_installed ``` ```math -\mathit{up}^{z}_{\xi,t,j} \le \mathrm{N}^{z,\mathrm{fix}}_{\xi,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \neg \left( \mathrm{ext}^{z}_{j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) \wedge \mathrm{on}^{z}_{t,j} +\mathit{up}^{z}_{\xi,t,j} \le \mathrm{N}^{z,\mathrm{fix}}_{\xi,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com}}_{t,j} \wedge \neg \left( \mathrm{ext}^{z}_{j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) ``` ### `Process-shut_down-p-fixed-upper` @@ -7608,12 +7593,12 @@ Process_shut_down_p_fixed_upper: 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 dims: [scenario, snapshot, process] - where: Process_committable AND NOT (Process_p_nom_extendable AND Process_p_nom_mod > 0) AND Process_active + where: Process_committed AND NOT (Process_p_nom_extendable AND Process_p_nom_mod > 0) expression: Process_shut_down <= Process_modules_installed ``` ```math -\mathit{dn}^{z}_{\xi,t,j} \le \mathrm{N}^{z,\mathrm{fix}}_{\xi,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \neg \left( \mathrm{ext}^{z}_{j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) \wedge \mathrm{on}^{z}_{t,j} +\mathit{dn}^{z}_{\xi,t,j} \le \mathrm{N}^{z,\mathrm{fix}}_{\xi,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com}}_{t,j} \wedge \neg \left( \mathrm{ext}^{z}_{j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) ``` ### `Process-status-p_nom-variable-upper` @@ -7624,12 +7609,12 @@ Process_shut_down_p_fixed_upper: Process_status_p_nom_variable_upper: description: "`Process-status-p_nom-variable-upper` — a modular process is on only where a module is built" dims: [scenario, snapshot, process] - where: Process_committable AND Process_p_nom_extendable AND Process_p_nom_mod > 0 AND Process_active + where: Process_committed AND Process_p_nom_extendable AND Process_p_nom_mod > 0 expression: Process_status <= Process_n_mod ``` ```math -u^{z}_{\xi,t,j} \le N^{z}_{j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \mathrm{ext}^{z}_{j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 \wedge \mathrm{on}^{z}_{t,j} +u^{z}_{\xi,t,j} \le N^{z}_{j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com}}_{t,j} \wedge \mathrm{ext}^{z}_{j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 ``` ### `Process-start_up-p_nom-variable-upper` @@ -7640,12 +7625,12 @@ u^{z}_{\xi,t,j} \le N^{z}_{j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ Process_start_up_p_nom_variable_upper: description: "`Process-start_up-p_nom-variable-upper` — a modular process starts only where a module is built" dims: [scenario, snapshot, process] - where: Process_committable AND Process_p_nom_extendable AND Process_p_nom_mod > 0 AND Process_active + where: Process_committed AND Process_p_nom_extendable AND Process_p_nom_mod > 0 expression: Process_start_up <= Process_n_mod ``` ```math -\mathit{up}^{z}_{\xi,t,j} \le N^{z}_{j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \mathrm{ext}^{z}_{j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 \wedge \mathrm{on}^{z}_{t,j} +\mathit{up}^{z}_{\xi,t,j} \le N^{z}_{j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com}}_{t,j} \wedge \mathrm{ext}^{z}_{j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 ``` ### `Process-shut_down-p_nom-variable-upper` @@ -7656,12 +7641,12 @@ Process_start_up_p_nom_variable_upper: Process_shut_down_p_nom_variable_upper: description: "`Process-shut_down-p_nom-variable-upper` — a modular process stops only where a module is built" dims: [scenario, snapshot, process] - where: Process_committable AND Process_p_nom_extendable AND Process_p_nom_mod > 0 AND Process_active + where: Process_committed AND Process_p_nom_extendable AND Process_p_nom_mod > 0 expression: Process_shut_down <= Process_n_mod ``` ```math -\mathit{dn}^{z}_{\xi,t,j} \le N^{z}_{j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \mathrm{ext}^{z}_{j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 \wedge \mathrm{on}^{z}_{t,j} +\mathit{dn}^{z}_{\xi,t,j} \le N^{z}_{j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com}}_{t,j} \wedge \mathrm{ext}^{z}_{j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 ``` ### `Process-maint-event-count` @@ -7725,12 +7710,12 @@ Process_maintcap_upper: `Process-maintcap_upper` — the build taken off is at most the chosen build in maintenance, and at most the build less its floor out of it dims: [scenario, snapshot, process] - where: Process_maintainable AND Process_p_nom_extendable AND NOT (Process_committable AND Process_p_nom_mod > 0) AND Process_active + where: Process_maint_ext expression: Process_maintenance_capacity <= Process_p_nom_ext - Process_p_nom_min * (1 - Process_maintenance) ``` ```math -\mu^{z,\mathrm{nom}}_{\xi,t,j} \le Z_{j} - \underline{\mathrm{z}}^{\mathrm{nom}}_{\xi,j} \cdot \left( 1 - \mu^{z}_{\xi,t,j} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{mnt}^{z}_{j} \wedge \mathrm{ext}^{z}_{j} \wedge \neg \left( \mathrm{com}^{z}_{j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) \wedge \mathrm{on}^{z}_{t,j} +\mu^{z,\mathrm{nom}}_{\xi,t,j} \le Z_{j} - \underline{\mathrm{z}}^{\mathrm{nom}}_{\xi,j} \cdot \left( 1 - \mu^{z}_{\xi,t,j} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{mnt,ext}}_{t,j} ``` ### `Process-maintcap_upper_nommax` @@ -7741,12 +7726,12 @@ Process_maintcap_upper: Process_maintcap_upper_nommax: description: "`Process-maintcap_upper_nommax` — out of maintenance, no build is taken off" dims: [scenario, snapshot, process] - where: Process_maintainable AND Process_p_nom_extendable AND NOT (Process_committable AND Process_p_nom_mod > 0) AND Process_active + where: Process_maint_ext expression: Process_maintenance_capacity <= Process_p_nom_max * Process_maintenance ``` ```math -\mu^{z,\mathrm{nom}}_{\xi,t,j} \le \overline{\mathrm{z}}^{\mathrm{nom}}_{\xi,j} \cdot \mu^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{mnt}^{z}_{j} \wedge \mathrm{ext}^{z}_{j} \wedge \neg \left( \mathrm{com}^{z}_{j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) \wedge \mathrm{on}^{z}_{t,j} +\mu^{z,\mathrm{nom}}_{\xi,t,j} \le \overline{\mathrm{z}}^{\mathrm{nom}}_{\xi,j} \cdot \mu^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{mnt,ext}}_{t,j} ``` ### `Process-maintcap_lower_nommax` @@ -7757,12 +7742,12 @@ Process_maintcap_upper_nommax: Process_maintcap_lower_nommax: description: "`Process-maintcap_lower_nommax` — in maintenance, the whole chosen build is taken off" dims: [scenario, snapshot, process] - where: Process_maintainable AND Process_p_nom_extendable AND NOT (Process_committable AND Process_p_nom_mod > 0) AND Process_active + where: Process_maint_ext expression: Process_maintenance_capacity >= Process_p_nom_ext - Process_p_nom_max * (1 - Process_maintenance) ``` ```math -\mu^{z,\mathrm{nom}}_{\xi,t,j} \ge Z_{j} - \overline{\mathrm{z}}^{\mathrm{nom}}_{\xi,j} \cdot \left( 1 - \mu^{z}_{\xi,t,j} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{mnt}^{z}_{j} \wedge \mathrm{ext}^{z}_{j} \wedge \neg \left( \mathrm{com}^{z}_{j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) \wedge \mathrm{on}^{z}_{t,j} +\mu^{z,\mathrm{nom}}_{\xi,t,j} \ge Z_{j} - \overline{\mathrm{z}}^{\mathrm{nom}}_{\xi,j} \cdot \left( 1 - \mu^{z}_{\xi,t,j} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{mnt,ext}}_{t,j} ``` ### `Process-maintcap_lower_nommin` @@ -7773,12 +7758,12 @@ Process_maintcap_lower_nommax: Process_maintcap_lower_nommin: description: "`Process-maintcap_lower_nommin` — in maintenance, at least the floor of the build is taken off" dims: [scenario, snapshot, process] - where: Process_maintainable AND Process_p_nom_extendable AND NOT (Process_committable AND Process_p_nom_mod > 0) AND Process_active AND Process_p_nom_min > 0 + where: Process_maint_ext AND Process_p_nom_min > 0 expression: Process_maintenance_capacity >= Process_p_nom_min * Process_maintenance ``` ```math -\mu^{z,\mathrm{nom}}_{\xi,t,j} \ge \underline{\mathrm{z}}^{\mathrm{nom}}_{\xi,j} \cdot \mu^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{mnt}^{z}_{j} \wedge \mathrm{ext}^{z}_{j} \wedge \neg \left( \mathrm{com}^{z}_{j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) \wedge \mathrm{on}^{z}_{t,j} \wedge \underline{\mathrm{z}}^{\mathrm{nom}}_{\xi,j} > 0 +\mu^{z,\mathrm{nom}}_{\xi,t,j} \ge \underline{\mathrm{z}}^{\mathrm{nom}}_{\xi,j} \cdot \mu^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{mnt,ext}}_{t,j} \wedge \underline{\mathrm{z}}^{\mathrm{nom}}_{\xi,j} > 0 ``` ### `Process-maint-status-le-status` @@ -7789,12 +7774,12 @@ Process_maintcap_lower_nommin: Process_maint_status_le_status: description: "`Process-maint-status-le-status` — the status in maintenance is at most the status" dims: [scenario, snapshot, process] - where: Process_maintainable AND Process_committable AND NOT Process_p_nom_extendable AND Process_active + where: Process_maintainable AND Process_committed AND NOT Process_p_nom_extendable expression: Process_maintenance_status <= Process_status ``` ```math -\mu^{z,u}_{\xi,t,j} \le u^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{mnt}^{z}_{j} \wedge \mathrm{com}^{z}_{j} \wedge \neg \mathrm{ext}^{z}_{j} \wedge \mathrm{on}^{z}_{t,j} +\mu^{z,u}_{\xi,t,j} \le u^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{mnt}^{z}_{j} \wedge \mathrm{on}^{z,\mathrm{com}}_{t,j} \wedge \neg \mathrm{ext}^{z}_{j} ``` ### `Process-maint-status-le-maint` @@ -7805,12 +7790,12 @@ Process_maint_status_le_status: Process_maint_status_le_maint: description: "`Process-maint-status-le-maint` — out of maintenance, the status in maintenance is zero" dims: [scenario, snapshot, process] - where: Process_maintainable AND Process_committable AND NOT Process_p_nom_extendable AND Process_active + where: Process_maintainable AND Process_committed AND NOT Process_p_nom_extendable expression: Process_maintenance_status <= Process_maintenance ``` ```math -\mu^{z,u}_{\xi,t,j} \le \mu^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{mnt}^{z}_{j} \wedge \mathrm{com}^{z}_{j} \wedge \neg \mathrm{ext}^{z}_{j} \wedge \mathrm{on}^{z}_{t,j} +\mu^{z,u}_{\xi,t,j} \le \mu^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{mnt}^{z}_{j} \wedge \mathrm{on}^{z,\mathrm{com}}_{t,j} \wedge \neg \mathrm{ext}^{z}_{j} ``` ### `Process-maint-status-lb` @@ -7821,12 +7806,12 @@ Process_maint_status_le_maint: Process_maint_status_lb: description: "`Process-maint-status-lb` — on and in maintenance, the status in maintenance is one" dims: [scenario, snapshot, process] - where: Process_maintainable AND Process_committable AND NOT Process_p_nom_extendable AND Process_active + where: Process_maintainable AND Process_committed AND NOT Process_p_nom_extendable expression: Process_maintenance_status >= Process_status + Process_maintenance - 1 ``` ```math -\mu^{z,u}_{\xi,t,j} \ge u^{z}_{\xi,t,j} + \mu^{z}_{\xi,t,j} - 1 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{mnt}^{z}_{j} \wedge \mathrm{com}^{z}_{j} \wedge \neg \mathrm{ext}^{z}_{j} \wedge \mathrm{on}^{z}_{t,j} +\mu^{z,u}_{\xi,t,j} \ge u^{z}_{\xi,t,j} + \mu^{z}_{\xi,t,j} - 1 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{mnt}^{z}_{j} \wedge \mathrm{on}^{z,\mathrm{com}}_{t,j} \wedge \neg \mathrm{ext}^{z}_{j} ``` ### `Process-maint-modstatus-le-status` @@ -7837,12 +7822,12 @@ Process_maint_status_lb: Process_maint_modstatus_le_status: description: "`Process-maint-modstatus-le-status` — the modules on in maintenance are at most the modules on" dims: [scenario, snapshot, process] - where: Process_maintainable AND Process_committable AND Process_p_nom_mod > 0 AND Process_active + where: Process_maintainable AND Process_committed AND Process_p_nom_mod > 0 expression: Process_maintenance_status <= Process_status ``` ```math -\mu^{z,u}_{\xi,t,j} \le u^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{mnt}^{z}_{j} \wedge \mathrm{com}^{z}_{j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 \wedge \mathrm{on}^{z}_{t,j} +\mu^{z,u}_{\xi,t,j} \le u^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{mnt}^{z}_{j} \wedge \mathrm{on}^{z,\mathrm{com}}_{t,j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 ``` ### `Process-maint-modstatus-le-maint` @@ -7855,12 +7840,12 @@ Process_maint_modstatus_le_maint: `Process-maint-modstatus-le-maint` — out of maintenance, no module is on in maintenance; in it, at most the modules the build cap holds dims: [scenario, snapshot, process] - where: Process_maintainable AND Process_committable AND Process_p_nom_mod > 0 AND Process_active + where: Process_maintainable AND Process_committed AND Process_p_nom_mod > 0 expression: Process_maintenance_status <= Process_p_nom_max / Process_p_nom_mod * Process_maintenance ``` ```math -\mu^{z,u}_{\xi,t,j} \le \frac{\overline{\mathrm{z}}^{\mathrm{nom}}_{\xi,j}}{\mathrm{z}^{\mathrm{mod}}_{j}} \cdot \mu^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{mnt}^{z}_{j} \wedge \mathrm{com}^{z}_{j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 \wedge \mathrm{on}^{z}_{t,j} +\mu^{z,u}_{\xi,t,j} \le \frac{\overline{\mathrm{z}}^{\mathrm{nom}}_{\xi,j}}{\mathrm{z}^{\mathrm{mod}}_{j}} \cdot \mu^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{mnt}^{z}_{j} \wedge \mathrm{on}^{z,\mathrm{com}}_{t,j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 ``` ### `Process-maint-modstatus-lb` @@ -7871,12 +7856,12 @@ Process_maint_modstatus_le_maint: Process_maint_modstatus_lb: description: "`Process-maint-modstatus-lb` — in maintenance, every module on is on in maintenance" dims: [scenario, snapshot, process] - where: Process_maintainable AND Process_committable AND Process_p_nom_mod > 0 AND Process_active + where: Process_maintainable AND Process_committed AND Process_p_nom_mod > 0 expression: Process_maintenance_status >= Process_status - Process_p_nom_max / Process_p_nom_mod * (1 - Process_maintenance) ``` ```math -\mu^{z,u}_{\xi,t,j} \ge u^{z}_{\xi,t,j} - \frac{\overline{\mathrm{z}}^{\mathrm{nom}}_{\xi,j}}{\mathrm{z}^{\mathrm{mod}}_{j}} \cdot \left( 1 - \mu^{z}_{\xi,t,j} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{mnt}^{z}_{j} \wedge \mathrm{com}^{z}_{j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 \wedge \mathrm{on}^{z}_{t,j} +\mu^{z,u}_{\xi,t,j} \ge u^{z}_{\xi,t,j} - \frac{\overline{\mathrm{z}}^{\mathrm{nom}}_{\xi,j}}{\mathrm{z}^{\mathrm{mod}}_{j}} \cdot \left( 1 - \mu^{z}_{\xi,t,j} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{mnt}^{z}_{j} \wedge \mathrm{on}^{z,\mathrm{com}}_{t,j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 ``` ### `Line-fix-s-lower` @@ -8015,12 +8000,12 @@ s_{\xi,t,k} = \mathrm{s}^{\mathrm{set}}_{\xi,t,k} \qquad \forall\, \xi \in \Xi,\ Line_loss_upper: description: "`Line-loss_upper` — a line dissipates at most the loss at its rating" dims: [scenario, snapshot, line] - where: transmission_losses AND Line_active + where: Line_lossy expression: Line_loss <= Line_loss_max ``` ```math -\ell_{\xi,t,k} \le \overline{\ell}_{\xi,t,k} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ k \in \mathcal{K} \,:\, \mathrm{lossy} \wedge \mathrm{on}^{s}_{t,k} +\ell_{\xi,t,k} \le \overline{\ell}_{\xi,t,k} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ k \in \mathcal{K} \,:\, \mathrm{on}^{s,\mathrm{lossy}}_{t,k} ``` ### `Line-loss_tangents-{k}-1` @@ -8035,12 +8020,12 @@ Line_loss_tangents_forward: `k`, or one row stacked over its `secant` axis, and this block states them all over the segment dimension dims: [scenario, snapshot, line, segment] - where: transmission_losses AND Line_active + where: Line_lossy expression: Line_loss + Line_loss_slope * Line_s >= Line_loss_offset ``` ```math -\ell_{\xi,t,k} + \mathrm{a}_{\xi,t,k,b} \cdot s_{\xi,t,k} \ge \mathrm{b}_{\xi,t,k,b} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ k \in \mathcal{K},\ b \in \mathcal{B} \,:\, \mathrm{lossy} \wedge \mathrm{on}^{s}_{t,k} +\ell_{\xi,t,k} + \mathrm{a}_{\xi,t,k,b} \cdot s_{\xi,t,k} \ge \mathrm{b}_{\xi,t,k,b} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ k \in \mathcal{K},\ b \in \mathcal{B} \,:\, \mathrm{on}^{s,\mathrm{lossy}}_{t,k} ``` ### `Line-loss_tangents-{k}--1` @@ -8053,12 +8038,12 @@ Line_loss_tangents_reverse: `Line-loss_tangents-{k}--1`, `Line-loss_secants-neg` — the same fan mirrored, the loss depending on the flow's magnitude dims: [scenario, snapshot, line, segment] - where: transmission_losses AND Line_active + where: Line_lossy expression: Line_loss - Line_loss_slope * Line_s >= Line_loss_offset ``` ```math -\ell_{\xi,t,k} - \mathrm{a}_{\xi,t,k,b} \cdot s_{\xi,t,k} \ge \mathrm{b}_{\xi,t,k,b} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ k \in \mathcal{K},\ b \in \mathcal{B} \,:\, \mathrm{lossy} \wedge \mathrm{on}^{s}_{t,k} +\ell_{\xi,t,k} - \mathrm{a}_{\xi,t,k,b} \cdot s_{\xi,t,k} \ge \mathrm{b}_{\xi,t,k,b} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ k \in \mathcal{K},\ b \in \mathcal{B} \,:\, \mathrm{on}^{s,\mathrm{lossy}}_{t,k} ``` ### `Transformer-fix-s-lower` @@ -8197,12 +8182,12 @@ Transformer_s_set: Transformer_loss_upper: description: "`Transformer-loss_upper` — a transformer dissipates at most the loss at its rating" dims: [scenario, snapshot, transformer] - where: transmission_losses AND Transformer_active + where: Transformer_lossy expression: Transformer_loss <= Transformer_loss_max ``` ```math -\ell^{\sigma}_{\xi,t,m} \le \overline{\ell}^{\sigma}_{\xi,t,m} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ m \in \mathcal{M} \,:\, \mathrm{lossy} \wedge \mathrm{on}^{\sigma}_{t,m} +\ell^{\sigma}_{\xi,t,m} \le \overline{\ell}^{\sigma}_{\xi,t,m} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ m \in \mathcal{M} \,:\, \mathrm{on}^{\sigma,\mathrm{lossy}}_{t,m} ``` ### `Transformer-loss_tangents-{k}-1` @@ -8216,12 +8201,12 @@ Transformer_loss_tangents_forward: loss sits above every cut to its curve for flow one way, as a line's does, over the segment dimension dims: [scenario, snapshot, transformer, segment] - where: transmission_losses AND Transformer_active + where: Transformer_lossy expression: Transformer_loss + Transformer_loss_slope * Transformer_s >= Transformer_loss_offset ``` ```math -\ell^{\sigma}_{\xi,t,m} + \mathrm{a}^{\sigma}_{\xi,t,m,b} \cdot \sigma_{\xi,t,m} \ge \mathrm{b}^{\sigma}_{\xi,t,m,b} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ m \in \mathcal{M},\ b \in \mathcal{B} \,:\, \mathrm{lossy} \wedge \mathrm{on}^{\sigma}_{t,m} +\ell^{\sigma}_{\xi,t,m} + \mathrm{a}^{\sigma}_{\xi,t,m,b} \cdot \sigma_{\xi,t,m} \ge \mathrm{b}^{\sigma}_{\xi,t,m,b} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ m \in \mathcal{M},\ b \in \mathcal{B} \,:\, \mathrm{on}^{\sigma,\mathrm{lossy}}_{t,m} ``` ### `Transformer-loss_tangents-{k}--1` @@ -8234,12 +8219,12 @@ Transformer_loss_tangents_reverse: `Transformer-loss_tangents-{k}--1`, `Transformer-loss_secants-neg` — the same fan mirrored, the loss depending on the flow's magnitude dims: [scenario, snapshot, transformer, segment] - where: transmission_losses AND Transformer_active + where: Transformer_lossy expression: Transformer_loss - Transformer_loss_slope * Transformer_s >= Transformer_loss_offset ``` ```math -\ell^{\sigma}_{\xi,t,m} - \mathrm{a}^{\sigma}_{\xi,t,m,b} \cdot \sigma_{\xi,t,m} \ge \mathrm{b}^{\sigma}_{\xi,t,m,b} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ m \in \mathcal{M},\ b \in \mathcal{B} \,:\, \mathrm{lossy} \wedge \mathrm{on}^{\sigma}_{t,m} +\ell^{\sigma}_{\xi,t,m} - \mathrm{a}^{\sigma}_{\xi,t,m,b} \cdot \sigma_{\xi,t,m} \ge \mathrm{b}^{\sigma}_{\xi,t,m,b} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ m \in \mathcal{M},\ b \in \mathcal{B} \,:\, \mathrm{on}^{\sigma,\mathrm{lossy}}_{t,m} ``` ### `Line-fix-s-lower-security-for-{c}-outage-in-sub-network-{n}` @@ -8454,13 +8439,13 @@ Generator_p_ramp_limit_up: where: >- (Generator_ramp_limit_up OR Generator_ramp_limit_start_up) AND NOT (Generator_committable AND Generator_p_nom_extendable AND NOT (Generator_p_nom_mod > 0)) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Generator_status_initial == 0 OR Generator_p_init))) + AND Generator_ramps_from_previous AND Generator_active expression: Generator_p - Generator_previous_p <= Generator_ramp_up_allowance ``` ```math -p_{\xi,t,g} - \overleftarrow{p}_{\xi,t,g} \le \Delta^{+}_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \left( \mathrm{ru}_{\xi,t,g} \text{ is defined} \vee \mathrm{ru}^{\mathrm{up}}_{\xi,g} \text{ is defined} \right) \wedge \neg \left( \mathrm{com}_{g} \wedge \mathrm{ext}_{g} \wedge \neg \left( \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) \right) \wedge \left( \mathrm{pos}_{\mathrm{snapshot\_period}(t)}(t) > 0 \vee \mathrm{pos}(t) = 0 \wedge \left( \mathrm{u}^{0}_{\xi,g} = 0 \vee \mathrm{p}^{0}_{\xi,g} \text{ is defined} \right) \right) \wedge \mathrm{on}_{t,g} +p_{\xi,t,g} - \overleftarrow{p}_{\xi,t,g} \le \Delta^{+}_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \left( \mathrm{ru}_{\xi,t,g} \text{ is defined} \vee \mathrm{ru}^{\mathrm{up}}_{\xi,g} \text{ is defined} \right) \wedge \neg \left( \mathrm{com}_{g} \wedge \mathrm{ext}_{g} \wedge \neg \left( \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) \right) \wedge \mathrm{prev}_{\xi,t,g} \wedge \mathrm{on}_{t,g} ``` ### `Generator-p-ramp_limit_down` @@ -8480,13 +8465,13 @@ Generator_p_ramp_limit_down: where: >- (Generator_ramp_limit_down OR Generator_ramp_limit_shut_down) AND NOT (Generator_committable AND Generator_p_nom_extendable AND NOT (Generator_p_nom_mod > 0)) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Generator_status_initial == 0 OR Generator_p_init))) + AND Generator_ramps_from_previous AND Generator_active expression: Generator_previous_p - Generator_p <= Generator_ramp_down_allowance ``` ```math -\overleftarrow{p}_{\xi,t,g} - p_{\xi,t,g} \le \Delta^{-}_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \left( \mathrm{rd}_{\xi,t,g} \text{ is defined} \vee \mathrm{rd}^{\mathrm{dn}}_{\xi,g} \text{ is defined} \right) \wedge \neg \left( \mathrm{com}_{g} \wedge \mathrm{ext}_{g} \wedge \neg \left( \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) \right) \wedge \left( \mathrm{pos}_{\mathrm{snapshot\_period}(t)}(t) > 0 \vee \mathrm{pos}(t) = 0 \wedge \left( \mathrm{u}^{0}_{\xi,g} = 0 \vee \mathrm{p}^{0}_{\xi,g} \text{ is defined} \right) \right) \wedge \mathrm{on}_{t,g} +\overleftarrow{p}_{\xi,t,g} - p_{\xi,t,g} \le \Delta^{-}_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \left( \mathrm{rd}_{\xi,t,g} \text{ is defined} \vee \mathrm{rd}^{\mathrm{dn}}_{\xi,g} \text{ is defined} \right) \wedge \neg \left( \mathrm{com}_{g} \wedge \mathrm{ext}_{g} \wedge \neg \left( \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) \right) \wedge \mathrm{prev}_{\xi,t,g} \wedge \mathrm{on}_{t,g} ``` ### `Link-p-ramp_limit_up` @@ -8506,13 +8491,13 @@ Link_p_ramp_limit_up: where: >- (Link_ramp_limit_up OR Link_ramp_limit_start_up) AND NOT (Link_committable AND Link_p_nom_extendable AND NOT (Link_p_nom_mod > 0)) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Link_status_initial == 0 OR Link_p_init))) + AND Link_ramps_from_previous AND Link_active expression: Link_p - Link_previous_p <= Link_ramp_up_allowance ``` ```math -f_{\xi,t,l} - \overleftarrow{f}_{\xi,t,l} \le \Delta^{f,+}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \left( \mathrm{ru}^{f}_{\xi,t,l} \text{ is defined} \vee \mathrm{ru}^{f,\mathrm{up}}_{\xi,l} \text{ is defined} \right) \wedge \neg \left( \mathrm{com}^{f}_{l} \wedge \mathrm{ext}^{f}_{l} \wedge \neg \left( \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) \right) \wedge \left( \mathrm{pos}_{\mathrm{snapshot\_period}(t)}(t) > 0 \vee \mathrm{pos}(t) = 0 \wedge \left( \mathrm{u}^{f,0}_{\xi,l} = 0 \vee \mathrm{f}^{0}_{\xi,l} \text{ is defined} \right) \right) \wedge \mathrm{on}^{f}_{t,l} +f_{\xi,t,l} - \overleftarrow{f}_{\xi,t,l} \le \Delta^{f,+}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \left( \mathrm{ru}^{f}_{\xi,t,l} \text{ is defined} \vee \mathrm{ru}^{f,\mathrm{up}}_{\xi,l} \text{ is defined} \right) \wedge \neg \left( \mathrm{com}^{f}_{l} \wedge \mathrm{ext}^{f}_{l} \wedge \neg \left( \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) \right) \wedge \mathrm{prev}^{f}_{\xi,t,l} \wedge \mathrm{on}^{f}_{t,l} ``` ### `Link-p-ramp_limit_down` @@ -8532,13 +8517,13 @@ Link_p_ramp_limit_down: where: >- (Link_ramp_limit_down OR Link_ramp_limit_shut_down) AND NOT (Link_committable AND Link_p_nom_extendable AND NOT (Link_p_nom_mod > 0)) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Link_status_initial == 0 OR Link_p_init))) + AND Link_ramps_from_previous AND Link_active expression: Link_previous_p - Link_p <= Link_ramp_down_allowance ``` ```math -\overleftarrow{f}_{\xi,t,l} - f_{\xi,t,l} \le \Delta^{f,-}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \left( \mathrm{rd}^{f}_{\xi,t,l} \text{ is defined} \vee \mathrm{rd}^{f,\mathrm{dn}}_{\xi,l} \text{ is defined} \right) \wedge \neg \left( \mathrm{com}^{f}_{l} \wedge \mathrm{ext}^{f}_{l} \wedge \neg \left( \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) \right) \wedge \left( \mathrm{pos}_{\mathrm{snapshot\_period}(t)}(t) > 0 \vee \mathrm{pos}(t) = 0 \wedge \left( \mathrm{u}^{f,0}_{\xi,l} = 0 \vee \mathrm{f}^{0}_{\xi,l} \text{ is defined} \right) \right) \wedge \mathrm{on}^{f}_{t,l} +\overleftarrow{f}_{\xi,t,l} - f_{\xi,t,l} \le \Delta^{f,-}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \left( \mathrm{rd}^{f}_{\xi,t,l} \text{ is defined} \vee \mathrm{rd}^{f,\mathrm{dn}}_{\xi,l} \text{ is defined} \right) \wedge \neg \left( \mathrm{com}^{f}_{l} \wedge \mathrm{ext}^{f}_{l} \wedge \neg \left( \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) \right) \wedge \mathrm{prev}^{f}_{\xi,t,l} \wedge \mathrm{on}^{f}_{t,l} ``` ### `Process-p-ramp_limit_up` @@ -8558,13 +8543,13 @@ Process_p_ramp_limit_up: where: >- (Process_ramp_limit_up OR Process_ramp_limit_start_up) AND NOT (Process_committable AND Process_p_nom_extendable AND NOT (Process_p_nom_mod > 0)) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Process_status_initial == 0 OR Process_p_init))) + AND Process_ramps_from_previous AND Process_active expression: Process_p - Process_previous_p <= Process_ramp_up_allowance ``` ```math -z_{\xi,t,j} - \overleftarrow{z}_{\xi,t,j} \le \Delta^{z,+}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \left( \mathrm{ru}^{z}_{\xi,t,j} \text{ is defined} \vee \mathrm{ru}^{z,\mathrm{up}}_{\xi,j} \text{ is defined} \right) \wedge \neg \left( \mathrm{com}^{z}_{j} \wedge \mathrm{ext}^{z}_{j} \wedge \neg \left( \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) \right) \wedge \left( \mathrm{pos}_{\mathrm{snapshot\_period}(t)}(t) > 0 \vee \mathrm{pos}(t) = 0 \wedge \left( \mathrm{u}^{z,0}_{\xi,j} = 0 \vee \mathrm{z}^{0}_{\xi,j} \text{ is defined} \right) \right) \wedge \mathrm{on}^{z}_{t,j} +z_{\xi,t,j} - \overleftarrow{z}_{\xi,t,j} \le \Delta^{z,+}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \left( \mathrm{ru}^{z}_{\xi,t,j} \text{ is defined} \vee \mathrm{ru}^{z,\mathrm{up}}_{\xi,j} \text{ is defined} \right) \wedge \neg \left( \mathrm{com}^{z}_{j} \wedge \mathrm{ext}^{z}_{j} \wedge \neg \left( \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) \right) \wedge \mathrm{prev}^{z}_{\xi,t,j} \wedge \mathrm{on}^{z}_{t,j} ``` ### `Process-p-ramp_limit_down` @@ -8584,13 +8569,13 @@ Process_p_ramp_limit_down: where: >- (Process_ramp_limit_down OR Process_ramp_limit_shut_down) AND NOT (Process_committable AND Process_p_nom_extendable AND NOT (Process_p_nom_mod > 0)) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Process_status_initial == 0 OR Process_p_init))) + AND Process_ramps_from_previous AND Process_active expression: Process_previous_p - Process_p <= Process_ramp_down_allowance ``` ```math -\overleftarrow{z}_{\xi,t,j} - z_{\xi,t,j} \le \Delta^{z,-}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \left( \mathrm{rd}^{z}_{\xi,t,j} \text{ is defined} \vee \mathrm{rd}^{z,\mathrm{dn}}_{\xi,j} \text{ is defined} \right) \wedge \neg \left( \mathrm{com}^{z}_{j} \wedge \mathrm{ext}^{z}_{j} \wedge \neg \left( \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) \right) \wedge \left( \mathrm{pos}_{\mathrm{snapshot\_period}(t)}(t) > 0 \vee \mathrm{pos}(t) = 0 \wedge \left( \mathrm{u}^{z,0}_{\xi,j} = 0 \vee \mathrm{z}^{0}_{\xi,j} \text{ is defined} \right) \right) \wedge \mathrm{on}^{z}_{t,j} +\overleftarrow{z}_{\xi,t,j} - z_{\xi,t,j} \le \Delta^{z,-}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \left( \mathrm{rd}^{z}_{\xi,t,j} \text{ is defined} \vee \mathrm{rd}^{z,\mathrm{dn}}_{\xi,j} \text{ is defined} \right) \wedge \neg \left( \mathrm{com}^{z}_{j} \wedge \mathrm{ext}^{z}_{j} \wedge \neg \left( \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) \right) \wedge \mathrm{prev}^{z}_{\xi,t,j} \wedge \mathrm{on}^{z}_{t,j} ``` ### `StorageUnit-ext-p_dispatch-lower` @@ -8601,12 +8586,12 @@ Process_p_ramp_limit_down: StorageUnit_ext_p_dispatch_lower: description: "`StorageUnit-ext-p_dispatch-lower` — dispatch is non-negative" dims: [scenario, snapshot, storage_unit] - where: StorageUnit_p_nom_extendable AND StorageUnit_active + where: StorageUnit_ext expression: StorageUnit_p_dispatch >= 0 ``` ```math -h^{+}_{\xi,t,s} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \mathrm{ext}^{h}_{s} \wedge \mathrm{on}^{h}_{t,s} +h^{+}_{\xi,t,s} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \mathrm{on}^{h,\mathrm{ext}}_{t,s} ``` ### `StorageUnit-ext-p_dispatch-upper` @@ -8617,12 +8602,12 @@ h^{+}_{\xi,t,s} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \ StorageUnit_ext_p_dispatch_upper: description: "`StorageUnit-ext-p_dispatch-upper` — an extendable unit dispatches at most the chosen build" dims: [scenario, snapshot, storage_unit] - where: StorageUnit_p_nom_extendable AND StorageUnit_active + where: StorageUnit_ext expression: StorageUnit_p_dispatch <= StorageUnit_p_max_pu * StorageUnit_p_nom_ext ``` ```math -h^{+}_{\xi,t,s} \le \overline{\mathrm{h}}_{\xi,t,s} \cdot H_{s} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \mathrm{ext}^{h}_{s} \wedge \mathrm{on}^{h}_{t,s} +h^{+}_{\xi,t,s} \le \overline{\mathrm{h}}_{\xi,t,s} \cdot H_{s} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \mathrm{on}^{h,\mathrm{ext}}_{t,s} ``` ### `StorageUnit-ext-p_store-lower` @@ -8633,12 +8618,12 @@ h^{+}_{\xi,t,s} \le \overline{\mathrm{h}}_{\xi,t,s} \cdot H_{s} \qquad \forall\, StorageUnit_ext_p_store_lower: description: "`StorageUnit-ext-p_store-lower` — storing is non-negative" dims: [scenario, snapshot, storage_unit] - where: StorageUnit_p_nom_extendable AND StorageUnit_active + where: StorageUnit_ext expression: StorageUnit_p_store >= 0 ``` ```math -h^{-}_{\xi,t,s} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \mathrm{ext}^{h}_{s} \wedge \mathrm{on}^{h}_{t,s} +h^{-}_{\xi,t,s} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \mathrm{on}^{h,\mathrm{ext}}_{t,s} ``` ### `StorageUnit-ext-p_store-upper` @@ -8651,12 +8636,12 @@ StorageUnit_ext_p_store_upper: `StorageUnit-ext-p_store-upper` — an extendable unit stores at most the chosen build, the minimum-per-unit column carrying that cap negated dims: [scenario, snapshot, storage_unit] - where: StorageUnit_p_nom_extendable AND StorageUnit_active + where: StorageUnit_ext expression: StorageUnit_p_store <= -StorageUnit_p_min_pu * StorageUnit_p_nom_ext ``` ```math -h^{-}_{\xi,t,s} \le -\underline{\mathrm{h}}_{\xi,t,s} \cdot H_{s} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \mathrm{ext}^{h}_{s} \wedge \mathrm{on}^{h}_{t,s} +h^{-}_{\xi,t,s} \le -\underline{\mathrm{h}}_{\xi,t,s} \cdot H_{s} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \mathrm{on}^{h,\mathrm{ext}}_{t,s} ``` ### `StorageUnit-ext-state_of_charge-lower` @@ -8667,12 +8652,12 @@ h^{-}_{\xi,t,s} \le -\underline{\mathrm{h}}_{\xi,t,s} \cdot H_{s} \qquad \forall StorageUnit_ext_state_of_charge_lower: description: "`StorageUnit-ext-state_of_charge-lower` — charge is non-negative" dims: [scenario, snapshot, storage_unit] - where: StorageUnit_p_nom_extendable AND StorageUnit_active + where: StorageUnit_ext expression: StorageUnit_state_of_charge >= 0 ``` ```math -\mathit{soc}_{\xi,t,s} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \mathrm{ext}^{h}_{s} \wedge \mathrm{on}^{h}_{t,s} +\mathit{soc}_{\xi,t,s} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \mathrm{on}^{h,\mathrm{ext}}_{t,s} ``` ### `StorageUnit-ext-state_of_charge-upper` @@ -8683,12 +8668,12 @@ StorageUnit_ext_state_of_charge_lower: 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" dims: [scenario, snapshot, storage_unit] - where: StorageUnit_p_nom_extendable AND StorageUnit_active + where: StorageUnit_ext expression: StorageUnit_state_of_charge <= StorageUnit_max_hours * StorageUnit_p_nom_ext ``` ```math -\mathit{soc}_{\xi,t,s} \le \mathrm{T}^{h}_{\xi,s} \cdot H_{s} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \mathrm{ext}^{h}_{s} \wedge \mathrm{on}^{h}_{t,s} +\mathit{soc}_{\xi,t,s} \le \mathrm{T}^{h}_{\xi,s} \cdot H_{s} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \mathrm{on}^{h,\mathrm{ext}}_{t,s} ``` ### `StorageUnit-ext-p_nom-lower` @@ -11042,19 +11027,19 @@ N_{g} \ge 0, N_{g} \in \mathbb{Z} \qquad \forall\, g \in \mathcal{G} \,:\, \math **`Generator_status`** ```math -u_{\xi,t,g} \ge 0, u_{\xi,t,g} \in \mathbb{Z} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \mathrm{on}_{t,g} +u_{\xi,t,g} \ge 0, u_{\xi,t,g} \in \mathbb{Z} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com}}_{t,g} ``` **`Generator_start_up`** ```math -\mathit{up}_{\xi,t,g} \ge 0, \mathit{up}_{\xi,t,g} \in \mathbb{Z} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \mathrm{on}_{t,g} +\mathit{up}_{\xi,t,g} \ge 0, \mathit{up}_{\xi,t,g} \in \mathbb{Z} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com}}_{t,g} ``` **`Generator_shut_down`** ```math -\mathit{dn}_{\xi,t,g} \ge 0, \mathit{dn}_{\xi,t,g} \in \mathbb{Z} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \mathrm{on}_{t,g} +\mathit{dn}_{\xi,t,g} \ge 0, \mathit{dn}_{\xi,t,g} \in \mathbb{Z} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com}}_{t,g} ``` **`Generator_maintenance`** @@ -11072,13 +11057,13 @@ u_{\xi,t,g} \ge 0, u_{\xi,t,g} \in \mathbb{Z} \qquad \forall\, \xi \in \Xi,\ t \ **`Generator_maintenance_capacity`** ```math -\mu^{\mathrm{nom}}_{\xi,t,g} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{mnt}_{g} \wedge \mathrm{ext}_{g} \wedge \neg \left( \mathrm{com}_{g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) \wedge \mathrm{on}_{t,g} +\mu^{\mathrm{nom}}_{\xi,t,g} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{mnt,ext}}_{t,g} ``` **`Generator_maintenance_status`** ```math -\mu^{u}_{\xi,t,g} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{mnt}_{g} \wedge \mathrm{com}_{g} \wedge \neg \left( \mathrm{ext}_{g} \wedge \neg \left( \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) \right) \wedge \mathrm{on}_{t,g} +\mu^{u}_{\xi,t,g} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{mnt}_{g} \wedge \mathrm{on}^{\mathrm{com}}_{t,g} \wedge \neg \left( \mathrm{ext}_{g} \wedge \neg \left( \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) \right) ``` **`Link_n_mod`** @@ -11090,19 +11075,19 @@ N^{f}_{l} \ge 0, N^{f}_{l} \in \mathbb{Z} \qquad \forall\, l \in \mathcal{L} \,: **`Link_status`** ```math -u^{f}_{\xi,t,l} \ge 0, u^{f}_{\xi,t,l} \in \mathbb{Z} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \mathrm{on}^{f}_{t,l} +u^{f}_{\xi,t,l} \ge 0, u^{f}_{\xi,t,l} \in \mathbb{Z} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com}}_{t,l} ``` **`Link_start_up`** ```math -\mathit{up}^{f}_{\xi,t,l} \ge 0, \mathit{up}^{f}_{\xi,t,l} \in \mathbb{Z} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \mathrm{on}^{f}_{t,l} +\mathit{up}^{f}_{\xi,t,l} \ge 0, \mathit{up}^{f}_{\xi,t,l} \in \mathbb{Z} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com}}_{t,l} ``` **`Link_shut_down`** ```math -\mathit{dn}^{f}_{\xi,t,l} \ge 0, \mathit{dn}^{f}_{\xi,t,l} \in \mathbb{Z} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \mathrm{on}^{f}_{t,l} +\mathit{dn}^{f}_{\xi,t,l} \ge 0, \mathit{dn}^{f}_{\xi,t,l} \in \mathbb{Z} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com}}_{t,l} ``` **`Link_maintenance`** @@ -11120,13 +11105,13 @@ u^{f}_{\xi,t,l} \ge 0, u^{f}_{\xi,t,l} \in \mathbb{Z} \qquad \forall\, \xi \in \ **`Link_maintenance_capacity`** ```math -\mu^{f,\mathrm{nom}}_{\xi,t,l} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{mnt}^{f}_{l} \wedge \mathrm{ext}^{f}_{l} \wedge \neg \left( \mathrm{com}^{f}_{l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) \wedge \mathrm{on}^{f}_{t,l} +\mu^{f,\mathrm{nom}}_{\xi,t,l} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{mnt,ext}}_{t,l} ``` **`Link_maintenance_status`** ```math -\mu^{f,u}_{\xi,t,l} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{mnt}^{f}_{l} \wedge \mathrm{com}^{f}_{l} \wedge \neg \left( \mathrm{ext}^{f}_{l} \wedge \neg \left( \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) \right) \wedge \mathrm{on}^{f}_{t,l} +\mu^{f,u}_{\xi,t,l} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{mnt}^{f}_{l} \wedge \mathrm{on}^{f,\mathrm{com}}_{t,l} \wedge \neg \left( \mathrm{ext}^{f}_{l} \wedge \neg \left( \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) \right) ``` **`Process_n_mod`** @@ -11138,19 +11123,19 @@ N^{z}_{j} \ge 0, N^{z}_{j} \in \mathbb{Z} \qquad \forall\, j \in \mathcal{J} \,: **`Process_status`** ```math -u^{z}_{\xi,t,j} \ge 0, u^{z}_{\xi,t,j} \in \mathbb{Z} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \mathrm{on}^{z}_{t,j} +u^{z}_{\xi,t,j} \ge 0, u^{z}_{\xi,t,j} \in \mathbb{Z} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com}}_{t,j} ``` **`Process_start_up`** ```math -\mathit{up}^{z}_{\xi,t,j} \ge 0, \mathit{up}^{z}_{\xi,t,j} \in \mathbb{Z} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \mathrm{on}^{z}_{t,j} +\mathit{up}^{z}_{\xi,t,j} \ge 0, \mathit{up}^{z}_{\xi,t,j} \in \mathbb{Z} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com}}_{t,j} ``` **`Process_shut_down`** ```math -\mathit{dn}^{z}_{\xi,t,j} \ge 0, \mathit{dn}^{z}_{\xi,t,j} \in \mathbb{Z} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \mathrm{on}^{z}_{t,j} +\mathit{dn}^{z}_{\xi,t,j} \ge 0, \mathit{dn}^{z}_{\xi,t,j} \in \mathbb{Z} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com}}_{t,j} ``` **`Process_maintenance`** @@ -11168,13 +11153,13 @@ u^{z}_{\xi,t,j} \ge 0, u^{z}_{\xi,t,j} \in \mathbb{Z} \qquad \forall\, \xi \in \ **`Process_maintenance_capacity`** ```math -\mu^{z,\mathrm{nom}}_{\xi,t,j} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{mnt}^{z}_{j} \wedge \mathrm{ext}^{z}_{j} \wedge \neg \left( \mathrm{com}^{z}_{j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) \wedge \mathrm{on}^{z}_{t,j} +\mu^{z,\mathrm{nom}}_{\xi,t,j} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{mnt,ext}}_{t,j} ``` **`Process_maintenance_status`** ```math -\mu^{z,u}_{\xi,t,j} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{mnt}^{z}_{j} \wedge \mathrm{com}^{z}_{j} \wedge \neg \left( \mathrm{ext}^{z}_{j} \wedge \neg \left( \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) \right) \wedge \mathrm{on}^{z}_{t,j} +\mu^{z,u}_{\xi,t,j} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{mnt}^{z}_{j} \wedge \mathrm{on}^{z,\mathrm{com}}_{t,j} \wedge \neg \left( \mathrm{ext}^{z}_{j} \wedge \neg \left( \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) \right) ``` **`Line_s`** @@ -11186,7 +11171,7 @@ s_{\xi,t,k} \in \mathbb{R} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ k **`Line_loss`** ```math -\ell_{\xi,t,k} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ k \in \mathcal{K} \,:\, \mathrm{lossy} \wedge \mathrm{on}^{s}_{t,k} +\ell_{\xi,t,k} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ k \in \mathcal{K} \,:\, \mathrm{on}^{s,\mathrm{lossy}}_{t,k} ``` **`Transformer_s`** @@ -11198,7 +11183,7 @@ s_{\xi,t,k} \in \mathbb{R} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ k **`Transformer_loss`** ```math -\ell^{\sigma}_{\xi,t,m} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ m \in \mathcal{M} \,:\, \mathrm{lossy} \wedge \mathrm{on}^{\sigma}_{t,m} +\ell^{\sigma}_{\xi,t,m} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ m \in \mathcal{M} \,:\, \mathrm{on}^{\sigma,\mathrm{lossy}}_{t,m} ``` **`Transformer_phase_shift`** diff --git a/docs/examples/pypsa/generator.md b/docs/examples/pypsa/generator.md index 4e4b82ab..2b0d6732 100644 --- a/docs/examples/pypsa/generator.md +++ b/docs/examples/pypsa/generator.md @@ -217,6 +217,13 @@ expressions: expression: sum(Generator_sign * Generator_p, by=Generator_bus, over=generator, into=bus) adds_to: Bus_injection +masks: + Generator_committed: + description: >- + a committable generator that stands in the snapshot's period — every + unit-commitment row's set + where: Generator_committable AND Generator_active + constraints: Generator_fix_p_lower: description: "`Generator-fix-p-lower` — a fixed generator outputs at least its minimum" @@ -369,6 +376,12 @@ assumptions: | $`\mathit{Generator\_additions}`$ | `Generator_additions` over $`\mathcal{Y} \times \mathcal{I}`$ | | $`\mathit{Generator\_injection}`$ | `Generator_injection` over $`\Xi \times \mathcal{T} \times \mathcal{N}`$ | +#### Masks + +| Symbol | Meaning | +|---|---| +| $`\mathrm{on}^{\mathrm{com}}`$ | `Generator_committed` over $`\mathcal{T} \times \mathcal{G}`$ — a committable generator that stands in the snapshot's period — every unit-commitment row's set | + #### Subject to **`Generator_fix_p_lower`** @@ -493,6 +506,14 @@ p_{\xi,t,g} = \mathrm{p}^{\mathrm{set}}_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ \mathit{Generator\_injection}_{\xi,t,n} = \sum_{g \in \mathcal{G} \,:\, \mathrm{Generator\_bus}(g) = n} \mathrm{sgn}_{g} \cdot p_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ n \in \mathcal{N} ``` +#### Masks + +**`Generator_committed`** + +```math +\mathrm{on}^{\mathrm{com}}_{t,g} \iff \mathrm{com}_{g} \wedge \mathrm{on}_{t,g} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} +``` + #### Variable domains **`Generator_p`** diff --git a/docs/examples/pypsa/generator_commitment.md b/docs/examples/pypsa/generator_commitment.md index 2384d6f3..e395d2dd 100644 --- a/docs/examples/pypsa/generator_commitment.md +++ b/docs/examples/pypsa/generator_commitment.md @@ -78,21 +78,21 @@ variables: the rows below cap at one, or at the module count where the build is modular dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_active + where: Generator_committed 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" dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_active + where: Generator_committed 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" dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_active + where: Generator_committed domain: integer bounds: lower: 0 @@ -118,6 +118,8 @@ given: Generator_p_nom_ext: { dims: [generator] } expressions: scenario_opex: { dims: [scenario] } + masks: + Generator_committed: { dims: [snapshot, generator] } expressions: Generator_previous_status: @@ -135,26 +137,38 @@ expressions: + sum(sum(Generator_shut_down * Generator_shut_down_cost, over=generator), over=snapshot) adds_to: scenario_opex +masks: + Generator_com_ext: + description: >- + a committable generator with an extendable, non-modular build that + stands in the snapshot's period — PyPSA's `com-ext` rows, whose status + is relaxed against the chosen build + where: >- + Generator_committable + AND Generator_p_nom_extendable + AND NOT (Generator_p_nom_mod > 0) + AND Generator_active + constraints: Generator_com_p_lower: description: "`Generator-com-p-lower` — a committed unit outputs at least its minimum; off, at least nothing" dims: [scenario, snapshot, generator] - where: Generator_committable AND not Generator_p_nom_extendable AND Generator_active + where: Generator_committed AND NOT Generator_p_nom_extendable expression: Generator_p >= Generator_p_min_pu * Generator_p_nom * (Generator_status - Generator_maintenance_pu * Generator_maintenance_status) Generator_com_p_upper: description: "`Generator-com-p-upper` — a committed unit outputs at most what is available; off, at most nothing" dims: [scenario, snapshot, generator] - where: Generator_committable AND not Generator_p_nom_extendable AND Generator_active + where: Generator_committed AND NOT Generator_p_nom_extendable expression: Generator_p <= Generator_p_max_pu * Generator_p_nom * (Generator_status - Generator_maintenance_pu * Generator_maintenance_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" dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_active + where: Generator_committed 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" dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_active + where: Generator_committed expression: Generator_shut_down >= Generator_previous_status - Generator_status Generator_com_up_time: description: >- @@ -162,7 +176,7 @@ constraints: 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 dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_min_up_time > 0 AND position(snapshot) > 0 AND Generator_active + where: Generator_committed AND Generator_min_up_time > 0 AND position(snapshot) > 0 expression: sum_back(Generator_start_up, along=snapshot, window=Generator_min_up_time) <= Generator_status Generator_com_down_time: description: >- @@ -170,38 +184,38 @@ constraints: time is still off. The first snapshot's share of the window is the brought-in down time's, which the must-stay-down mask carries dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_min_down_time > 0 AND position(snapshot) > 0 AND Generator_active + where: Generator_committed AND Generator_min_down_time > 0 AND position(snapshot) > 0 expression: sum_back(Generator_shut_down, along=snapshot, window=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" dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_must_stay_up AND Generator_active + where: Generator_committed AND Generator_must_stay_up expression: Generator_status == 1 Generator_com_status_must_stay_down: description: >- `Generator-com-status-min_down_time_must_stay_up` — a unit still serving the down time it brought in stays off; PyPSA names the row `_must_stay_up` dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_must_stay_down AND Generator_active + where: Generator_committed AND Generator_must_stay_down expression: Generator_status == 0 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 dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_p_nom_extendable AND NOT (Generator_p_nom_mod > 0) AND Generator_active + where: Generator_com_ext expression: Generator_p <= Generator_p_max_pu * (Generator_p_nom_ext - Generator_maintenance_pu * Generator_maintenance_capacity) 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" dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_p_nom_extendable AND NOT (Generator_p_nom_mod > 0) AND Generator_active + where: Generator_com_ext 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 dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_p_nom_extendable AND NOT (Generator_p_nom_mod > 0) AND Generator_active + where: Generator_com_ext expression: >- Generator_p >= Generator_p_min_pu * (Generator_p_nom_ext - Generator_maintenance_pu * Generator_maintenance_capacity) @@ -212,23 +226,21 @@ constraints: negative, output is also plainly non-negative, a row the big-M lower cannot assert while the unit is off dims: [scenario, snapshot, generator] - where: >- - Generator_committable AND Generator_p_nom_extendable - AND Generator_p_min_pu_nonneg AND NOT (Generator_p_nom_mod > 0) AND Generator_active + where: Generator_com_ext AND Generator_p_min_pu_nonneg expression: Generator_p >= 0 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 dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_p_nom_mod > 0 AND Generator_active + where: Generator_committed AND Generator_p_nom_mod > 0 expression: Generator_p >= Generator_p_min_pu * Generator_p_nom_mod * (Generator_status - Generator_maintenance_pu * Generator_maintenance_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 dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_p_nom_mod > 0 AND Generator_active + where: Generator_committed AND Generator_p_nom_mod > 0 expression: Generator_p <= Generator_p_max_pu * Generator_p_nom_mod * (Generator_status - Generator_maintenance_pu * Generator_maintenance_status) Generator_status_p_fixed_upper: description: >- @@ -236,7 +248,7 @@ constraints: 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 dims: [scenario, snapshot, generator] - where: Generator_committable AND NOT (Generator_p_nom_extendable AND Generator_p_nom_mod > 0) AND Generator_active + where: Generator_committed AND NOT (Generator_p_nom_extendable AND Generator_p_nom_mod > 0) expression: Generator_status <= Generator_modules_installed Generator_start_up_p_fixed_upper: description: >- @@ -244,7 +256,7 @@ constraints: 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 dims: [scenario, snapshot, generator] - where: Generator_committable AND NOT (Generator_p_nom_extendable AND Generator_p_nom_mod > 0) AND Generator_active + where: Generator_committed 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: description: >- @@ -252,22 +264,22 @@ constraints: 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 dims: [scenario, snapshot, generator] - where: Generator_committable AND NOT (Generator_p_nom_extendable AND Generator_p_nom_mod > 0) AND Generator_active + where: Generator_committed 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" dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_p_nom_extendable AND Generator_p_nom_mod > 0 AND Generator_active + where: Generator_committed 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" dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_p_nom_extendable AND Generator_p_nom_mod > 0 AND Generator_active + where: Generator_committed 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" dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_p_nom_extendable AND Generator_p_nom_mod > 0 AND Generator_active + where: Generator_committed AND Generator_p_nom_extendable AND Generator_p_nom_mod > 0 expression: Generator_shut_down <= Generator_n_mod ``` @@ -324,6 +336,7 @@ constraints: | $`\mu^{u}`$ | `Generator_maintenance_status` over $`\Xi \times \mathcal{T} \times \mathcal{G}`$ | | $`P`$ | `Generator_p_nom_ext` over $`\mathcal{G}`$ | | $`\mathit{scenario\_opex}`$ | `scenario_opex` over $`\Xi`$, an expression this file adds `Generator_commitment_opex` to | +| $`\mathrm{on}^{\mathrm{com}}`$ | `Generator_committed` over $`\mathcal{T} \times \mathcal{G}`$, a mask another file defines | #### Definitions @@ -332,6 +345,12 @@ constraints: | $`\overleftarrow{u}`$ | `Generator_previous_status` over $`\Xi \times \mathcal{T} \times \mathcal{G}`$ — 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 | | $`\mathit{Generator\_commitment\_opex}`$ | `Generator_commitment_opex` over $`\Xi`$ | +#### Masks + +| Symbol | Meaning | +|---|---| +| $`\mathrm{on}^{\mathrm{com,ext}}`$ | `Generator_com_ext` over $`\mathcal{T} \times \mathcal{G}`$ — a committable generator with an extendable, non-modular build that stands in the snapshot's period — PyPSA's `com-ext` rows, whose status is relaxed against the chosen build | + $`\mathrm{pos}(t)`$ denotes where index $`t`$ sits along its dimension's own order — the order `shift` steps along, not the order labels sort in — counted from $`0`$. The index itself stays the coordinate, so $`t`$ compares against labels and $`\mathrm{pos}(t)`$ against positions. #### Subject to @@ -339,121 +358,121 @@ $`\mathrm{pos}(t)`$ denotes where index $`t`$ sits along its dimension's own ord **`Generator_com_p_lower`** ```math -p_{\xi,t,g} \ge \underline{\mathrm{p}}_{\xi,t,g} \cdot \mathrm{p}^{\mathrm{nom}}_{\xi,g} \cdot \left( u_{\xi,t,g} - \gamma_{\xi,g} \cdot \mu^{u}_{\xi,t,g} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \neg \mathrm{ext}_{g} \wedge \mathrm{on}_{t,g} +p_{\xi,t,g} \ge \underline{\mathrm{p}}_{\xi,t,g} \cdot \mathrm{p}^{\mathrm{nom}}_{\xi,g} \cdot \left( u_{\xi,t,g} - \gamma_{\xi,g} \cdot \mu^{u}_{\xi,t,g} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com}}_{t,g} \wedge \neg \mathrm{ext}_{g} ``` **`Generator_com_p_upper`** ```math -p_{\xi,t,g} \le \overline{\mathrm{p}}_{\xi,t,g} \cdot \mathrm{p}^{\mathrm{nom}}_{\xi,g} \cdot \left( u_{\xi,t,g} - \gamma_{\xi,g} \cdot \mu^{u}_{\xi,t,g} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \neg \mathrm{ext}_{g} \wedge \mathrm{on}_{t,g} +p_{\xi,t,g} \le \overline{\mathrm{p}}_{\xi,t,g} \cdot \mathrm{p}^{\mathrm{nom}}_{\xi,g} \cdot \left( u_{\xi,t,g} - \gamma_{\xi,g} \cdot \mu^{u}_{\xi,t,g} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com}}_{t,g} \wedge \neg \mathrm{ext}_{g} ``` **`Generator_com_transition_start_up`** ```math -\mathit{up}_{\xi,t,g} \ge u_{\xi,t,g} - \overleftarrow{u}_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \mathrm{on}_{t,g} +\mathit{up}_{\xi,t,g} \ge u_{\xi,t,g} - \overleftarrow{u}_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com}}_{t,g} ``` **`Generator_com_transition_shut_down`** ```math -\mathit{dn}_{\xi,t,g} \ge \overleftarrow{u}_{\xi,t,g} - u_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \mathrm{on}_{t,g} +\mathit{dn}_{\xi,t,g} \ge \overleftarrow{u}_{\xi,t,g} - u_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com}}_{t,g} ``` **`Generator_com_up_time`** ```math -\sum_{t' \in \mathcal{T} \,:\, 0 \le t - t' < \mathrm{UT}} \mathit{up}_{\xi,t',g} \le u_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \mathrm{UT}_{\xi,g} > 0 \wedge \mathrm{pos}(t) > 0 \wedge \mathrm{on}_{t,g} +\sum_{t' \in \mathcal{T} \,:\, 0 \le t - t' < \mathrm{UT}} \mathit{up}_{\xi,t',g} \le u_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com}}_{t,g} \wedge \mathrm{UT}_{\xi,g} > 0 \wedge \mathrm{pos}(t) > 0 ``` **`Generator_com_down_time`** ```math -\sum_{t' \in \mathcal{T} \,:\, 0 \le t - t' < \mathrm{DT}} \mathit{dn}_{\xi,t',g} \le 1 - u_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \mathrm{DT}_{\xi,g} > 0 \wedge \mathrm{pos}(t) > 0 \wedge \mathrm{on}_{t,g} +\sum_{t' \in \mathcal{T} \,:\, 0 \le t - t' < \mathrm{DT}} \mathit{dn}_{\xi,t',g} \le 1 - u_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com}}_{t,g} \wedge \mathrm{DT}_{\xi,g} > 0 \wedge \mathrm{pos}(t) > 0 ``` **`Generator_com_status_must_stay_up`** ```math -u_{\xi,t,g} = 1 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \mathrm{hold}_{\xi,t,g} \wedge \mathrm{on}_{t,g} +u_{\xi,t,g} = 1 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com}}_{t,g} \wedge \mathrm{hold}_{\xi,t,g} ``` **`Generator_com_status_must_stay_down`** ```math -u_{\xi,t,g} = 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \mathrm{rest}_{\xi,t,g} \wedge \mathrm{on}_{t,g} +u_{\xi,t,g} = 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com}}_{t,g} \wedge \mathrm{rest}_{\xi,t,g} ``` **`Generator_com_ext_p_upper_cap`** ```math -p_{\xi,t,g} \le \overline{\mathrm{p}}_{\xi,t,g} \cdot \left( P_{g} - \gamma_{\xi,g} \cdot \mu^{\mathrm{nom}}_{\xi,t,g} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \mathrm{ext}_{g} \wedge \neg \left( \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) \wedge \mathrm{on}_{t,g} +p_{\xi,t,g} \le \overline{\mathrm{p}}_{\xi,t,g} \cdot \left( P_{g} - \gamma_{\xi,g} \cdot \mu^{\mathrm{nom}}_{\xi,t,g} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com,ext}}_{t,g} ``` **`Generator_com_ext_p_upper_big_m`** ```math -p_{\xi,t,g} \le \mathrm{M}_{\xi,g} \cdot u_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \mathrm{ext}_{g} \wedge \neg \left( \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) \wedge \mathrm{on}_{t,g} +p_{\xi,t,g} \le \mathrm{M}_{\xi,g} \cdot u_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com,ext}}_{t,g} ``` **`Generator_com_ext_p_lower`** ```math -p_{\xi,t,g} \ge \underline{\mathrm{p}}_{\xi,t,g} \cdot \left( P_{g} - \gamma_{\xi,g} \cdot \mu^{\mathrm{nom}}_{\xi,t,g} \right) + \mathrm{M}_{\xi,g} \cdot u_{\xi,t,g} - \mathrm{M}_{\xi,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \mathrm{ext}_{g} \wedge \neg \left( \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) \wedge \mathrm{on}_{t,g} +p_{\xi,t,g} \ge \underline{\mathrm{p}}_{\xi,t,g} \cdot \left( P_{g} - \gamma_{\xi,g} \cdot \mu^{\mathrm{nom}}_{\xi,t,g} \right) + \mathrm{M}_{\xi,g} \cdot u_{\xi,t,g} - \mathrm{M}_{\xi,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com,ext}}_{t,g} ``` **`Generator_com_ext_p_lower_nonneg`** ```math -p_{\xi,t,g} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \mathrm{ext}_{g} \wedge \mathrm{nonneg}_{g} \wedge \neg \left( \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) \wedge \mathrm{on}_{t,g} +p_{\xi,t,g} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com,ext}}_{t,g} \wedge \mathrm{nonneg}_{g} ``` **`Generator_com_mod_p_lower`** ```math -p_{\xi,t,g} \ge \underline{\mathrm{p}}_{\xi,t,g} \cdot \mathrm{p}^{\mathrm{mod}}_{g} \cdot \left( u_{\xi,t,g} - \gamma_{\xi,g} \cdot \mu^{u}_{\xi,t,g} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 \wedge \mathrm{on}_{t,g} +p_{\xi,t,g} \ge \underline{\mathrm{p}}_{\xi,t,g} \cdot \mathrm{p}^{\mathrm{mod}}_{g} \cdot \left( u_{\xi,t,g} - \gamma_{\xi,g} \cdot \mu^{u}_{\xi,t,g} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com}}_{t,g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 ``` **`Generator_com_mod_p_upper`** ```math -p_{\xi,t,g} \le \overline{\mathrm{p}}_{\xi,t,g} \cdot \mathrm{p}^{\mathrm{mod}}_{g} \cdot \left( u_{\xi,t,g} - \gamma_{\xi,g} \cdot \mu^{u}_{\xi,t,g} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 \wedge \mathrm{on}_{t,g} +p_{\xi,t,g} \le \overline{\mathrm{p}}_{\xi,t,g} \cdot \mathrm{p}^{\mathrm{mod}}_{g} \cdot \left( u_{\xi,t,g} - \gamma_{\xi,g} \cdot \mu^{u}_{\xi,t,g} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com}}_{t,g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 ``` **`Generator_status_p_fixed_upper`** ```math -u_{\xi,t,g} \le \mathrm{N}^{\mathrm{fix}}_{\xi,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \neg \left( \mathrm{ext}_{g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) \wedge \mathrm{on}_{t,g} +u_{\xi,t,g} \le \mathrm{N}^{\mathrm{fix}}_{\xi,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com}}_{t,g} \wedge \neg \left( \mathrm{ext}_{g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) ``` **`Generator_start_up_p_fixed_upper`** ```math -\mathit{up}_{\xi,t,g} \le \mathrm{N}^{\mathrm{fix}}_{\xi,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \neg \left( \mathrm{ext}_{g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) \wedge \mathrm{on}_{t,g} +\mathit{up}_{\xi,t,g} \le \mathrm{N}^{\mathrm{fix}}_{\xi,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com}}_{t,g} \wedge \neg \left( \mathrm{ext}_{g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) ``` **`Generator_shut_down_p_fixed_upper`** ```math -\mathit{dn}_{\xi,t,g} \le \mathrm{N}^{\mathrm{fix}}_{\xi,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \neg \left( \mathrm{ext}_{g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) \wedge \mathrm{on}_{t,g} +\mathit{dn}_{\xi,t,g} \le \mathrm{N}^{\mathrm{fix}}_{\xi,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com}}_{t,g} \wedge \neg \left( \mathrm{ext}_{g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) ``` **`Generator_status_p_nom_variable_upper`** ```math -u_{\xi,t,g} \le N_{g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \mathrm{ext}_{g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 \wedge \mathrm{on}_{t,g} +u_{\xi,t,g} \le N_{g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com}}_{t,g} \wedge \mathrm{ext}_{g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 ``` **`Generator_start_up_p_nom_variable_upper`** ```math -\mathit{up}_{\xi,t,g} \le N_{g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \mathrm{ext}_{g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 \wedge \mathrm{on}_{t,g} +\mathit{up}_{\xi,t,g} \le N_{g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com}}_{t,g} \wedge \mathrm{ext}_{g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 ``` **`Generator_shut_down_p_nom_variable_upper`** ```math -\mathit{dn}_{\xi,t,g} \le N_{g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \mathrm{ext}_{g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 \wedge \mathrm{on}_{t,g} +\mathit{dn}_{\xi,t,g} \le N_{g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com}}_{t,g} \wedge \mathrm{ext}_{g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 ``` #### Definitions @@ -470,23 +489,31 @@ u_{\xi,t,g} \le N_{g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \ \mathit{Generator\_commitment\_opex}_{\xi} = \sum_{t \in \mathcal{T}} \sum_{g \in \mathcal{G}} u_{\xi,t,g} \cdot \mathrm{c}^{\mathrm{on}}_{\xi,t,g} \cdot \mathrm{w}_{t} \cdot \mathrm{w}^{y}_{\mathrm{snapshot\_period}(t)} + \sum_{t \in \mathcal{T}} \sum_{g \in \mathcal{G}} \mathit{up}_{\xi,t,g} \cdot \mathrm{c}^{\mathrm{up}}_{\xi,g} + \sum_{t \in \mathcal{T}} \sum_{g \in \mathcal{G}} \mathit{dn}_{\xi,t,g} \cdot \mathrm{c}^{\mathrm{dn}}_{\xi,g} \qquad \forall\, \xi \in \Xi ``` +#### Masks + +**`Generator_com_ext`** + +```math +\mathrm{on}^{\mathrm{com,ext}}_{t,g} \iff \mathrm{com}_{g} \wedge \mathrm{ext}_{g} \wedge \neg \left( \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) \wedge \mathrm{on}_{t,g} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} +``` + #### Variable domains **`Generator_status`** ```math -u_{\xi,t,g} \ge 0, u_{\xi,t,g} \in \mathbb{Z} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \mathrm{on}_{t,g} +u_{\xi,t,g} \ge 0, u_{\xi,t,g} \in \mathbb{Z} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com}}_{t,g} ``` **`Generator_start_up`** ```math -\mathit{up}_{\xi,t,g} \ge 0, \mathit{up}_{\xi,t,g} \in \mathbb{Z} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \mathrm{on}_{t,g} +\mathit{up}_{\xi,t,g} \ge 0, \mathit{up}_{\xi,t,g} \in \mathbb{Z} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com}}_{t,g} ``` **`Generator_shut_down`** ```math -\mathit{dn}_{\xi,t,g} \ge 0, \mathit{dn}_{\xi,t,g} \in \mathbb{Z} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \mathrm{on}_{t,g} +\mathit{dn}_{\xi,t,g} \ge 0, \mathit{dn}_{\xi,t,g} \in \mathbb{Z} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com}}_{t,g} ``` diff --git a/docs/examples/pypsa/generator_maintenance.md b/docs/examples/pypsa/generator_maintenance.md index de416877..2bb7e9fc 100644 --- a/docs/examples/pypsa/generator_maintenance.md +++ b/docs/examples/pypsa/generator_maintenance.md @@ -81,9 +81,7 @@ variables: `Generator-maintenance_capacity` — the chosen build while in maintenance, zero otherwise: the product the `maintcap` rows linearize dims: [scenario, snapshot, generator] - where: >- - Generator_maintainable AND Generator_p_nom_extendable - AND NOT (Generator_committable AND Generator_p_nom_mod > 0) AND Generator_active + where: Generator_maint_ext absence: zero bounds: lower: 0 @@ -94,8 +92,9 @@ variables: maintenance may also be off dims: [scenario, snapshot, generator] where: >- - Generator_maintainable AND Generator_committable - AND NOT (Generator_p_nom_extendable AND NOT (Generator_p_nom_mod > 0)) AND Generator_active + Generator_maintainable + AND Generator_committed + AND NOT (Generator_p_nom_extendable AND NOT (Generator_p_nom_mod > 0)) absence: zero bounds: lower: 0 @@ -112,6 +111,20 @@ given: variables: Generator_status: { dims: [scenario, snapshot, generator], domain: integer } Generator_p_nom_ext: { dims: [generator] } + masks: + Generator_committed: { dims: [snapshot, generator] } + +masks: + Generator_maint_ext: + description: >- + a maintainable generator with an extendable build, unless it is + committable and modular, that stands in the snapshot's period — the + maintenance rows against the chosen build + where: >- + Generator_maintainable + AND Generator_p_nom_extendable + AND NOT (Generator_committable AND Generator_p_nom_mod > 0) + AND Generator_active constraints: Generator_maint_event_count: @@ -137,54 +150,54 @@ constraints: `Generator-maintcap_upper` — the build taken off is at most the chosen build in maintenance, and at most the build less its floor out of it dims: [scenario, snapshot, generator] - where: Generator_maintainable AND Generator_p_nom_extendable AND NOT (Generator_committable AND Generator_p_nom_mod > 0) AND Generator_active + where: Generator_maint_ext expression: Generator_maintenance_capacity <= Generator_p_nom_ext - Generator_p_nom_min * (1 - Generator_maintenance) Generator_maintcap_upper_nommax: description: "`Generator-maintcap_upper_nommax` — out of maintenance, no build is taken off" dims: [scenario, snapshot, generator] - where: Generator_maintainable AND Generator_p_nom_extendable AND NOT (Generator_committable AND Generator_p_nom_mod > 0) AND Generator_active + where: Generator_maint_ext expression: Generator_maintenance_capacity <= Generator_p_nom_max * Generator_maintenance Generator_maintcap_lower_nommax: description: "`Generator-maintcap_lower_nommax` — in maintenance, the whole chosen build is taken off" dims: [scenario, snapshot, generator] - where: Generator_maintainable AND Generator_p_nom_extendable AND NOT (Generator_committable AND Generator_p_nom_mod > 0) AND Generator_active + where: Generator_maint_ext expression: Generator_maintenance_capacity >= Generator_p_nom_ext - Generator_p_nom_max * (1 - Generator_maintenance) Generator_maintcap_lower_nommin: description: "`Generator-maintcap_lower_nommin` — in maintenance, at least the floor of the build is taken off" dims: [scenario, snapshot, generator] - where: Generator_maintainable AND Generator_p_nom_extendable AND NOT (Generator_committable AND Generator_p_nom_mod > 0) AND Generator_active AND Generator_p_nom_min > 0 + where: Generator_maint_ext AND Generator_p_nom_min > 0 expression: Generator_maintenance_capacity >= Generator_p_nom_min * Generator_maintenance Generator_maint_status_le_status: description: "`Generator-maint-status-le-status` — the status in maintenance is at most the status" dims: [scenario, snapshot, generator] - where: Generator_maintainable AND Generator_committable AND NOT Generator_p_nom_extendable AND Generator_active + where: Generator_maintainable AND Generator_committed AND NOT Generator_p_nom_extendable expression: Generator_maintenance_status <= Generator_status Generator_maint_status_le_maint: description: "`Generator-maint-status-le-maint` — out of maintenance, the status in maintenance is zero" dims: [scenario, snapshot, generator] - where: Generator_maintainable AND Generator_committable AND NOT Generator_p_nom_extendable AND Generator_active + where: Generator_maintainable AND Generator_committed AND NOT Generator_p_nom_extendable expression: Generator_maintenance_status <= Generator_maintenance Generator_maint_status_lb: description: "`Generator-maint-status-lb` — on and in maintenance, the status in maintenance is one" dims: [scenario, snapshot, generator] - where: Generator_maintainable AND Generator_committable AND NOT Generator_p_nom_extendable AND Generator_active + where: Generator_maintainable AND Generator_committed AND NOT Generator_p_nom_extendable expression: Generator_maintenance_status >= Generator_status + Generator_maintenance - 1 Generator_maint_modstatus_le_status: description: "`Generator-maint-modstatus-le-status` — the modules on in maintenance are at most the modules on" dims: [scenario, snapshot, generator] - where: Generator_maintainable AND Generator_committable AND Generator_p_nom_mod > 0 AND Generator_active + where: Generator_maintainable AND Generator_committed AND Generator_p_nom_mod > 0 expression: Generator_maintenance_status <= Generator_status Generator_maint_modstatus_le_maint: description: >- `Generator-maint-modstatus-le-maint` — out of maintenance, no module is on in maintenance; in it, at most the modules the build cap holds dims: [scenario, snapshot, generator] - where: Generator_maintainable AND Generator_committable AND Generator_p_nom_mod > 0 AND Generator_active + where: Generator_maintainable AND Generator_committed AND Generator_p_nom_mod > 0 expression: Generator_maintenance_status <= Generator_p_nom_max / Generator_p_nom_mod * Generator_maintenance Generator_maint_modstatus_lb: description: "`Generator-maint-modstatus-lb` — in maintenance, every module on is on in maintenance" dims: [scenario, snapshot, generator] - where: Generator_maintainable AND Generator_committable AND Generator_p_nom_mod > 0 AND Generator_active + where: Generator_maintainable AND Generator_committed AND Generator_p_nom_mod > 0 expression: Generator_maintenance_status >= Generator_status - Generator_p_nom_max / Generator_p_nom_mod * (1 - Generator_maintenance) assumptions: @@ -271,6 +284,13 @@ assumptions: | $`\overline{\mathrm{p}}^{\mathrm{nom}}`$ | `Generator_p_nom_max` over $`\Xi \times \mathcal{G}`$, data another file declares | | $`u`$ | `Generator_status` over $`\Xi \times \mathcal{T} \times \mathcal{G}`$ | | $`P`$ | `Generator_p_nom_ext` over $`\mathcal{G}`$ | +| $`\mathrm{on}^{\mathrm{com}}`$ | `Generator_committed` over $`\mathcal{T} \times \mathcal{G}`$, a mask another file defines | + +#### Masks + +| Symbol | Meaning | +|---|---| +| $`\mathrm{on}^{\mathrm{mnt,ext}}`$ | `Generator_maint_ext` over $`\mathcal{T} \times \mathcal{G}`$ — a maintainable generator with an extendable build, unless it is committable and modular, that stands in the snapshot's period — the maintenance rows against the chosen build | #### Subject to @@ -295,61 +315,69 @@ assumptions: **`Generator_maintcap_upper`** ```math -\mu^{\mathrm{nom}}_{\xi,t,g} \le P_{g} - \underline{\mathrm{p}}^{\mathrm{nom}}_{\xi,g} \cdot \left( 1 - \mu_{\xi,t,g} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{mnt}_{g} \wedge \mathrm{ext}_{g} \wedge \neg \left( \mathrm{com}_{g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) \wedge \mathrm{on}_{t,g} +\mu^{\mathrm{nom}}_{\xi,t,g} \le P_{g} - \underline{\mathrm{p}}^{\mathrm{nom}}_{\xi,g} \cdot \left( 1 - \mu_{\xi,t,g} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{mnt,ext}}_{t,g} ``` **`Generator_maintcap_upper_nommax`** ```math -\mu^{\mathrm{nom}}_{\xi,t,g} \le \overline{\mathrm{p}}^{\mathrm{nom}}_{\xi,g} \cdot \mu_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{mnt}_{g} \wedge \mathrm{ext}_{g} \wedge \neg \left( \mathrm{com}_{g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) \wedge \mathrm{on}_{t,g} +\mu^{\mathrm{nom}}_{\xi,t,g} \le \overline{\mathrm{p}}^{\mathrm{nom}}_{\xi,g} \cdot \mu_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{mnt,ext}}_{t,g} ``` **`Generator_maintcap_lower_nommax`** ```math -\mu^{\mathrm{nom}}_{\xi,t,g} \ge P_{g} - \overline{\mathrm{p}}^{\mathrm{nom}}_{\xi,g} \cdot \left( 1 - \mu_{\xi,t,g} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{mnt}_{g} \wedge \mathrm{ext}_{g} \wedge \neg \left( \mathrm{com}_{g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) \wedge \mathrm{on}_{t,g} +\mu^{\mathrm{nom}}_{\xi,t,g} \ge P_{g} - \overline{\mathrm{p}}^{\mathrm{nom}}_{\xi,g} \cdot \left( 1 - \mu_{\xi,t,g} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{mnt,ext}}_{t,g} ``` **`Generator_maintcap_lower_nommin`** ```math -\mu^{\mathrm{nom}}_{\xi,t,g} \ge \underline{\mathrm{p}}^{\mathrm{nom}}_{\xi,g} \cdot \mu_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{mnt}_{g} \wedge \mathrm{ext}_{g} \wedge \neg \left( \mathrm{com}_{g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) \wedge \mathrm{on}_{t,g} \wedge \underline{\mathrm{p}}^{\mathrm{nom}}_{\xi,g} > 0 +\mu^{\mathrm{nom}}_{\xi,t,g} \ge \underline{\mathrm{p}}^{\mathrm{nom}}_{\xi,g} \cdot \mu_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{mnt,ext}}_{t,g} \wedge \underline{\mathrm{p}}^{\mathrm{nom}}_{\xi,g} > 0 ``` **`Generator_maint_status_le_status`** ```math -\mu^{u}_{\xi,t,g} \le u_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{mnt}_{g} \wedge \mathrm{com}_{g} \wedge \neg \mathrm{ext}_{g} \wedge \mathrm{on}_{t,g} +\mu^{u}_{\xi,t,g} \le u_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{mnt}_{g} \wedge \mathrm{on}^{\mathrm{com}}_{t,g} \wedge \neg \mathrm{ext}_{g} ``` **`Generator_maint_status_le_maint`** ```math -\mu^{u}_{\xi,t,g} \le \mu_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{mnt}_{g} \wedge \mathrm{com}_{g} \wedge \neg \mathrm{ext}_{g} \wedge \mathrm{on}_{t,g} +\mu^{u}_{\xi,t,g} \le \mu_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{mnt}_{g} \wedge \mathrm{on}^{\mathrm{com}}_{t,g} \wedge \neg \mathrm{ext}_{g} ``` **`Generator_maint_status_lb`** ```math -\mu^{u}_{\xi,t,g} \ge u_{\xi,t,g} + \mu_{\xi,t,g} - 1 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{mnt}_{g} \wedge \mathrm{com}_{g} \wedge \neg \mathrm{ext}_{g} \wedge \mathrm{on}_{t,g} +\mu^{u}_{\xi,t,g} \ge u_{\xi,t,g} + \mu_{\xi,t,g} - 1 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{mnt}_{g} \wedge \mathrm{on}^{\mathrm{com}}_{t,g} \wedge \neg \mathrm{ext}_{g} ``` **`Generator_maint_modstatus_le_status`** ```math -\mu^{u}_{\xi,t,g} \le u_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{mnt}_{g} \wedge \mathrm{com}_{g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 \wedge \mathrm{on}_{t,g} +\mu^{u}_{\xi,t,g} \le u_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{mnt}_{g} \wedge \mathrm{on}^{\mathrm{com}}_{t,g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 ``` **`Generator_maint_modstatus_le_maint`** ```math -\mu^{u}_{\xi,t,g} \le \frac{\overline{\mathrm{p}}^{\mathrm{nom}}_{\xi,g}}{\mathrm{p}^{\mathrm{mod}}_{g}} \cdot \mu_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{mnt}_{g} \wedge \mathrm{com}_{g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 \wedge \mathrm{on}_{t,g} +\mu^{u}_{\xi,t,g} \le \frac{\overline{\mathrm{p}}^{\mathrm{nom}}_{\xi,g}}{\mathrm{p}^{\mathrm{mod}}_{g}} \cdot \mu_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{mnt}_{g} \wedge \mathrm{on}^{\mathrm{com}}_{t,g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 ``` **`Generator_maint_modstatus_lb`** ```math -\mu^{u}_{\xi,t,g} \ge u_{\xi,t,g} - \frac{\overline{\mathrm{p}}^{\mathrm{nom}}_{\xi,g}}{\mathrm{p}^{\mathrm{mod}}_{g}} \cdot \left( 1 - \mu_{\xi,t,g} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{mnt}_{g} \wedge \mathrm{com}_{g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 \wedge \mathrm{on}_{t,g} +\mu^{u}_{\xi,t,g} \ge u_{\xi,t,g} - \frac{\overline{\mathrm{p}}^{\mathrm{nom}}_{\xi,g}}{\mathrm{p}^{\mathrm{mod}}_{g}} \cdot \left( 1 - \mu_{\xi,t,g} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{mnt}_{g} \wedge \mathrm{on}^{\mathrm{com}}_{t,g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 +``` + +#### Masks + +**`Generator_maint_ext`** + +```math +\mathrm{on}^{\mathrm{mnt,ext}}_{t,g} \iff \mathrm{mnt}_{g} \wedge \mathrm{ext}_{g} \wedge \neg \left( \mathrm{com}_{g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) \wedge \mathrm{on}_{t,g} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} ``` #### Variable domains @@ -369,13 +397,13 @@ assumptions: **`Generator_maintenance_capacity`** ```math -\mu^{\mathrm{nom}}_{\xi,t,g} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{mnt}_{g} \wedge \mathrm{ext}_{g} \wedge \neg \left( \mathrm{com}_{g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) \wedge \mathrm{on}_{t,g} +\mu^{\mathrm{nom}}_{\xi,t,g} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{mnt,ext}}_{t,g} ``` **`Generator_maintenance_status`** ```math -\mu^{u}_{\xi,t,g} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{mnt}_{g} \wedge \mathrm{com}_{g} \wedge \neg \left( \mathrm{ext}_{g} \wedge \neg \left( \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) \right) \wedge \mathrm{on}_{t,g} +\mu^{u}_{\xi,t,g} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{mnt}_{g} \wedge \mathrm{on}^{\mathrm{com}}_{t,g} \wedge \neg \left( \mathrm{ext}_{g} \wedge \neg \left( \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) \right) ``` #### Assumptions diff --git a/docs/examples/pypsa/generator_ramping.md b/docs/examples/pypsa/generator_ramping.md index bcb55e43..60da7bc0 100644 --- a/docs/examples/pypsa/generator_ramping.md +++ b/docs/examples/pypsa/generator_ramping.md @@ -65,6 +65,8 @@ given: Generator_previous_status: { dims: [scenario, snapshot, generator] } Generator_p_nom_effective: { dims: [scenario, generator] } Generator_p_nom_committed: { dims: [scenario, generator] } + masks: + Generator_com_ext: { dims: [snapshot, generator] } expressions: Generator_previous_p: @@ -137,6 +139,16 @@ expressions: * (Generator_previous_status - Generator_status) otherwise: Generator_ramp_down_rate * Generator_p_nom_effective +masks: + Generator_ramps_from_previous: + description: >- + a snapshot whose ramp reads a previous output — any snapshot but its + period's first, and the horizon's first where the generator comes in off + or carries an initial output + where: >- + position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND + (Generator_status_initial == 0 OR Generator_p_init)) + constraints: Generator_p_ramp_limit_up_run_big_m: description: >- @@ -145,10 +157,9 @@ constraints: releases the row in the snapshot it turns on dims: [scenario, snapshot, generator] where: >- - Generator_committable AND Generator_p_nom_extendable AND NOT (Generator_p_nom_mod > 0) + Generator_com_ext AND (Generator_ramp_limit_up OR Generator_ramp_limit_start_up) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Generator_status_initial == 0 OR Generator_p_init))) - AND Generator_active + AND Generator_ramps_from_previous expression: >- Generator_p - Generator_previous_p <= Generator_ramp_up_rate * Generator_p_nom_ext @@ -160,10 +171,9 @@ constraints: the chosen build; the big M releases the row everywhere else dims: [scenario, snapshot, generator] where: >- - Generator_committable AND Generator_p_nom_extendable AND NOT (Generator_p_nom_mod > 0) + Generator_com_ext AND (Generator_ramp_limit_up OR Generator_ramp_limit_start_up) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Generator_status_initial == 0 OR Generator_p_init))) - AND Generator_active + AND Generator_ramps_from_previous expression: >- Generator_p - Generator_previous_p <= Generator_start_up_rate * Generator_p_nom_ext @@ -175,10 +185,9 @@ constraints: releases the row in the snapshot it turns off dims: [scenario, snapshot, generator] where: >- - Generator_committable AND Generator_p_nom_extendable AND NOT (Generator_p_nom_mod > 0) + Generator_com_ext AND (Generator_ramp_limit_down OR Generator_ramp_limit_shut_down) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Generator_status_initial == 0 OR Generator_p_init))) - AND Generator_active + AND Generator_ramps_from_previous expression: >- Generator_previous_p - Generator_p <= Generator_ramp_down_rate * Generator_p_nom_ext @@ -190,10 +199,9 @@ constraints: the chosen build; the big M releases the row everywhere else dims: [scenario, snapshot, generator] where: >- - Generator_committable AND Generator_p_nom_extendable AND NOT (Generator_p_nom_mod > 0) + Generator_com_ext AND (Generator_ramp_limit_down OR Generator_ramp_limit_shut_down) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Generator_status_initial == 0 OR Generator_p_init))) - AND Generator_active + AND Generator_ramps_from_previous expression: >- Generator_previous_p - Generator_p <= Generator_shut_down_rate * Generator_p_nom_ext @@ -210,7 +218,7 @@ constraints: where: >- (Generator_ramp_limit_up OR Generator_ramp_limit_start_up) AND NOT (Generator_committable AND Generator_p_nom_extendable AND NOT (Generator_p_nom_mod > 0)) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Generator_status_initial == 0 OR Generator_p_init))) + AND Generator_ramps_from_previous AND Generator_active expression: Generator_p - Generator_previous_p <= Generator_ramp_up_allowance Generator_p_ramp_limit_down: @@ -225,7 +233,7 @@ constraints: where: >- (Generator_ramp_limit_down OR Generator_ramp_limit_shut_down) AND NOT (Generator_committable AND Generator_p_nom_extendable AND NOT (Generator_p_nom_mod > 0)) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Generator_status_initial == 0 OR Generator_p_init))) + AND Generator_ramps_from_previous AND Generator_active expression: Generator_previous_p - Generator_p <= Generator_ramp_down_allowance @@ -281,6 +289,7 @@ assumptions: | $`\overleftarrow{u}`$ | `Generator_previous_status` over $`\Xi \times \mathcal{T} \times \mathcal{G}`$, an expression another file defines | | $`\widetilde{\mathrm{p}}^{\mathrm{nom}}`$ | `Generator_p_nom_effective` over $`\Xi \times \mathcal{G}`$, an expression another file defines | | $`\widehat{\mathrm{p}}^{\mathrm{nom}}`$ | `Generator_p_nom_committed` over $`\Xi \times \mathcal{G}`$, an expression another file defines | +| $`\mathrm{on}^{\mathrm{com,ext}}`$ | `Generator_com_ext` over $`\mathcal{T} \times \mathcal{G}`$, a mask another file defines | #### Definitions @@ -294,6 +303,12 @@ assumptions: | $`\Delta^{+}`$ | `Generator_ramp_up_allowance` over $`\Xi \times \mathcal{T} \times \mathcal{G}`$ — 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 | | $`\Delta^{-}`$ | `Generator_ramp_down_allowance` over $`\Xi \times \mathcal{T} \times \mathcal{G}`$ — 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 | +#### Masks + +| Symbol | Meaning | +|---|---| +| $`\mathrm{prev}`$ | `Generator_ramps_from_previous` over $`\Xi \times \mathcal{T} \times \mathcal{G}`$ — a snapshot whose ramp reads a previous output — any snapshot but its period's first, and the horizon's first where the generator comes in off or carries an initial output | + $`\mathrm{pos}(t)`$ denotes where index $`t`$ sits along its dimension's own order — the order `shift` steps along, not the order labels sort in — counted from $`0`$. The index itself stays the coordinate, so $`t`$ compares against labels and $`\mathrm{pos}(t)`$ against positions. $`\mathrm{pos}_{\mathrm{relation}(t)}(t)`$ counts within the group a relation puts $`t`$ in: the subscript names the map, $`\mathcal{T}_{\mathrm{relation}(t)}`$ is the group it lands in, and that group has a first position of its own. @@ -303,37 +318,37 @@ $`\mathrm{pos}_{\mathrm{relation}(t)}(t)`$ counts within the group a relation pu **`Generator_p_ramp_limit_up_run_big_m`** ```math -p_{\xi,t,g} - \overleftarrow{p}_{\xi,t,g} \le \widetilde{\mathrm{ru}}_{\xi,t,g} \cdot P_{g} + \mathrm{M}_{\xi,g} - \mathrm{M}_{\xi,g} \cdot \overleftarrow{u}_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \mathrm{ext}_{g} \wedge \neg \left( \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) \wedge \left( \mathrm{ru}_{\xi,t,g} \text{ is defined} \vee \mathrm{ru}^{\mathrm{up}}_{\xi,g} \text{ is defined} \right) \wedge \left( \mathrm{pos}_{\mathrm{snapshot\_period}(t)}(t) > 0 \vee \mathrm{pos}(t) = 0 \wedge \left( \mathrm{u}^{0}_{\xi,g} = 0 \vee \mathrm{p}^{0}_{\xi,g} \text{ is defined} \right) \right) \wedge \mathrm{on}_{t,g} +p_{\xi,t,g} - \overleftarrow{p}_{\xi,t,g} \le \widetilde{\mathrm{ru}}_{\xi,t,g} \cdot P_{g} + \mathrm{M}_{\xi,g} - \mathrm{M}_{\xi,g} \cdot \overleftarrow{u}_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com,ext}}_{t,g} \wedge \left( \mathrm{ru}_{\xi,t,g} \text{ is defined} \vee \mathrm{ru}^{\mathrm{up}}_{\xi,g} \text{ is defined} \right) \wedge \mathrm{prev}_{\xi,t,g} ``` **`Generator_p_ramp_limit_up_start_big_m`** ```math -p_{\xi,t,g} - \overleftarrow{p}_{\xi,t,g} \le \widetilde{\mathrm{ru}}^{\mathrm{up}}_{\xi,g} \cdot P_{g} + \mathrm{M}_{\xi,g} - \mathrm{M}_{\xi,g} \cdot \mathit{up}_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \mathrm{ext}_{g} \wedge \neg \left( \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) \wedge \left( \mathrm{ru}_{\xi,t,g} \text{ is defined} \vee \mathrm{ru}^{\mathrm{up}}_{\xi,g} \text{ is defined} \right) \wedge \left( \mathrm{pos}_{\mathrm{snapshot\_period}(t)}(t) > 0 \vee \mathrm{pos}(t) = 0 \wedge \left( \mathrm{u}^{0}_{\xi,g} = 0 \vee \mathrm{p}^{0}_{\xi,g} \text{ is defined} \right) \right) \wedge \mathrm{on}_{t,g} +p_{\xi,t,g} - \overleftarrow{p}_{\xi,t,g} \le \widetilde{\mathrm{ru}}^{\mathrm{up}}_{\xi,g} \cdot P_{g} + \mathrm{M}_{\xi,g} - \mathrm{M}_{\xi,g} \cdot \mathit{up}_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com,ext}}_{t,g} \wedge \left( \mathrm{ru}_{\xi,t,g} \text{ is defined} \vee \mathrm{ru}^{\mathrm{up}}_{\xi,g} \text{ is defined} \right) \wedge \mathrm{prev}_{\xi,t,g} ``` **`Generator_p_ramp_limit_down_run_big_m`** ```math -\overleftarrow{p}_{\xi,t,g} - p_{\xi,t,g} \le \widetilde{\mathrm{rd}}_{\xi,t,g} \cdot P_{g} + \mathrm{M}_{\xi,g} - \mathrm{M}_{\xi,g} \cdot u_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \mathrm{ext}_{g} \wedge \neg \left( \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) \wedge \left( \mathrm{rd}_{\xi,t,g} \text{ is defined} \vee \mathrm{rd}^{\mathrm{dn}}_{\xi,g} \text{ is defined} \right) \wedge \left( \mathrm{pos}_{\mathrm{snapshot\_period}(t)}(t) > 0 \vee \mathrm{pos}(t) = 0 \wedge \left( \mathrm{u}^{0}_{\xi,g} = 0 \vee \mathrm{p}^{0}_{\xi,g} \text{ is defined} \right) \right) \wedge \mathrm{on}_{t,g} +\overleftarrow{p}_{\xi,t,g} - p_{\xi,t,g} \le \widetilde{\mathrm{rd}}_{\xi,t,g} \cdot P_{g} + \mathrm{M}_{\xi,g} - \mathrm{M}_{\xi,g} \cdot u_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com,ext}}_{t,g} \wedge \left( \mathrm{rd}_{\xi,t,g} \text{ is defined} \vee \mathrm{rd}^{\mathrm{dn}}_{\xi,g} \text{ is defined} \right) \wedge \mathrm{prev}_{\xi,t,g} ``` **`Generator_p_ramp_limit_down_shut_big_m`** ```math -\overleftarrow{p}_{\xi,t,g} - p_{\xi,t,g} \le \widetilde{\mathrm{rd}}^{\mathrm{dn}}_{\xi,g} \cdot P_{g} + \mathrm{M}_{\xi,g} - \mathrm{M}_{\xi,g} \cdot \mathit{dn}_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \mathrm{ext}_{g} \wedge \neg \left( \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) \wedge \left( \mathrm{rd}_{\xi,t,g} \text{ is defined} \vee \mathrm{rd}^{\mathrm{dn}}_{\xi,g} \text{ is defined} \right) \wedge \left( \mathrm{pos}_{\mathrm{snapshot\_period}(t)}(t) > 0 \vee \mathrm{pos}(t) = 0 \wedge \left( \mathrm{u}^{0}_{\xi,g} = 0 \vee \mathrm{p}^{0}_{\xi,g} \text{ is defined} \right) \right) \wedge \mathrm{on}_{t,g} +\overleftarrow{p}_{\xi,t,g} - p_{\xi,t,g} \le \widetilde{\mathrm{rd}}^{\mathrm{dn}}_{\xi,g} \cdot P_{g} + \mathrm{M}_{\xi,g} - \mathrm{M}_{\xi,g} \cdot \mathit{dn}_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{on}^{\mathrm{com,ext}}_{t,g} \wedge \left( \mathrm{rd}_{\xi,t,g} \text{ is defined} \vee \mathrm{rd}^{\mathrm{dn}}_{\xi,g} \text{ is defined} \right) \wedge \mathrm{prev}_{\xi,t,g} ``` **`Generator_p_ramp_limit_up`** ```math -p_{\xi,t,g} - \overleftarrow{p}_{\xi,t,g} \le \Delta^{+}_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \left( \mathrm{ru}_{\xi,t,g} \text{ is defined} \vee \mathrm{ru}^{\mathrm{up}}_{\xi,g} \text{ is defined} \right) \wedge \neg \left( \mathrm{com}_{g} \wedge \mathrm{ext}_{g} \wedge \neg \left( \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) \right) \wedge \left( \mathrm{pos}_{\mathrm{snapshot\_period}(t)}(t) > 0 \vee \mathrm{pos}(t) = 0 \wedge \left( \mathrm{u}^{0}_{\xi,g} = 0 \vee \mathrm{p}^{0}_{\xi,g} \text{ is defined} \right) \right) \wedge \mathrm{on}_{t,g} +p_{\xi,t,g} - \overleftarrow{p}_{\xi,t,g} \le \Delta^{+}_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \left( \mathrm{ru}_{\xi,t,g} \text{ is defined} \vee \mathrm{ru}^{\mathrm{up}}_{\xi,g} \text{ is defined} \right) \wedge \neg \left( \mathrm{com}_{g} \wedge \mathrm{ext}_{g} \wedge \neg \left( \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) \right) \wedge \mathrm{prev}_{\xi,t,g} \wedge \mathrm{on}_{t,g} ``` **`Generator_p_ramp_limit_down`** ```math -\overleftarrow{p}_{\xi,t,g} - p_{\xi,t,g} \le \Delta^{-}_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \left( \mathrm{rd}_{\xi,t,g} \text{ is defined} \vee \mathrm{rd}^{\mathrm{dn}}_{\xi,g} \text{ is defined} \right) \wedge \neg \left( \mathrm{com}_{g} \wedge \mathrm{ext}_{g} \wedge \neg \left( \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) \right) \wedge \left( \mathrm{pos}_{\mathrm{snapshot\_period}(t)}(t) > 0 \vee \mathrm{pos}(t) = 0 \wedge \left( \mathrm{u}^{0}_{\xi,g} = 0 \vee \mathrm{p}^{0}_{\xi,g} \text{ is defined} \right) \right) \wedge \mathrm{on}_{t,g} +\overleftarrow{p}_{\xi,t,g} - p_{\xi,t,g} \le \Delta^{-}_{\xi,t,g} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \left( \mathrm{rd}_{\xi,t,g} \text{ is defined} \vee \mathrm{rd}^{\mathrm{dn}}_{\xi,g} \text{ is defined} \right) \wedge \neg \left( \mathrm{com}_{g} \wedge \mathrm{ext}_{g} \wedge \neg \left( \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) \right) \wedge \mathrm{prev}_{\xi,t,g} \wedge \mathrm{on}_{t,g} ``` #### Definitions @@ -380,6 +395,14 @@ p_{\xi,t,g} - \overleftarrow{p}_{\xi,t,g} \le \Delta^{+}_{\xi,t,g} \qquad \foral \Delta^{-}_{\xi,t,g} = \begin{cases} \widetilde{\mathrm{rd}}_{\xi,t,g} \cdot \widehat{\mathrm{p}}^{\mathrm{nom}}_{\xi,g} \cdot u_{\xi,t,g} + \widetilde{\mathrm{rd}}^{\mathrm{dn}}_{\xi,g} \cdot \widehat{\mathrm{p}}^{\mathrm{nom}}_{\xi,g} \cdot \left( \overleftarrow{u}_{\xi,t,g} - u_{\xi,t,g} \right) & \text{if } \mathrm{com}_{g} \\ \widetilde{\mathrm{rd}}_{\xi,t,g} \cdot \widetilde{\mathrm{p}}^{\mathrm{nom}}_{\xi,g} & \text{otherwise} \end{cases} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} ``` +#### Masks + +**`Generator_ramps_from_previous`** + +```math +\mathrm{prev}_{\xi,t,g} \iff \mathrm{pos}_{\mathrm{snapshot\_period}(t)}(t) > 0 \vee \mathrm{pos}(t) = 0 \wedge \left( \mathrm{u}^{0}_{\xi,g} = 0 \vee \mathrm{p}^{0}_{\xi,g} \text{ is defined} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ g \in \mathcal{G} +``` + #### Assumptions **`Generator_came_in_running_unless_committable`** diff --git a/docs/examples/pypsa/line.md b/docs/examples/pypsa/line.md index 7d980d8f..d78db392 100644 --- a/docs/examples/pypsa/line.md +++ b/docs/examples/pypsa/line.md @@ -145,7 +145,7 @@ variables: cost and held up by the cuts; absent, and zero in the balance, where the network is lossless dims: [scenario, snapshot, line] - where: transmission_losses AND Line_active + where: Line_lossy absence: zero bounds: lower: 0 @@ -206,6 +206,13 @@ expressions: expression: sum(Line_s * Line_cycle_weight, over=line) adds_to: Cycle_angle_sum +masks: + Line_lossy: + description: >- + a line that stands in the snapshot's period, where the run models + transmission losses + where: transmission_losses AND Line_active + constraints: 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" @@ -250,7 +257,7 @@ constraints: Line_loss_upper: description: "`Line-loss_upper` — a line dissipates at most the loss at its rating" dims: [scenario, snapshot, line] - where: transmission_losses AND Line_active + where: Line_lossy expression: Line_loss <= Line_loss_max Line_loss_tangents_forward: description: >- @@ -259,14 +266,14 @@ constraints: `k`, or one row stacked over its `secant` axis, and this block states them all over the segment dimension dims: [scenario, snapshot, line, segment] - where: transmission_losses AND Line_active + where: Line_lossy expression: Line_loss + Line_loss_slope * Line_s >= Line_loss_offset Line_loss_tangents_reverse: description: >- `Line-loss_tangents-{k}--1`, `Line-loss_secants-neg` — the same fan mirrored, the loss depending on the flow's magnitude dims: [scenario, snapshot, line, segment] - where: transmission_losses AND Line_active + where: Line_lossy expression: Line_loss - Line_loss_slope * Line_s >= Line_loss_offset ``` @@ -342,6 +349,12 @@ constraints: | $`\mathit{Line\_injection}`$ | `Line_injection` over $`\Xi \times \mathcal{T} \times \mathcal{N}`$ | | $`\mathit{Line\_angle\_sum}`$ | `Line_angle_sum` over $`\Xi \times \mathcal{T} \times \mathcal{C}`$ | +#### Masks + +| Symbol | Meaning | +|---|---| +| $`\mathrm{on}^{s,\mathrm{lossy}}`$ | `Line_lossy` over $`\mathcal{T} \times \mathcal{K}`$ — a line that stands in the snapshot's period, where the run models transmission losses | + #### Subject to **`Line_fix_s_lower`** @@ -395,19 +408,19 @@ s_{\xi,t,k} = \mathrm{s}^{\mathrm{set}}_{\xi,t,k} \qquad \forall\, \xi \in \Xi,\ **`Line_loss_upper`** ```math -\ell_{\xi,t,k} \le \overline{\ell}_{\xi,t,k} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ k \in \mathcal{K} \,:\, \mathrm{lossy} \wedge \mathrm{on}^{s}_{t,k} +\ell_{\xi,t,k} \le \overline{\ell}_{\xi,t,k} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ k \in \mathcal{K} \,:\, \mathrm{on}^{s,\mathrm{lossy}}_{t,k} ``` **`Line_loss_tangents_forward`** ```math -\ell_{\xi,t,k} + \mathrm{a}_{\xi,t,k,e} \cdot s_{\xi,t,k} \ge \mathrm{b}_{\xi,t,k,e} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ k \in \mathcal{K},\ e \in \mathcal{E} \,:\, \mathrm{lossy} \wedge \mathrm{on}^{s}_{t,k} +\ell_{\xi,t,k} + \mathrm{a}_{\xi,t,k,e} \cdot s_{\xi,t,k} \ge \mathrm{b}_{\xi,t,k,e} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ k \in \mathcal{K},\ e \in \mathcal{E} \,:\, \mathrm{on}^{s,\mathrm{lossy}}_{t,k} ``` **`Line_loss_tangents_reverse`** ```math -\ell_{\xi,t,k} - \mathrm{a}_{\xi,t,k,e} \cdot s_{\xi,t,k} \ge \mathrm{b}_{\xi,t,k,e} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ k \in \mathcal{K},\ e \in \mathcal{E} \,:\, \mathrm{lossy} \wedge \mathrm{on}^{s}_{t,k} +\ell_{\xi,t,k} - \mathrm{a}_{\xi,t,k,e} \cdot s_{\xi,t,k} \ge \mathrm{b}_{\xi,t,k,e} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ k \in \mathcal{K},\ e \in \mathcal{E} \,:\, \mathrm{on}^{s,\mathrm{lossy}}_{t,k} ``` #### Definitions @@ -460,6 +473,14 @@ s_{\xi,t,k} = \mathrm{s}^{\mathrm{set}}_{\xi,t,k} \qquad \forall\, \xi \in \Xi,\ \mathit{Line\_angle\_sum}_{\xi,t,c} = \sum_{k \in \mathcal{K}} s_{\xi,t,k} \cdot \mathrm{x}_{k,c} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ c \in \mathcal{C} ``` +#### Masks + +**`Line_lossy`** + +```math +\mathrm{on}^{s,\mathrm{lossy}}_{t,k} \iff \mathrm{lossy} \wedge \mathrm{on}^{s}_{t,k} \qquad \forall\, t \in \mathcal{T},\ k \in \mathcal{K} +``` + #### Variable domains **`Line_s`** @@ -471,7 +492,7 @@ s_{\xi,t,k} \in \mathbb{R} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ k **`Line_loss`** ```math -\ell_{\xi,t,k} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ k \in \mathcal{K} \,:\, \mathrm{lossy} \wedge \mathrm{on}^{s}_{t,k} +\ell_{\xi,t,k} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ k \in \mathcal{K} \,:\, \mathrm{on}^{s,\mathrm{lossy}}_{t,k} ``` **`Line_s_nom_ext`** diff --git a/docs/examples/pypsa/link.md b/docs/examples/pypsa/link.md index 77f20182..966f8e9f 100644 --- a/docs/examples/pypsa/link.md +++ b/docs/examples/pypsa/link.md @@ -270,6 +270,13 @@ expressions: + sum(Link_output_arrival, by=Link_output_bus, over=link_output, into=bus) adds_to: Bus_injection +masks: + Link_committed: + description: >- + a committable link that stands in the snapshot's period — every + unit-commitment row's set + where: Link_committable AND Link_active + constraints: Link_fix_p_lower: description: "`Link-fix-p-lower` — a fixed link carries at least its minimum, negative for the other way" @@ -412,6 +419,12 @@ assumptions: | $`\mathit{Link\_additions}`$ | `Link_additions` over $`\mathcal{Y} \times \mathcal{I}`$ | | $`\mathit{Link\_injection}`$ | `Link_injection` over $`\Xi \times \mathcal{T} \times \mathcal{N}`$ | +#### Masks + +| Symbol | Meaning | +|---|---| +| $`\mathrm{on}^{f,\mathrm{com}}`$ | `Link_committed` over $`\mathcal{T} \times \mathcal{L}`$ — a committable link that stands in the snapshot's period — every unit-commitment row's set | + $`t \ominus k`$ denotes cyclic translation: index $`t-k`$ taken modulo the size of the dimension (`roll`). Plain $`t-k`$ (`shift`) has no wraparound — terms translated past the edge are simply absent. $`t \boxminus_{v} k`$ denotes translation with $`v`$ standing where index $`t-k`$ leaves the dimension (`shift(edge=v)`), so the row at that boundary is built and carries $`v`$ rather than being dropped. @@ -536,6 +549,14 @@ f_{\xi,t,l} = \mathrm{f}^{\mathrm{set}}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ \mathit{Link\_injection}_{\xi,t,n} = -\left( \sum_{l \in \mathcal{L} \,:\, \mathrm{Link\_bus0}(l) = n} f_{\xi,t,l} \right) + \sum_{o \in \mathcal{O} \,:\, \mathrm{Link\_output\_bus}(o) = n} \overrightarrow{f}_{\xi,t,o} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ n \in \mathcal{N} ``` +#### Masks + +**`Link_committed`** + +```math +\mathrm{on}^{f,\mathrm{com}}_{t,l} \iff \mathrm{com}^{f}_{l} \wedge \mathrm{on}^{f}_{t,l} \qquad \forall\, t \in \mathcal{T},\ l \in \mathcal{L} +``` + #### Variable domains **`Link_p`** diff --git a/docs/examples/pypsa/link_commitment.md b/docs/examples/pypsa/link_commitment.md index 0f76a073..fbdec69e 100644 --- a/docs/examples/pypsa/link_commitment.md +++ b/docs/examples/pypsa/link_commitment.md @@ -78,21 +78,21 @@ variables: the rows below cap at one, or at the module count where the build is modular dims: [scenario, snapshot, link] - where: Link_committable AND Link_active + where: Link_committed domain: integer bounds: lower: 0 Link_start_up: description: "`Link-start_up` — how much of a committable link turns on this snapshot, capped as the status is" dims: [scenario, snapshot, link] - where: Link_committable AND Link_active + where: Link_committed domain: integer bounds: lower: 0 Link_shut_down: description: "`Link-shut_down` — how much of a committable link turns off this snapshot, capped as the status is" dims: [scenario, snapshot, link] - where: Link_committable AND Link_active + where: Link_committed domain: integer bounds: lower: 0 @@ -118,6 +118,8 @@ given: Link_p_nom_ext: { dims: [link] } expressions: scenario_opex: { dims: [scenario] } + masks: + Link_committed: { dims: [snapshot, link] } expressions: Link_previous_status: @@ -135,26 +137,38 @@ expressions: + sum(sum(Link_shut_down * Link_shut_down_cost, over=link), over=snapshot) adds_to: scenario_opex +masks: + Link_com_ext: + description: >- + a committable link with an extendable, non-modular build that stands in + the snapshot's period — PyPSA's `com-ext` rows, whose status is relaxed + against the chosen build + where: >- + Link_committable + AND Link_p_nom_extendable + AND NOT (Link_p_nom_mod > 0) + AND Link_active + constraints: Link_com_p_lower: description: "`Link-com-p-lower` — a committed link flows at least its minimum; off, at least nothing" dims: [scenario, snapshot, link] - where: Link_committable AND not Link_p_nom_extendable AND Link_active + where: Link_committed AND NOT Link_p_nom_extendable expression: Link_p >= Link_p_min_pu * Link_p_nom * (Link_status - Link_maintenance_pu * Link_maintenance_status) Link_com_p_upper: description: "`Link-com-p-upper` — a committed link flows at most what is available; off, at most nothing" dims: [scenario, snapshot, link] - where: Link_committable AND not Link_p_nom_extendable AND Link_active + where: Link_committed AND NOT Link_p_nom_extendable expression: Link_p <= Link_p_max_pu * Link_p_nom * (Link_status - Link_maintenance_pu * Link_maintenance_status) Link_com_transition_start_up: description: "`Link-com-transition-start-up` — turning on is a start, counted against the state the link carried into the snapshot" dims: [scenario, snapshot, link] - where: Link_committable AND Link_active + where: Link_committed expression: Link_start_up >= Link_status - Link_previous_status Link_com_transition_shut_down: description: "`Link-com-transition-shut-down` — turning off is a stop, counted against the state the link carried into the snapshot" dims: [scenario, snapshot, link] - where: Link_committable AND Link_active + where: Link_committed expression: Link_shut_down >= Link_previous_status - Link_status Link_com_up_time: description: >- @@ -162,7 +176,7 @@ constraints: 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 dims: [scenario, snapshot, link] - where: Link_committable AND Link_min_up_time > 0 AND position(snapshot) > 0 AND Link_active + where: Link_committed AND Link_min_up_time > 0 AND position(snapshot) > 0 expression: sum_back(Link_start_up, along=snapshot, window=Link_min_up_time) <= Link_status Link_com_down_time: description: >- @@ -170,38 +184,38 @@ constraints: time is still off. The first snapshot's share of the window is the brought-in down time's, which the must-stay-down mask carries dims: [scenario, snapshot, link] - where: Link_committable AND Link_min_down_time > 0 AND position(snapshot) > 0 AND Link_active + where: Link_committed AND Link_min_down_time > 0 AND position(snapshot) > 0 expression: sum_back(Link_shut_down, along=snapshot, window=Link_min_down_time) <= 1 - Link_status Link_com_status_must_stay_up: description: "`Link-com-status-min_up_time_must_stay_up` — a link still serving the up time it brought in stays on" dims: [scenario, snapshot, link] - where: Link_committable AND Link_must_stay_up AND Link_active + where: Link_committed AND Link_must_stay_up expression: Link_status == 1 Link_com_status_must_stay_down: description: >- `Link-com-status-min_down_time_must_stay_up` — a link still serving the down time it brought in stays off; PyPSA names the row `_must_stay_up` dims: [scenario, snapshot, link] - where: Link_committable AND Link_must_stay_down AND Link_active + where: Link_committed AND Link_must_stay_down expression: Link_status == 0 Link_com_ext_p_upper_cap: description: >- `Link-com-ext-p-upper-cap` — a committed extendable link flows at most what is available of the chosen build, whatever its status dims: [scenario, snapshot, link] - where: Link_committable AND Link_p_nom_extendable AND NOT (Link_p_nom_mod > 0) AND Link_active + where: Link_com_ext expression: Link_p <= Link_p_max_pu * (Link_p_nom_ext - Link_maintenance_pu * Link_maintenance_capacity) Link_com_ext_p_upper_big_m: description: "`Link-com-ext-p-upper-bigM` — off, a link flows nothing; on, the big M is no bound" dims: [scenario, snapshot, link] - where: Link_committable AND Link_p_nom_extendable AND NOT (Link_p_nom_mod > 0) AND Link_active + where: Link_com_ext expression: Link_p <= Link_big_m * Link_status Link_com_ext_p_lower: description: >- `Link-com-ext-p-lower` — a committed extendable link flows at least its minimum of the chosen build; off, the big M releases the row dims: [scenario, snapshot, link] - where: Link_committable AND Link_p_nom_extendable AND NOT (Link_p_nom_mod > 0) AND Link_active + where: Link_com_ext expression: >- Link_p >= Link_p_min_pu * (Link_p_nom_ext - Link_maintenance_pu * Link_maintenance_capacity) @@ -212,23 +226,21 @@ constraints: negative, flow is also plainly non-negative, a row the big-M lower cannot assert while the link is off dims: [scenario, snapshot, link] - where: >- - Link_committable AND Link_p_nom_extendable - AND Link_p_min_pu_nonneg AND NOT (Link_p_nom_mod > 0) AND Link_active + where: Link_com_ext AND Link_p_min_pu_nonneg expression: Link_p >= 0 Link_com_mod_p_lower: description: >- `Link-com-mod-p-lower` — a committed modular link flows at least its minimum of one module, whether the build is fixed or a decision dims: [scenario, snapshot, link] - where: Link_committable AND Link_p_nom_mod > 0 AND Link_active + where: Link_committed AND Link_p_nom_mod > 0 expression: Link_p >= Link_p_min_pu * Link_p_nom_mod * (Link_status - Link_maintenance_pu * Link_maintenance_status) Link_com_mod_p_upper: description: >- `Link-com-mod-p-upper` — a committed modular link flows at most one module's share, whether the build is fixed or a decision dims: [scenario, snapshot, link] - where: Link_committable AND Link_p_nom_mod > 0 AND Link_active + where: Link_committed AND Link_p_nom_mod > 0 expression: Link_p <= Link_p_max_pu * Link_p_nom_mod * (Link_status - Link_maintenance_pu * Link_maintenance_status) Link_status_p_fixed_upper: description: >- @@ -236,7 +248,7 @@ constraints: 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 dims: [scenario, snapshot, link] - where: Link_committable AND NOT (Link_p_nom_extendable AND Link_p_nom_mod > 0) AND Link_active + where: Link_committed AND NOT (Link_p_nom_extendable AND Link_p_nom_mod > 0) expression: Link_status <= Link_modules_installed Link_start_up_p_fixed_upper: description: >- @@ -244,7 +256,7 @@ constraints: 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 dims: [scenario, snapshot, link] - where: Link_committable AND NOT (Link_p_nom_extendable AND Link_p_nom_mod > 0) AND Link_active + where: Link_committed AND NOT (Link_p_nom_extendable AND Link_p_nom_mod > 0) expression: Link_start_up <= Link_modules_installed Link_shut_down_p_fixed_upper: description: >- @@ -252,22 +264,22 @@ constraints: 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 dims: [scenario, snapshot, link] - where: Link_committable AND NOT (Link_p_nom_extendable AND Link_p_nom_mod > 0) AND Link_active + where: Link_committed AND NOT (Link_p_nom_extendable AND Link_p_nom_mod > 0) expression: Link_shut_down <= Link_modules_installed Link_status_p_nom_variable_upper: description: "`Link-status-p_nom-variable-upper` — a modular link is on only where a module is built" dims: [scenario, snapshot, link] - where: Link_committable AND Link_p_nom_extendable AND Link_p_nom_mod > 0 AND Link_active + where: Link_committed AND Link_p_nom_extendable AND Link_p_nom_mod > 0 expression: Link_status <= Link_n_mod Link_start_up_p_nom_variable_upper: description: "`Link-start_up-p_nom-variable-upper` — a modular link starts only where a module is built" dims: [scenario, snapshot, link] - where: Link_committable AND Link_p_nom_extendable AND Link_p_nom_mod > 0 AND Link_active + where: Link_committed AND Link_p_nom_extendable AND Link_p_nom_mod > 0 expression: Link_start_up <= Link_n_mod Link_shut_down_p_nom_variable_upper: description: "`Link-shut_down-p_nom-variable-upper` — a modular link stops only where a module is built" dims: [scenario, snapshot, link] - where: Link_committable AND Link_p_nom_extendable AND Link_p_nom_mod > 0 AND Link_active + where: Link_committed AND Link_p_nom_extendable AND Link_p_nom_mod > 0 expression: Link_shut_down <= Link_n_mod ``` @@ -324,6 +336,7 @@ constraints: | $`\mu^{f,u}`$ | `Link_maintenance_status` over $`\Xi \times \mathcal{T} \times \mathcal{L}`$ | | $`F`$ | `Link_p_nom_ext` over $`\mathcal{L}`$ | | $`\mathit{scenario\_opex}`$ | `scenario_opex` over $`\Xi`$, an expression this file adds `Link_commitment_opex` to | +| $`\mathrm{on}^{f,\mathrm{com}}`$ | `Link_committed` over $`\mathcal{T} \times \mathcal{L}`$, a mask another file defines | #### Definitions @@ -332,6 +345,12 @@ constraints: | $`\overleftarrow{u}^{f}`$ | `Link_previous_status` over $`\Xi \times \mathcal{T} \times \mathcal{L}`$ — the commitment state a link carries into a snapshot — the state it brought into the horizon at the first, the previous snapshot's after that | | $`\mathit{Link\_commitment\_opex}`$ | `Link_commitment_opex` over $`\Xi`$ | +#### Masks + +| Symbol | Meaning | +|---|---| +| $`\mathrm{on}^{f,\mathrm{com,ext}}`$ | `Link_com_ext` over $`\mathcal{T} \times \mathcal{L}`$ — a committable link with an extendable, non-modular build that stands in the snapshot's period — PyPSA's `com-ext` rows, whose status is relaxed against the chosen build | + $`\mathrm{pos}(t)`$ denotes where index $`t`$ sits along its dimension's own order — the order `shift` steps along, not the order labels sort in — counted from $`0`$. The index itself stays the coordinate, so $`t`$ compares against labels and $`\mathrm{pos}(t)`$ against positions. #### Subject to @@ -339,121 +358,121 @@ $`\mathrm{pos}(t)`$ denotes where index $`t`$ sits along its dimension's own ord **`Link_com_p_lower`** ```math -f_{\xi,t,l} \ge \underline{\mathrm{f}}_{\xi,t,l} \cdot \mathrm{f}^{\mathrm{nom}}_{\xi,l} \cdot \left( u^{f}_{\xi,t,l} - \gamma^{f}_{\xi,l} \cdot \mu^{f,u}_{\xi,t,l} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \neg \mathrm{ext}^{f}_{l} \wedge \mathrm{on}^{f}_{t,l} +f_{\xi,t,l} \ge \underline{\mathrm{f}}_{\xi,t,l} \cdot \mathrm{f}^{\mathrm{nom}}_{\xi,l} \cdot \left( u^{f}_{\xi,t,l} - \gamma^{f}_{\xi,l} \cdot \mu^{f,u}_{\xi,t,l} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com}}_{t,l} \wedge \neg \mathrm{ext}^{f}_{l} ``` **`Link_com_p_upper`** ```math -f_{\xi,t,l} \le \overline{\mathrm{f}}_{\xi,t,l} \cdot \mathrm{f}^{\mathrm{nom}}_{\xi,l} \cdot \left( u^{f}_{\xi,t,l} - \gamma^{f}_{\xi,l} \cdot \mu^{f,u}_{\xi,t,l} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \neg \mathrm{ext}^{f}_{l} \wedge \mathrm{on}^{f}_{t,l} +f_{\xi,t,l} \le \overline{\mathrm{f}}_{\xi,t,l} \cdot \mathrm{f}^{\mathrm{nom}}_{\xi,l} \cdot \left( u^{f}_{\xi,t,l} - \gamma^{f}_{\xi,l} \cdot \mu^{f,u}_{\xi,t,l} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com}}_{t,l} \wedge \neg \mathrm{ext}^{f}_{l} ``` **`Link_com_transition_start_up`** ```math -\mathit{up}^{f}_{\xi,t,l} \ge u^{f}_{\xi,t,l} - \overleftarrow{u}^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \mathrm{on}^{f}_{t,l} +\mathit{up}^{f}_{\xi,t,l} \ge u^{f}_{\xi,t,l} - \overleftarrow{u}^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com}}_{t,l} ``` **`Link_com_transition_shut_down`** ```math -\mathit{dn}^{f}_{\xi,t,l} \ge \overleftarrow{u}^{f}_{\xi,t,l} - u^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \mathrm{on}^{f}_{t,l} +\mathit{dn}^{f}_{\xi,t,l} \ge \overleftarrow{u}^{f}_{\xi,t,l} - u^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com}}_{t,l} ``` **`Link_com_up_time`** ```math -\sum_{t' \in \mathcal{T} \,:\, 0 \le t - t' < \mathrm{UT}^{f}} \mathit{up}^{f}_{\xi,t',l} \le u^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \mathrm{UT}^{f}_{\xi,l} > 0 \wedge \mathrm{pos}(t) > 0 \wedge \mathrm{on}^{f}_{t,l} +\sum_{t' \in \mathcal{T} \,:\, 0 \le t - t' < \mathrm{UT}^{f}} \mathit{up}^{f}_{\xi,t',l} \le u^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com}}_{t,l} \wedge \mathrm{UT}^{f}_{\xi,l} > 0 \wedge \mathrm{pos}(t) > 0 ``` **`Link_com_down_time`** ```math -\sum_{t' \in \mathcal{T} \,:\, 0 \le t - t' < \mathrm{DT}^{f}} \mathit{dn}^{f}_{\xi,t',l} \le 1 - u^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \mathrm{DT}^{f}_{\xi,l} > 0 \wedge \mathrm{pos}(t) > 0 \wedge \mathrm{on}^{f}_{t,l} +\sum_{t' \in \mathcal{T} \,:\, 0 \le t - t' < \mathrm{DT}^{f}} \mathit{dn}^{f}_{\xi,t',l} \le 1 - u^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com}}_{t,l} \wedge \mathrm{DT}^{f}_{\xi,l} > 0 \wedge \mathrm{pos}(t) > 0 ``` **`Link_com_status_must_stay_up`** ```math -u^{f}_{\xi,t,l} = 1 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \mathrm{hold}^{f}_{\xi,t,l} \wedge \mathrm{on}^{f}_{t,l} +u^{f}_{\xi,t,l} = 1 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com}}_{t,l} \wedge \mathrm{hold}^{f}_{\xi,t,l} ``` **`Link_com_status_must_stay_down`** ```math -u^{f}_{\xi,t,l} = 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \mathrm{rest}^{f}_{\xi,t,l} \wedge \mathrm{on}^{f}_{t,l} +u^{f}_{\xi,t,l} = 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com}}_{t,l} \wedge \mathrm{rest}^{f}_{\xi,t,l} ``` **`Link_com_ext_p_upper_cap`** ```math -f_{\xi,t,l} \le \overline{\mathrm{f}}_{\xi,t,l} \cdot \left( F_{l} - \gamma^{f}_{\xi,l} \cdot \mu^{f,\mathrm{nom}}_{\xi,t,l} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \mathrm{ext}^{f}_{l} \wedge \neg \left( \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) \wedge \mathrm{on}^{f}_{t,l} +f_{\xi,t,l} \le \overline{\mathrm{f}}_{\xi,t,l} \cdot \left( F_{l} - \gamma^{f}_{\xi,l} \cdot \mu^{f,\mathrm{nom}}_{\xi,t,l} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com,ext}}_{t,l} ``` **`Link_com_ext_p_upper_big_m`** ```math -f_{\xi,t,l} \le \mathrm{M}^{f}_{\xi,l} \cdot u^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \mathrm{ext}^{f}_{l} \wedge \neg \left( \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) \wedge \mathrm{on}^{f}_{t,l} +f_{\xi,t,l} \le \mathrm{M}^{f}_{\xi,l} \cdot u^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com,ext}}_{t,l} ``` **`Link_com_ext_p_lower`** ```math -f_{\xi,t,l} \ge \underline{\mathrm{f}}_{\xi,t,l} \cdot \left( F_{l} - \gamma^{f}_{\xi,l} \cdot \mu^{f,\mathrm{nom}}_{\xi,t,l} \right) + \mathrm{M}^{f}_{\xi,l} \cdot u^{f}_{\xi,t,l} - \mathrm{M}^{f}_{\xi,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \mathrm{ext}^{f}_{l} \wedge \neg \left( \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) \wedge \mathrm{on}^{f}_{t,l} +f_{\xi,t,l} \ge \underline{\mathrm{f}}_{\xi,t,l} \cdot \left( F_{l} - \gamma^{f}_{\xi,l} \cdot \mu^{f,\mathrm{nom}}_{\xi,t,l} \right) + \mathrm{M}^{f}_{\xi,l} \cdot u^{f}_{\xi,t,l} - \mathrm{M}^{f}_{\xi,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com,ext}}_{t,l} ``` **`Link_com_ext_p_lower_nonneg`** ```math -f_{\xi,t,l} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \mathrm{ext}^{f}_{l} \wedge \mathrm{nonneg}^{f}_{l} \wedge \neg \left( \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) \wedge \mathrm{on}^{f}_{t,l} +f_{\xi,t,l} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com,ext}}_{t,l} \wedge \mathrm{nonneg}^{f}_{l} ``` **`Link_com_mod_p_lower`** ```math -f_{\xi,t,l} \ge \underline{\mathrm{f}}_{\xi,t,l} \cdot \mathrm{f}^{\mathrm{mod}}_{l} \cdot \left( u^{f}_{\xi,t,l} - \gamma^{f}_{\xi,l} \cdot \mu^{f,u}_{\xi,t,l} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 \wedge \mathrm{on}^{f}_{t,l} +f_{\xi,t,l} \ge \underline{\mathrm{f}}_{\xi,t,l} \cdot \mathrm{f}^{\mathrm{mod}}_{l} \cdot \left( u^{f}_{\xi,t,l} - \gamma^{f}_{\xi,l} \cdot \mu^{f,u}_{\xi,t,l} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com}}_{t,l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 ``` **`Link_com_mod_p_upper`** ```math -f_{\xi,t,l} \le \overline{\mathrm{f}}_{\xi,t,l} \cdot \mathrm{f}^{\mathrm{mod}}_{l} \cdot \left( u^{f}_{\xi,t,l} - \gamma^{f}_{\xi,l} \cdot \mu^{f,u}_{\xi,t,l} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 \wedge \mathrm{on}^{f}_{t,l} +f_{\xi,t,l} \le \overline{\mathrm{f}}_{\xi,t,l} \cdot \mathrm{f}^{\mathrm{mod}}_{l} \cdot \left( u^{f}_{\xi,t,l} - \gamma^{f}_{\xi,l} \cdot \mu^{f,u}_{\xi,t,l} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com}}_{t,l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 ``` **`Link_status_p_fixed_upper`** ```math -u^{f}_{\xi,t,l} \le \mathrm{N}^{f,\mathrm{fix}}_{\xi,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \neg \left( \mathrm{ext}^{f}_{l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) \wedge \mathrm{on}^{f}_{t,l} +u^{f}_{\xi,t,l} \le \mathrm{N}^{f,\mathrm{fix}}_{\xi,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com}}_{t,l} \wedge \neg \left( \mathrm{ext}^{f}_{l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) ``` **`Link_start_up_p_fixed_upper`** ```math -\mathit{up}^{f}_{\xi,t,l} \le \mathrm{N}^{f,\mathrm{fix}}_{\xi,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \neg \left( \mathrm{ext}^{f}_{l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) \wedge \mathrm{on}^{f}_{t,l} +\mathit{up}^{f}_{\xi,t,l} \le \mathrm{N}^{f,\mathrm{fix}}_{\xi,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com}}_{t,l} \wedge \neg \left( \mathrm{ext}^{f}_{l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) ``` **`Link_shut_down_p_fixed_upper`** ```math -\mathit{dn}^{f}_{\xi,t,l} \le \mathrm{N}^{f,\mathrm{fix}}_{\xi,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \neg \left( \mathrm{ext}^{f}_{l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) \wedge \mathrm{on}^{f}_{t,l} +\mathit{dn}^{f}_{\xi,t,l} \le \mathrm{N}^{f,\mathrm{fix}}_{\xi,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com}}_{t,l} \wedge \neg \left( \mathrm{ext}^{f}_{l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) ``` **`Link_status_p_nom_variable_upper`** ```math -u^{f}_{\xi,t,l} \le N^{f}_{l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \mathrm{ext}^{f}_{l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 \wedge \mathrm{on}^{f}_{t,l} +u^{f}_{\xi,t,l} \le N^{f}_{l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com}}_{t,l} \wedge \mathrm{ext}^{f}_{l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 ``` **`Link_start_up_p_nom_variable_upper`** ```math -\mathit{up}^{f}_{\xi,t,l} \le N^{f}_{l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \mathrm{ext}^{f}_{l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 \wedge \mathrm{on}^{f}_{t,l} +\mathit{up}^{f}_{\xi,t,l} \le N^{f}_{l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com}}_{t,l} \wedge \mathrm{ext}^{f}_{l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 ``` **`Link_shut_down_p_nom_variable_upper`** ```math -\mathit{dn}^{f}_{\xi,t,l} \le N^{f}_{l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \mathrm{ext}^{f}_{l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 \wedge \mathrm{on}^{f}_{t,l} +\mathit{dn}^{f}_{\xi,t,l} \le N^{f}_{l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com}}_{t,l} \wedge \mathrm{ext}^{f}_{l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 ``` #### Definitions @@ -470,23 +489,31 @@ u^{f}_{\xi,t,l} \le N^{f}_{l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ \mathit{Link\_commitment\_opex}_{\xi} = \sum_{t \in \mathcal{T}} \sum_{l \in \mathcal{L}} u^{f}_{\xi,t,l} \cdot \mathrm{c}^{f,\mathrm{on}}_{\xi,t,l} \cdot \mathrm{w}_{t} \cdot \mathrm{w}^{y}_{\mathrm{snapshot\_period}(t)} + \sum_{t \in \mathcal{T}} \sum_{l \in \mathcal{L}} \mathit{up}^{f}_{\xi,t,l} \cdot \mathrm{c}^{f,\mathrm{up}}_{\xi,l} + \sum_{t \in \mathcal{T}} \sum_{l \in \mathcal{L}} \mathit{dn}^{f}_{\xi,t,l} \cdot \mathrm{c}^{f,\mathrm{dn}}_{\xi,l} \qquad \forall\, \xi \in \Xi ``` +#### Masks + +**`Link_com_ext`** + +```math +\mathrm{on}^{f,\mathrm{com,ext}}_{t,l} \iff \mathrm{com}^{f}_{l} \wedge \mathrm{ext}^{f}_{l} \wedge \neg \left( \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) \wedge \mathrm{on}^{f}_{t,l} \qquad \forall\, t \in \mathcal{T},\ l \in \mathcal{L} +``` + #### Variable domains **`Link_status`** ```math -u^{f}_{\xi,t,l} \ge 0, u^{f}_{\xi,t,l} \in \mathbb{Z} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \mathrm{on}^{f}_{t,l} +u^{f}_{\xi,t,l} \ge 0, u^{f}_{\xi,t,l} \in \mathbb{Z} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com}}_{t,l} ``` **`Link_start_up`** ```math -\mathit{up}^{f}_{\xi,t,l} \ge 0, \mathit{up}^{f}_{\xi,t,l} \in \mathbb{Z} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \mathrm{on}^{f}_{t,l} +\mathit{up}^{f}_{\xi,t,l} \ge 0, \mathit{up}^{f}_{\xi,t,l} \in \mathbb{Z} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com}}_{t,l} ``` **`Link_shut_down`** ```math -\mathit{dn}^{f}_{\xi,t,l} \ge 0, \mathit{dn}^{f}_{\xi,t,l} \in \mathbb{Z} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \mathrm{on}^{f}_{t,l} +\mathit{dn}^{f}_{\xi,t,l} \ge 0, \mathit{dn}^{f}_{\xi,t,l} \in \mathbb{Z} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com}}_{t,l} ``` diff --git a/docs/examples/pypsa/link_maintenance.md b/docs/examples/pypsa/link_maintenance.md index d8b5ed3d..419d527d 100644 --- a/docs/examples/pypsa/link_maintenance.md +++ b/docs/examples/pypsa/link_maintenance.md @@ -81,9 +81,7 @@ variables: `Link-maintenance_capacity` — the chosen build while in maintenance, zero otherwise: the product the `maintcap` rows linearize dims: [scenario, snapshot, link] - where: >- - Link_maintainable AND Link_p_nom_extendable - AND NOT (Link_committable AND Link_p_nom_mod > 0) AND Link_active + where: Link_maint_ext absence: zero bounds: lower: 0 @@ -93,9 +91,7 @@ variables: otherwise: the product the `maint-status` rows linearize, so a unit in maintenance may also be off dims: [scenario, snapshot, link] - where: >- - Link_maintainable AND Link_committable - AND NOT (Link_p_nom_extendable AND NOT (Link_p_nom_mod > 0)) AND Link_active + where: Link_maintainable AND Link_committed AND NOT (Link_p_nom_extendable AND NOT (Link_p_nom_mod > 0)) absence: zero bounds: lower: 0 @@ -112,6 +108,20 @@ given: variables: Link_status: { dims: [scenario, snapshot, link], domain: integer } Link_p_nom_ext: { dims: [link] } + masks: + Link_committed: { dims: [snapshot, link] } + +masks: + Link_maint_ext: + description: >- + a maintainable link with an extendable build, unless it is committable + and modular, that stands in the snapshot's period — the maintenance rows + against the chosen build + where: >- + Link_maintainable + AND Link_p_nom_extendable + AND NOT (Link_committable AND Link_p_nom_mod > 0) + AND Link_active constraints: Link_maint_event_count: @@ -137,54 +147,54 @@ constraints: `Link-maintcap_upper` — the build taken off is at most the chosen build in maintenance, and at most the build less its floor out of it dims: [scenario, snapshot, link] - where: Link_maintainable AND Link_p_nom_extendable AND NOT (Link_committable AND Link_p_nom_mod > 0) AND Link_active + where: Link_maint_ext expression: Link_maintenance_capacity <= Link_p_nom_ext - Link_p_nom_min * (1 - Link_maintenance) Link_maintcap_upper_nommax: description: "`Link-maintcap_upper_nommax` — out of maintenance, no build is taken off" dims: [scenario, snapshot, link] - where: Link_maintainable AND Link_p_nom_extendable AND NOT (Link_committable AND Link_p_nom_mod > 0) AND Link_active + where: Link_maint_ext expression: Link_maintenance_capacity <= Link_p_nom_max * Link_maintenance Link_maintcap_lower_nommax: description: "`Link-maintcap_lower_nommax` — in maintenance, the whole chosen build is taken off" dims: [scenario, snapshot, link] - where: Link_maintainable AND Link_p_nom_extendable AND NOT (Link_committable AND Link_p_nom_mod > 0) AND Link_active + where: Link_maint_ext expression: Link_maintenance_capacity >= Link_p_nom_ext - Link_p_nom_max * (1 - Link_maintenance) Link_maintcap_lower_nommin: description: "`Link-maintcap_lower_nommin` — in maintenance, at least the floor of the build is taken off" dims: [scenario, snapshot, link] - where: Link_maintainable AND Link_p_nom_extendable AND NOT (Link_committable AND Link_p_nom_mod > 0) AND Link_active AND Link_p_nom_min > 0 + where: Link_maint_ext AND Link_p_nom_min > 0 expression: Link_maintenance_capacity >= Link_p_nom_min * Link_maintenance Link_maint_status_le_status: description: "`Link-maint-status-le-status` — the status in maintenance is at most the status" dims: [scenario, snapshot, link] - where: Link_maintainable AND Link_committable AND NOT Link_p_nom_extendable AND Link_active + where: Link_maintainable AND Link_committed AND NOT Link_p_nom_extendable expression: Link_maintenance_status <= Link_status Link_maint_status_le_maint: description: "`Link-maint-status-le-maint` — out of maintenance, the status in maintenance is zero" dims: [scenario, snapshot, link] - where: Link_maintainable AND Link_committable AND NOT Link_p_nom_extendable AND Link_active + where: Link_maintainable AND Link_committed AND NOT Link_p_nom_extendable expression: Link_maintenance_status <= Link_maintenance Link_maint_status_lb: description: "`Link-maint-status-lb` — on and in maintenance, the status in maintenance is one" dims: [scenario, snapshot, link] - where: Link_maintainable AND Link_committable AND NOT Link_p_nom_extendable AND Link_active + where: Link_maintainable AND Link_committed AND NOT Link_p_nom_extendable expression: Link_maintenance_status >= Link_status + Link_maintenance - 1 Link_maint_modstatus_le_status: description: "`Link-maint-modstatus-le-status` — the modules on in maintenance are at most the modules on" dims: [scenario, snapshot, link] - where: Link_maintainable AND Link_committable AND Link_p_nom_mod > 0 AND Link_active + where: Link_maintainable AND Link_committed AND Link_p_nom_mod > 0 expression: Link_maintenance_status <= Link_status Link_maint_modstatus_le_maint: description: >- `Link-maint-modstatus-le-maint` — out of maintenance, no module is on in maintenance; in it, at most the modules the build cap holds dims: [scenario, snapshot, link] - where: Link_maintainable AND Link_committable AND Link_p_nom_mod > 0 AND Link_active + where: Link_maintainable AND Link_committed AND Link_p_nom_mod > 0 expression: Link_maintenance_status <= Link_p_nom_max / Link_p_nom_mod * Link_maintenance Link_maint_modstatus_lb: description: "`Link-maint-modstatus-lb` — in maintenance, every module on is on in maintenance" dims: [scenario, snapshot, link] - where: Link_maintainable AND Link_committable AND Link_p_nom_mod > 0 AND Link_active + where: Link_maintainable AND Link_committed AND Link_p_nom_mod > 0 expression: Link_maintenance_status >= Link_status - Link_p_nom_max / Link_p_nom_mod * (1 - Link_maintenance) assumptions: @@ -271,6 +281,13 @@ assumptions: | $`\overline{\mathrm{f}}^{\mathrm{nom}}`$ | `Link_p_nom_max` over $`\Xi \times \mathcal{L}`$, data another file declares | | $`u^{f}`$ | `Link_status` over $`\Xi \times \mathcal{T} \times \mathcal{L}`$ | | $`F`$ | `Link_p_nom_ext` over $`\mathcal{L}`$ | +| $`\mathrm{on}^{f,\mathrm{com}}`$ | `Link_committed` over $`\mathcal{T} \times \mathcal{L}`$, a mask another file defines | + +#### Masks + +| Symbol | Meaning | +|---|---| +| $`\mathrm{on}^{f,\mathrm{mnt,ext}}`$ | `Link_maint_ext` over $`\mathcal{T} \times \mathcal{L}`$ — a maintainable link with an extendable build, unless it is committable and modular, that stands in the snapshot's period — the maintenance rows against the chosen build | #### Subject to @@ -295,61 +312,69 @@ assumptions: **`Link_maintcap_upper`** ```math -\mu^{f,\mathrm{nom}}_{\xi,t,l} \le F_{l} - \underline{\mathrm{f}}^{\mathrm{nom}}_{\xi,l} \cdot \left( 1 - \mu^{f}_{\xi,t,l} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{mnt}^{f}_{l} \wedge \mathrm{ext}^{f}_{l} \wedge \neg \left( \mathrm{com}^{f}_{l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) \wedge \mathrm{on}^{f}_{t,l} +\mu^{f,\mathrm{nom}}_{\xi,t,l} \le F_{l} - \underline{\mathrm{f}}^{\mathrm{nom}}_{\xi,l} \cdot \left( 1 - \mu^{f}_{\xi,t,l} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{mnt,ext}}_{t,l} ``` **`Link_maintcap_upper_nommax`** ```math -\mu^{f,\mathrm{nom}}_{\xi,t,l} \le \overline{\mathrm{f}}^{\mathrm{nom}}_{\xi,l} \cdot \mu^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{mnt}^{f}_{l} \wedge \mathrm{ext}^{f}_{l} \wedge \neg \left( \mathrm{com}^{f}_{l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) \wedge \mathrm{on}^{f}_{t,l} +\mu^{f,\mathrm{nom}}_{\xi,t,l} \le \overline{\mathrm{f}}^{\mathrm{nom}}_{\xi,l} \cdot \mu^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{mnt,ext}}_{t,l} ``` **`Link_maintcap_lower_nommax`** ```math -\mu^{f,\mathrm{nom}}_{\xi,t,l} \ge F_{l} - \overline{\mathrm{f}}^{\mathrm{nom}}_{\xi,l} \cdot \left( 1 - \mu^{f}_{\xi,t,l} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{mnt}^{f}_{l} \wedge \mathrm{ext}^{f}_{l} \wedge \neg \left( \mathrm{com}^{f}_{l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) \wedge \mathrm{on}^{f}_{t,l} +\mu^{f,\mathrm{nom}}_{\xi,t,l} \ge F_{l} - \overline{\mathrm{f}}^{\mathrm{nom}}_{\xi,l} \cdot \left( 1 - \mu^{f}_{\xi,t,l} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{mnt,ext}}_{t,l} ``` **`Link_maintcap_lower_nommin`** ```math -\mu^{f,\mathrm{nom}}_{\xi,t,l} \ge \underline{\mathrm{f}}^{\mathrm{nom}}_{\xi,l} \cdot \mu^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{mnt}^{f}_{l} \wedge \mathrm{ext}^{f}_{l} \wedge \neg \left( \mathrm{com}^{f}_{l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) \wedge \mathrm{on}^{f}_{t,l} \wedge \underline{\mathrm{f}}^{\mathrm{nom}}_{\xi,l} > 0 +\mu^{f,\mathrm{nom}}_{\xi,t,l} \ge \underline{\mathrm{f}}^{\mathrm{nom}}_{\xi,l} \cdot \mu^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{mnt,ext}}_{t,l} \wedge \underline{\mathrm{f}}^{\mathrm{nom}}_{\xi,l} > 0 ``` **`Link_maint_status_le_status`** ```math -\mu^{f,u}_{\xi,t,l} \le u^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{mnt}^{f}_{l} \wedge \mathrm{com}^{f}_{l} \wedge \neg \mathrm{ext}^{f}_{l} \wedge \mathrm{on}^{f}_{t,l} +\mu^{f,u}_{\xi,t,l} \le u^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{mnt}^{f}_{l} \wedge \mathrm{on}^{f,\mathrm{com}}_{t,l} \wedge \neg \mathrm{ext}^{f}_{l} ``` **`Link_maint_status_le_maint`** ```math -\mu^{f,u}_{\xi,t,l} \le \mu^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{mnt}^{f}_{l} \wedge \mathrm{com}^{f}_{l} \wedge \neg \mathrm{ext}^{f}_{l} \wedge \mathrm{on}^{f}_{t,l} +\mu^{f,u}_{\xi,t,l} \le \mu^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{mnt}^{f}_{l} \wedge \mathrm{on}^{f,\mathrm{com}}_{t,l} \wedge \neg \mathrm{ext}^{f}_{l} ``` **`Link_maint_status_lb`** ```math -\mu^{f,u}_{\xi,t,l} \ge u^{f}_{\xi,t,l} + \mu^{f}_{\xi,t,l} - 1 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{mnt}^{f}_{l} \wedge \mathrm{com}^{f}_{l} \wedge \neg \mathrm{ext}^{f}_{l} \wedge \mathrm{on}^{f}_{t,l} +\mu^{f,u}_{\xi,t,l} \ge u^{f}_{\xi,t,l} + \mu^{f}_{\xi,t,l} - 1 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{mnt}^{f}_{l} \wedge \mathrm{on}^{f,\mathrm{com}}_{t,l} \wedge \neg \mathrm{ext}^{f}_{l} ``` **`Link_maint_modstatus_le_status`** ```math -\mu^{f,u}_{\xi,t,l} \le u^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{mnt}^{f}_{l} \wedge \mathrm{com}^{f}_{l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 \wedge \mathrm{on}^{f}_{t,l} +\mu^{f,u}_{\xi,t,l} \le u^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{mnt}^{f}_{l} \wedge \mathrm{on}^{f,\mathrm{com}}_{t,l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 ``` **`Link_maint_modstatus_le_maint`** ```math -\mu^{f,u}_{\xi,t,l} \le \frac{\overline{\mathrm{f}}^{\mathrm{nom}}_{\xi,l}}{\mathrm{f}^{\mathrm{mod}}_{l}} \cdot \mu^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{mnt}^{f}_{l} \wedge \mathrm{com}^{f}_{l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 \wedge \mathrm{on}^{f}_{t,l} +\mu^{f,u}_{\xi,t,l} \le \frac{\overline{\mathrm{f}}^{\mathrm{nom}}_{\xi,l}}{\mathrm{f}^{\mathrm{mod}}_{l}} \cdot \mu^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{mnt}^{f}_{l} \wedge \mathrm{on}^{f,\mathrm{com}}_{t,l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 ``` **`Link_maint_modstatus_lb`** ```math -\mu^{f,u}_{\xi,t,l} \ge u^{f}_{\xi,t,l} - \frac{\overline{\mathrm{f}}^{\mathrm{nom}}_{\xi,l}}{\mathrm{f}^{\mathrm{mod}}_{l}} \cdot \left( 1 - \mu^{f}_{\xi,t,l} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{mnt}^{f}_{l} \wedge \mathrm{com}^{f}_{l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 \wedge \mathrm{on}^{f}_{t,l} +\mu^{f,u}_{\xi,t,l} \ge u^{f}_{\xi,t,l} - \frac{\overline{\mathrm{f}}^{\mathrm{nom}}_{\xi,l}}{\mathrm{f}^{\mathrm{mod}}_{l}} \cdot \left( 1 - \mu^{f}_{\xi,t,l} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{mnt}^{f}_{l} \wedge \mathrm{on}^{f,\mathrm{com}}_{t,l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 +``` + +#### Masks + +**`Link_maint_ext`** + +```math +\mathrm{on}^{f,\mathrm{mnt,ext}}_{t,l} \iff \mathrm{mnt}^{f}_{l} \wedge \mathrm{ext}^{f}_{l} \wedge \neg \left( \mathrm{com}^{f}_{l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) \wedge \mathrm{on}^{f}_{t,l} \qquad \forall\, t \in \mathcal{T},\ l \in \mathcal{L} ``` #### Variable domains @@ -369,13 +394,13 @@ assumptions: **`Link_maintenance_capacity`** ```math -\mu^{f,\mathrm{nom}}_{\xi,t,l} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{mnt}^{f}_{l} \wedge \mathrm{ext}^{f}_{l} \wedge \neg \left( \mathrm{com}^{f}_{l} \wedge \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) \wedge \mathrm{on}^{f}_{t,l} +\mu^{f,\mathrm{nom}}_{\xi,t,l} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{mnt,ext}}_{t,l} ``` **`Link_maintenance_status`** ```math -\mu^{f,u}_{\xi,t,l} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{mnt}^{f}_{l} \wedge \mathrm{com}^{f}_{l} \wedge \neg \left( \mathrm{ext}^{f}_{l} \wedge \neg \left( \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) \right) \wedge \mathrm{on}^{f}_{t,l} +\mu^{f,u}_{\xi,t,l} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{mnt}^{f}_{l} \wedge \mathrm{on}^{f,\mathrm{com}}_{t,l} \wedge \neg \left( \mathrm{ext}^{f}_{l} \wedge \neg \left( \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) \right) ``` #### Assumptions diff --git a/docs/examples/pypsa/link_ramping.md b/docs/examples/pypsa/link_ramping.md index 0b55db30..19dfe2d7 100644 --- a/docs/examples/pypsa/link_ramping.md +++ b/docs/examples/pypsa/link_ramping.md @@ -65,6 +65,8 @@ given: Link_p_nom_effective: { dims: [scenario, link] } Link_previous_status: { dims: [scenario, snapshot, link] } Link_p_nom_committed: { dims: [scenario, link] } + masks: + Link_com_ext: { dims: [snapshot, link] } expressions: Link_previous_p: @@ -137,6 +139,16 @@ expressions: * (Link_previous_status - Link_status) otherwise: Link_ramp_down_rate * Link_p_nom_effective +masks: + Link_ramps_from_previous: + description: >- + a snapshot whose ramp reads a previous output — any snapshot but its + period's first, and the horizon's first where the link comes in off or + carries an initial output + where: >- + position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND + (Link_status_initial == 0 OR Link_p_init)) + constraints: Link_p_ramp_limit_up_run_big_m: description: >- @@ -144,11 +156,7 @@ constraints: raises flow no faster than its limit of the chosen build; the big M releases the row in the snapshot it turns on dims: [scenario, snapshot, link] - where: >- - Link_committable AND Link_p_nom_extendable AND NOT (Link_p_nom_mod > 0) - AND (Link_ramp_limit_up OR Link_ramp_limit_start_up) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Link_status_initial == 0 OR Link_p_init))) - AND Link_active + where: Link_com_ext AND (Link_ramp_limit_up OR Link_ramp_limit_start_up) AND Link_ramps_from_previous expression: >- Link_p - Link_previous_p <= Link_ramp_up_rate * Link_p_nom_ext @@ -159,11 +167,7 @@ constraints: committed extendable link ramps no further than its start-up ramp of the chosen build; the big M releases the row everywhere else dims: [scenario, snapshot, link] - where: >- - Link_committable AND Link_p_nom_extendable AND NOT (Link_p_nom_mod > 0) - AND (Link_ramp_limit_up OR Link_ramp_limit_start_up) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Link_status_initial == 0 OR Link_p_init))) - AND Link_active + where: Link_com_ext AND (Link_ramp_limit_up OR Link_ramp_limit_start_up) AND Link_ramps_from_previous expression: >- Link_p - Link_previous_p <= Link_start_up_rate * Link_p_nom_ext @@ -174,11 +178,7 @@ constraints: lowers flow no faster than its limit of the chosen build; the big M releases the row in the snapshot it turns off dims: [scenario, snapshot, link] - where: >- - Link_committable AND Link_p_nom_extendable AND NOT (Link_p_nom_mod > 0) - AND (Link_ramp_limit_down OR Link_ramp_limit_shut_down) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Link_status_initial == 0 OR Link_p_init))) - AND Link_active + where: Link_com_ext AND (Link_ramp_limit_down OR Link_ramp_limit_shut_down) AND Link_ramps_from_previous expression: >- Link_previous_p - Link_p <= Link_ramp_down_rate * Link_p_nom_ext @@ -189,11 +189,7 @@ constraints: a committed extendable link ramps no further than its shut-down ramp of the chosen build; the big M releases the row everywhere else dims: [scenario, snapshot, link] - where: >- - Link_committable AND Link_p_nom_extendable AND NOT (Link_p_nom_mod > 0) - AND (Link_ramp_limit_down OR Link_ramp_limit_shut_down) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Link_status_initial == 0 OR Link_p_init))) - AND Link_active + where: Link_com_ext AND (Link_ramp_limit_down OR Link_ramp_limit_shut_down) AND Link_ramps_from_previous expression: >- Link_previous_p - Link_p <= Link_shut_down_rate * Link_p_nom_ext @@ -210,7 +206,7 @@ constraints: where: >- (Link_ramp_limit_up OR Link_ramp_limit_start_up) AND NOT (Link_committable AND Link_p_nom_extendable AND NOT (Link_p_nom_mod > 0)) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Link_status_initial == 0 OR Link_p_init))) + AND Link_ramps_from_previous AND Link_active expression: Link_p - Link_previous_p <= Link_ramp_up_allowance Link_p_ramp_limit_down: @@ -225,7 +221,7 @@ constraints: where: >- (Link_ramp_limit_down OR Link_ramp_limit_shut_down) AND NOT (Link_committable AND Link_p_nom_extendable AND NOT (Link_p_nom_mod > 0)) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Link_status_initial == 0 OR Link_p_init))) + AND Link_ramps_from_previous AND Link_active expression: Link_previous_p - Link_p <= Link_ramp_down_allowance @@ -281,6 +277,7 @@ assumptions: | $`\widetilde{\mathrm{f}}^{\mathrm{nom}}`$ | `Link_p_nom_effective` over $`\Xi \times \mathcal{L}`$, an expression another file defines | | $`\overleftarrow{u}^{f}`$ | `Link_previous_status` over $`\Xi \times \mathcal{T} \times \mathcal{L}`$, an expression another file defines | | $`\widehat{\mathrm{f}}^{\mathrm{nom}}`$ | `Link_p_nom_committed` over $`\Xi \times \mathcal{L}`$, an expression another file defines | +| $`\mathrm{on}^{f,\mathrm{com,ext}}`$ | `Link_com_ext` over $`\mathcal{T} \times \mathcal{L}`$, a mask another file defines | #### Definitions @@ -294,6 +291,12 @@ assumptions: | $`\Delta^{f,+}`$ | `Link_ramp_up_allowance` over $`\Xi \times \mathcal{T} \times \mathcal{L}`$ — how far a link may raise flow between two snapshots — its ramp limit of the build while it stays on, plus its start-up ramp in the snapshot it turns on | | $`\Delta^{f,-}`$ | `Link_ramp_down_allowance` over $`\Xi \times \mathcal{T} \times \mathcal{L}`$ — how far a link may lower flow between two snapshots — its ramp limit of the build while it stays on, plus its shut-down ramp in the snapshot it turns off | +#### Masks + +| Symbol | Meaning | +|---|---| +| $`\mathrm{prev}^{f}`$ | `Link_ramps_from_previous` over $`\Xi \times \mathcal{T} \times \mathcal{L}`$ — a snapshot whose ramp reads a previous output — any snapshot but its period's first, and the horizon's first where the link comes in off or carries an initial output | + $`\mathrm{pos}(t)`$ denotes where index $`t`$ sits along its dimension's own order — the order `shift` steps along, not the order labels sort in — counted from $`0`$. The index itself stays the coordinate, so $`t`$ compares against labels and $`\mathrm{pos}(t)`$ against positions. $`\mathrm{pos}_{\mathrm{relation}(t)}(t)`$ counts within the group a relation puts $`t`$ in: the subscript names the map, $`\mathcal{T}_{\mathrm{relation}(t)}`$ is the group it lands in, and that group has a first position of its own. @@ -303,37 +306,37 @@ $`\mathrm{pos}_{\mathrm{relation}(t)}(t)`$ counts within the group a relation pu **`Link_p_ramp_limit_up_run_big_m`** ```math -f_{\xi,t,l} - \overleftarrow{f}_{\xi,t,l} \le \widetilde{\mathrm{ru}}^{f}_{\xi,t,l} \cdot F_{l} + \mathrm{M}^{f}_{\xi,l} - \mathrm{M}^{f}_{\xi,l} \cdot \overleftarrow{u}^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \mathrm{ext}^{f}_{l} \wedge \neg \left( \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) \wedge \left( \mathrm{ru}^{f}_{\xi,t,l} \text{ is defined} \vee \mathrm{ru}^{f,\mathrm{up}}_{\xi,l} \text{ is defined} \right) \wedge \left( \mathrm{pos}_{\mathrm{snapshot\_period}(t)}(t) > 0 \vee \mathrm{pos}(t) = 0 \wedge \left( \mathrm{u}^{f,0}_{\xi,l} = 0 \vee \mathrm{f}^{0}_{\xi,l} \text{ is defined} \right) \right) \wedge \mathrm{on}^{f}_{t,l} +f_{\xi,t,l} - \overleftarrow{f}_{\xi,t,l} \le \widetilde{\mathrm{ru}}^{f}_{\xi,t,l} \cdot F_{l} + \mathrm{M}^{f}_{\xi,l} - \mathrm{M}^{f}_{\xi,l} \cdot \overleftarrow{u}^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com,ext}}_{t,l} \wedge \left( \mathrm{ru}^{f}_{\xi,t,l} \text{ is defined} \vee \mathrm{ru}^{f,\mathrm{up}}_{\xi,l} \text{ is defined} \right) \wedge \mathrm{prev}^{f}_{\xi,t,l} ``` **`Link_p_ramp_limit_up_start_big_m`** ```math -f_{\xi,t,l} - \overleftarrow{f}_{\xi,t,l} \le \widetilde{\mathrm{ru}}^{f,\mathrm{up}}_{\xi,l} \cdot F_{l} + \mathrm{M}^{f}_{\xi,l} - \mathrm{M}^{f}_{\xi,l} \cdot \mathit{up}^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \mathrm{ext}^{f}_{l} \wedge \neg \left( \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) \wedge \left( \mathrm{ru}^{f}_{\xi,t,l} \text{ is defined} \vee \mathrm{ru}^{f,\mathrm{up}}_{\xi,l} \text{ is defined} \right) \wedge \left( \mathrm{pos}_{\mathrm{snapshot\_period}(t)}(t) > 0 \vee \mathrm{pos}(t) = 0 \wedge \left( \mathrm{u}^{f,0}_{\xi,l} = 0 \vee \mathrm{f}^{0}_{\xi,l} \text{ is defined} \right) \right) \wedge \mathrm{on}^{f}_{t,l} +f_{\xi,t,l} - \overleftarrow{f}_{\xi,t,l} \le \widetilde{\mathrm{ru}}^{f,\mathrm{up}}_{\xi,l} \cdot F_{l} + \mathrm{M}^{f}_{\xi,l} - \mathrm{M}^{f}_{\xi,l} \cdot \mathit{up}^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com,ext}}_{t,l} \wedge \left( \mathrm{ru}^{f}_{\xi,t,l} \text{ is defined} \vee \mathrm{ru}^{f,\mathrm{up}}_{\xi,l} \text{ is defined} \right) \wedge \mathrm{prev}^{f}_{\xi,t,l} ``` **`Link_p_ramp_limit_down_run_big_m`** ```math -\overleftarrow{f}_{\xi,t,l} - f_{\xi,t,l} \le \widetilde{\mathrm{rd}}^{f}_{\xi,t,l} \cdot F_{l} + \mathrm{M}^{f}_{\xi,l} - \mathrm{M}^{f}_{\xi,l} \cdot u^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \mathrm{ext}^{f}_{l} \wedge \neg \left( \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) \wedge \left( \mathrm{rd}^{f}_{\xi,t,l} \text{ is defined} \vee \mathrm{rd}^{f,\mathrm{dn}}_{\xi,l} \text{ is defined} \right) \wedge \left( \mathrm{pos}_{\mathrm{snapshot\_period}(t)}(t) > 0 \vee \mathrm{pos}(t) = 0 \wedge \left( \mathrm{u}^{f,0}_{\xi,l} = 0 \vee \mathrm{f}^{0}_{\xi,l} \text{ is defined} \right) \right) \wedge \mathrm{on}^{f}_{t,l} +\overleftarrow{f}_{\xi,t,l} - f_{\xi,t,l} \le \widetilde{\mathrm{rd}}^{f}_{\xi,t,l} \cdot F_{l} + \mathrm{M}^{f}_{\xi,l} - \mathrm{M}^{f}_{\xi,l} \cdot u^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com,ext}}_{t,l} \wedge \left( \mathrm{rd}^{f}_{\xi,t,l} \text{ is defined} \vee \mathrm{rd}^{f,\mathrm{dn}}_{\xi,l} \text{ is defined} \right) \wedge \mathrm{prev}^{f}_{\xi,t,l} ``` **`Link_p_ramp_limit_down_shut_big_m`** ```math -\overleftarrow{f}_{\xi,t,l} - f_{\xi,t,l} \le \widetilde{\mathrm{rd}}^{f,\mathrm{dn}}_{\xi,l} \cdot F_{l} + \mathrm{M}^{f}_{\xi,l} - \mathrm{M}^{f}_{\xi,l} \cdot \mathit{dn}^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \mathrm{ext}^{f}_{l} \wedge \neg \left( \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) \wedge \left( \mathrm{rd}^{f}_{\xi,t,l} \text{ is defined} \vee \mathrm{rd}^{f,\mathrm{dn}}_{\xi,l} \text{ is defined} \right) \wedge \left( \mathrm{pos}_{\mathrm{snapshot\_period}(t)}(t) > 0 \vee \mathrm{pos}(t) = 0 \wedge \left( \mathrm{u}^{f,0}_{\xi,l} = 0 \vee \mathrm{f}^{0}_{\xi,l} \text{ is defined} \right) \right) \wedge \mathrm{on}^{f}_{t,l} +\overleftarrow{f}_{\xi,t,l} - f_{\xi,t,l} \le \widetilde{\mathrm{rd}}^{f,\mathrm{dn}}_{\xi,l} \cdot F_{l} + \mathrm{M}^{f}_{\xi,l} - \mathrm{M}^{f}_{\xi,l} \cdot \mathit{dn}^{f}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \mathrm{on}^{f,\mathrm{com,ext}}_{t,l} \wedge \left( \mathrm{rd}^{f}_{\xi,t,l} \text{ is defined} \vee \mathrm{rd}^{f,\mathrm{dn}}_{\xi,l} \text{ is defined} \right) \wedge \mathrm{prev}^{f}_{\xi,t,l} ``` **`Link_p_ramp_limit_up`** ```math -f_{\xi,t,l} - \overleftarrow{f}_{\xi,t,l} \le \Delta^{f,+}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \left( \mathrm{ru}^{f}_{\xi,t,l} \text{ is defined} \vee \mathrm{ru}^{f,\mathrm{up}}_{\xi,l} \text{ is defined} \right) \wedge \neg \left( \mathrm{com}^{f}_{l} \wedge \mathrm{ext}^{f}_{l} \wedge \neg \left( \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) \right) \wedge \left( \mathrm{pos}_{\mathrm{snapshot\_period}(t)}(t) > 0 \vee \mathrm{pos}(t) = 0 \wedge \left( \mathrm{u}^{f,0}_{\xi,l} = 0 \vee \mathrm{f}^{0}_{\xi,l} \text{ is defined} \right) \right) \wedge \mathrm{on}^{f}_{t,l} +f_{\xi,t,l} - \overleftarrow{f}_{\xi,t,l} \le \Delta^{f,+}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \left( \mathrm{ru}^{f}_{\xi,t,l} \text{ is defined} \vee \mathrm{ru}^{f,\mathrm{up}}_{\xi,l} \text{ is defined} \right) \wedge \neg \left( \mathrm{com}^{f}_{l} \wedge \mathrm{ext}^{f}_{l} \wedge \neg \left( \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) \right) \wedge \mathrm{prev}^{f}_{\xi,t,l} \wedge \mathrm{on}^{f}_{t,l} ``` **`Link_p_ramp_limit_down`** ```math -\overleftarrow{f}_{\xi,t,l} - f_{\xi,t,l} \le \Delta^{f,-}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \left( \mathrm{rd}^{f}_{\xi,t,l} \text{ is defined} \vee \mathrm{rd}^{f,\mathrm{dn}}_{\xi,l} \text{ is defined} \right) \wedge \neg \left( \mathrm{com}^{f}_{l} \wedge \mathrm{ext}^{f}_{l} \wedge \neg \left( \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) \right) \wedge \left( \mathrm{pos}_{\mathrm{snapshot\_period}(t)}(t) > 0 \vee \mathrm{pos}(t) = 0 \wedge \left( \mathrm{u}^{f,0}_{\xi,l} = 0 \vee \mathrm{f}^{0}_{\xi,l} \text{ is defined} \right) \right) \wedge \mathrm{on}^{f}_{t,l} +\overleftarrow{f}_{\xi,t,l} - f_{\xi,t,l} \le \Delta^{f,-}_{\xi,t,l} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} \,:\, \left( \mathrm{rd}^{f}_{\xi,t,l} \text{ is defined} \vee \mathrm{rd}^{f,\mathrm{dn}}_{\xi,l} \text{ is defined} \right) \wedge \neg \left( \mathrm{com}^{f}_{l} \wedge \mathrm{ext}^{f}_{l} \wedge \neg \left( \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) \right) \wedge \mathrm{prev}^{f}_{\xi,t,l} \wedge \mathrm{on}^{f}_{t,l} ``` #### Definitions @@ -380,6 +383,14 @@ f_{\xi,t,l} - \overleftarrow{f}_{\xi,t,l} \le \Delta^{f,+}_{\xi,t,l} \qquad \for \Delta^{f,-}_{\xi,t,l} = \begin{cases} \widetilde{\mathrm{rd}}^{f}_{\xi,t,l} \cdot \widehat{\mathrm{f}}^{\mathrm{nom}}_{\xi,l} \cdot u^{f}_{\xi,t,l} + \widetilde{\mathrm{rd}}^{f,\mathrm{dn}}_{\xi,l} \cdot \widehat{\mathrm{f}}^{\mathrm{nom}}_{\xi,l} \cdot \left( \overleftarrow{u}^{f}_{\xi,t,l} - u^{f}_{\xi,t,l} \right) & \text{if } \mathrm{com}^{f}_{l} \\ \widetilde{\mathrm{rd}}^{f}_{\xi,t,l} \cdot \widetilde{\mathrm{f}}^{\mathrm{nom}}_{\xi,l} & \text{otherwise} \end{cases} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} ``` +#### Masks + +**`Link_ramps_from_previous`** + +```math +\mathrm{prev}^{f}_{\xi,t,l} \iff \mathrm{pos}_{\mathrm{snapshot\_period}(t)}(t) > 0 \vee \mathrm{pos}(t) = 0 \wedge \left( \mathrm{u}^{f,0}_{\xi,l} = 0 \vee \mathrm{f}^{0}_{\xi,l} \text{ is defined} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ l \in \mathcal{L} +``` + #### Assumptions **`Link_came_in_running_unless_committable`** diff --git a/docs/examples/pypsa/process.md b/docs/examples/pypsa/process.md index 00711b99..5bb52b60 100644 --- a/docs/examples/pypsa/process.md +++ b/docs/examples/pypsa/process.md @@ -240,6 +240,13 @@ expressions: sum(Process_output_arrival, by=Process_output_bus, over=process_output, into=bus) adds_to: Bus_injection +masks: + Process_committed: + description: >- + a committable process that stands in the snapshot's period — every + unit-commitment row's set + where: Process_committable AND Process_active + constraints: Process_fix_p_lower: description: "`Process-fix-p-lower` — a fixed process runs at least its minimum, negative for the other way" @@ -376,6 +383,12 @@ assumptions: | $`\mathit{Process\_additions}`$ | `Process_additions` over $`\mathcal{Y} \times \mathcal{I}`$ | | $`\mathit{Process\_injection}`$ | `Process_injection` over $`\Xi \times \mathcal{T} \times \mathcal{N}`$ | +#### Masks + +| Symbol | Meaning | +|---|---| +| $`\mathrm{on}^{z,\mathrm{com}}`$ | `Process_committed` over $`\mathcal{T} \times \mathcal{J}`$ — a committable process that stands in the snapshot's period — every unit-commitment row's set | + $`t \ominus k`$ denotes cyclic translation: index $`t-k`$ taken modulo the size of the dimension (`roll`). Plain $`t-k`$ (`shift`) has no wraparound — terms translated past the edge are simply absent. $`t \boxminus_{v} k`$ denotes translation with $`v`$ standing where index $`t-k`$ leaves the dimension (`shift(edge=v)`), so the row at that boundary is built and carries $`v`$ rather than being dropped. @@ -488,6 +501,14 @@ z_{\xi,t,j} = \mathrm{z}^{\mathrm{set}}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ \mathit{Process\_injection}_{\xi,t,n} = \sum_{r \in \mathcal{R} \,:\, \mathrm{Process\_output\_bus}(r) = n} \overrightarrow{z}_{\xi,t,r} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ n \in \mathcal{N} ``` +#### Masks + +**`Process_committed`** + +```math +\mathrm{on}^{z,\mathrm{com}}_{t,j} \iff \mathrm{com}^{z}_{j} \wedge \mathrm{on}^{z}_{t,j} \qquad \forall\, t \in \mathcal{T},\ j \in \mathcal{J} +``` + #### Variable domains **`Process_p`** diff --git a/docs/examples/pypsa/process_commitment.md b/docs/examples/pypsa/process_commitment.md index f2087dc2..d8934db9 100644 --- a/docs/examples/pypsa/process_commitment.md +++ b/docs/examples/pypsa/process_commitment.md @@ -78,21 +78,21 @@ variables: the rows below cap at one, or at the module count where the build is modular dims: [scenario, snapshot, process] - where: Process_committable AND Process_active + where: Process_committed domain: integer bounds: lower: 0 Process_start_up: description: "`Process-start_up` — how much of a committable process turns on this snapshot, capped as the status is" dims: [scenario, snapshot, process] - where: Process_committable AND Process_active + where: Process_committed domain: integer bounds: lower: 0 Process_shut_down: description: "`Process-shut_down` — how much of a committable process turns off this snapshot, capped as the status is" dims: [scenario, snapshot, process] - where: Process_committable AND Process_active + where: Process_committed domain: integer bounds: lower: 0 @@ -118,6 +118,8 @@ given: Process_p_nom_ext: { dims: [process] } expressions: scenario_opex: { dims: [scenario] } + masks: + Process_committed: { dims: [snapshot, process] } expressions: Process_previous_status: @@ -135,26 +137,38 @@ expressions: + sum(sum(Process_shut_down * Process_shut_down_cost, over=process), over=snapshot) adds_to: scenario_opex +masks: + Process_com_ext: + description: >- + a committable process with an extendable, non-modular build that stands + in the snapshot's period — PyPSA's `com-ext` rows, whose status is + relaxed against the chosen build + where: >- + Process_committable + AND Process_p_nom_extendable + AND NOT (Process_p_nom_mod > 0) + AND Process_active + constraints: Process_com_p_lower: description: "`Process-com-p-lower` — a committed process runs at least its minimum; off, at least nothing" dims: [scenario, snapshot, process] - where: Process_committable AND not Process_p_nom_extendable AND Process_active + where: Process_committed AND NOT Process_p_nom_extendable expression: Process_p >= Process_p_min_pu * Process_p_nom * (Process_status - Process_maintenance_pu * Process_maintenance_status) Process_com_p_upper: description: "`Process-com-p-upper` — a committed process runs at most what is available; off, at most nothing" dims: [scenario, snapshot, process] - where: Process_committable AND not Process_p_nom_extendable AND Process_active + where: Process_committed AND NOT Process_p_nom_extendable expression: Process_p <= Process_p_max_pu * Process_p_nom * (Process_status - Process_maintenance_pu * Process_maintenance_status) Process_com_transition_start_up: description: "`Process-com-transition-start-up` — turning on is a start, counted against the state the process carried into the snapshot" dims: [scenario, snapshot, process] - where: Process_committable AND Process_active + where: Process_committed expression: Process_start_up >= Process_status - Process_previous_status Process_com_transition_shut_down: description: "`Process-com-transition-shut-down` — turning off is a stop, counted against the state the process carried into the snapshot" dims: [scenario, snapshot, process] - where: Process_committable AND Process_active + where: Process_committed expression: Process_shut_down >= Process_previous_status - Process_status Process_com_up_time: description: >- @@ -162,7 +176,7 @@ constraints: 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 dims: [scenario, snapshot, process] - where: Process_committable AND Process_min_up_time > 0 AND position(snapshot) > 0 AND Process_active + where: Process_committed AND Process_min_up_time > 0 AND position(snapshot) > 0 expression: sum_back(Process_start_up, along=snapshot, window=Process_min_up_time) <= Process_status Process_com_down_time: description: >- @@ -170,38 +184,38 @@ constraints: time is still off. The first snapshot's share of the window is the brought-in down time's, which the must-stay-down mask carries dims: [scenario, snapshot, process] - where: Process_committable AND Process_min_down_time > 0 AND position(snapshot) > 0 AND Process_active + where: Process_committed AND Process_min_down_time > 0 AND position(snapshot) > 0 expression: sum_back(Process_shut_down, along=snapshot, window=Process_min_down_time) <= 1 - Process_status Process_com_status_must_stay_up: description: "`Process-com-status-min_up_time_must_stay_up` — a process still serving the up time it brought in stays on" dims: [scenario, snapshot, process] - where: Process_committable AND Process_must_stay_up AND Process_active + where: Process_committed AND Process_must_stay_up expression: Process_status == 1 Process_com_status_must_stay_down: description: >- `Process-com-status-min_down_time_must_stay_up` — a process still serving the down time it brought in stays off; PyPSA names the row `_must_stay_up` dims: [scenario, snapshot, process] - where: Process_committable AND Process_must_stay_down AND Process_active + where: Process_committed AND Process_must_stay_down expression: Process_status == 0 Process_com_ext_p_upper_cap: description: >- `Process-com-ext-p-upper-cap` — a committed extendable process runs at most what is available of the chosen build, whatever its status dims: [scenario, snapshot, process] - where: Process_committable AND Process_p_nom_extendable AND NOT (Process_p_nom_mod > 0) AND Process_active + where: Process_com_ext expression: Process_p <= Process_p_max_pu * (Process_p_nom_ext - Process_maintenance_pu * Process_maintenance_capacity) Process_com_ext_p_upper_big_m: description: "`Process-com-ext-p-upper-bigM` — off, a process does not run; on, the big M is no bound" dims: [scenario, snapshot, process] - where: Process_committable AND Process_p_nom_extendable AND NOT (Process_p_nom_mod > 0) AND Process_active + where: Process_com_ext expression: Process_p <= Process_big_m * Process_status Process_com_ext_p_lower: description: >- `Process-com-ext-p-lower` — a committed extendable process runs at least its minimum of the chosen build; off, the big M releases the row dims: [scenario, snapshot, process] - where: Process_committable AND Process_p_nom_extendable AND NOT (Process_p_nom_mod > 0) AND Process_active + where: Process_com_ext expression: >- Process_p >= Process_p_min_pu * (Process_p_nom_ext - Process_maintenance_pu * Process_maintenance_capacity) @@ -212,23 +226,21 @@ constraints: negative, internal power is also plainly non-negative, a row the big-M lower cannot assert while the process is off dims: [scenario, snapshot, process] - where: >- - Process_committable AND Process_p_nom_extendable - AND Process_p_min_pu_nonneg AND NOT (Process_p_nom_mod > 0) AND Process_active + where: Process_com_ext AND Process_p_min_pu_nonneg expression: Process_p >= 0 Process_com_mod_p_lower: description: >- `Process-com-mod-p-lower` — a committed modular process runs at least its minimum of one module, whether the build is fixed or a decision dims: [scenario, snapshot, process] - where: Process_committable AND Process_p_nom_mod > 0 AND Process_active + where: Process_committed AND Process_p_nom_mod > 0 expression: Process_p >= Process_p_min_pu * Process_p_nom_mod * (Process_status - Process_maintenance_pu * Process_maintenance_status) Process_com_mod_p_upper: description: >- `Process-com-mod-p-upper` — a committed modular process runs at most one module's share, whether the build is fixed or a decision dims: [scenario, snapshot, process] - where: Process_committable AND Process_p_nom_mod > 0 AND Process_active + where: Process_committed AND Process_p_nom_mod > 0 expression: Process_p <= Process_p_max_pu * Process_p_nom_mod * (Process_status - Process_maintenance_pu * Process_maintenance_status) Process_status_p_fixed_upper: description: >- @@ -236,7 +248,7 @@ constraints: 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 dims: [scenario, snapshot, process] - where: Process_committable AND NOT (Process_p_nom_extendable AND Process_p_nom_mod > 0) AND Process_active + where: Process_committed AND NOT (Process_p_nom_extendable AND Process_p_nom_mod > 0) expression: Process_status <= Process_modules_installed Process_start_up_p_fixed_upper: description: >- @@ -244,7 +256,7 @@ constraints: 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 dims: [scenario, snapshot, process] - where: Process_committable AND NOT (Process_p_nom_extendable AND Process_p_nom_mod > 0) AND Process_active + where: Process_committed AND NOT (Process_p_nom_extendable AND Process_p_nom_mod > 0) expression: Process_start_up <= Process_modules_installed Process_shut_down_p_fixed_upper: description: >- @@ -252,22 +264,22 @@ constraints: 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 dims: [scenario, snapshot, process] - where: Process_committable AND NOT (Process_p_nom_extendable AND Process_p_nom_mod > 0) AND Process_active + where: Process_committed AND NOT (Process_p_nom_extendable AND Process_p_nom_mod > 0) expression: Process_shut_down <= Process_modules_installed Process_status_p_nom_variable_upper: description: "`Process-status-p_nom-variable-upper` — a modular process is on only where a module is built" dims: [scenario, snapshot, process] - where: Process_committable AND Process_p_nom_extendable AND Process_p_nom_mod > 0 AND Process_active + where: Process_committed AND Process_p_nom_extendable AND Process_p_nom_mod > 0 expression: Process_status <= Process_n_mod Process_start_up_p_nom_variable_upper: description: "`Process-start_up-p_nom-variable-upper` — a modular process starts only where a module is built" dims: [scenario, snapshot, process] - where: Process_committable AND Process_p_nom_extendable AND Process_p_nom_mod > 0 AND Process_active + where: Process_committed AND Process_p_nom_extendable AND Process_p_nom_mod > 0 expression: Process_start_up <= Process_n_mod Process_shut_down_p_nom_variable_upper: description: "`Process-shut_down-p_nom-variable-upper` — a modular process stops only where a module is built" dims: [scenario, snapshot, process] - where: Process_committable AND Process_p_nom_extendable AND Process_p_nom_mod > 0 AND Process_active + where: Process_committed AND Process_p_nom_extendable AND Process_p_nom_mod > 0 expression: Process_shut_down <= Process_n_mod ``` @@ -324,6 +336,7 @@ constraints: | $`\mu^{z,u}`$ | `Process_maintenance_status` over $`\Xi \times \mathcal{T} \times \mathcal{J}`$ | | $`Z`$ | `Process_p_nom_ext` over $`\mathcal{J}`$ | | $`\mathit{scenario\_opex}`$ | `scenario_opex` over $`\Xi`$, an expression this file adds `Process_commitment_opex` to | +| $`\mathrm{on}^{z,\mathrm{com}}`$ | `Process_committed` over $`\mathcal{T} \times \mathcal{J}`$, a mask another file defines | #### Definitions @@ -332,6 +345,12 @@ constraints: | $`\overleftarrow{u}^{z}`$ | `Process_previous_status` over $`\Xi \times \mathcal{T} \times \mathcal{J}`$ — the commitment state a process carries into a snapshot — the state it brought into the horizon at the first, the previous snapshot's after that | | $`\mathit{Process\_commitment\_opex}`$ | `Process_commitment_opex` over $`\Xi`$ | +#### Masks + +| Symbol | Meaning | +|---|---| +| $`\mathrm{on}^{z,\mathrm{com,ext}}`$ | `Process_com_ext` over $`\mathcal{T} \times \mathcal{J}`$ — a committable process with an extendable, non-modular build that stands in the snapshot's period — PyPSA's `com-ext` rows, whose status is relaxed against the chosen build | + $`\mathrm{pos}(t)`$ denotes where index $`t`$ sits along its dimension's own order — the order `shift` steps along, not the order labels sort in — counted from $`0`$. The index itself stays the coordinate, so $`t`$ compares against labels and $`\mathrm{pos}(t)`$ against positions. #### Subject to @@ -339,121 +358,121 @@ $`\mathrm{pos}(t)`$ denotes where index $`t`$ sits along its dimension's own ord **`Process_com_p_lower`** ```math -z_{\xi,t,j} \ge \underline{\mathrm{z}}_{\xi,t,j} \cdot \mathrm{z}^{\mathrm{nom}}_{\xi,j} \cdot \left( u^{z}_{\xi,t,j} - \gamma^{z}_{\xi,j} \cdot \mu^{z,u}_{\xi,t,j} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \neg \mathrm{ext}^{z}_{j} \wedge \mathrm{on}^{z}_{t,j} +z_{\xi,t,j} \ge \underline{\mathrm{z}}_{\xi,t,j} \cdot \mathrm{z}^{\mathrm{nom}}_{\xi,j} \cdot \left( u^{z}_{\xi,t,j} - \gamma^{z}_{\xi,j} \cdot \mu^{z,u}_{\xi,t,j} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com}}_{t,j} \wedge \neg \mathrm{ext}^{z}_{j} ``` **`Process_com_p_upper`** ```math -z_{\xi,t,j} \le \overline{\mathrm{z}}_{\xi,t,j} \cdot \mathrm{z}^{\mathrm{nom}}_{\xi,j} \cdot \left( u^{z}_{\xi,t,j} - \gamma^{z}_{\xi,j} \cdot \mu^{z,u}_{\xi,t,j} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \neg \mathrm{ext}^{z}_{j} \wedge \mathrm{on}^{z}_{t,j} +z_{\xi,t,j} \le \overline{\mathrm{z}}_{\xi,t,j} \cdot \mathrm{z}^{\mathrm{nom}}_{\xi,j} \cdot \left( u^{z}_{\xi,t,j} - \gamma^{z}_{\xi,j} \cdot \mu^{z,u}_{\xi,t,j} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com}}_{t,j} \wedge \neg \mathrm{ext}^{z}_{j} ``` **`Process_com_transition_start_up`** ```math -\mathit{up}^{z}_{\xi,t,j} \ge u^{z}_{\xi,t,j} - \overleftarrow{u}^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \mathrm{on}^{z}_{t,j} +\mathit{up}^{z}_{\xi,t,j} \ge u^{z}_{\xi,t,j} - \overleftarrow{u}^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com}}_{t,j} ``` **`Process_com_transition_shut_down`** ```math -\mathit{dn}^{z}_{\xi,t,j} \ge \overleftarrow{u}^{z}_{\xi,t,j} - u^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \mathrm{on}^{z}_{t,j} +\mathit{dn}^{z}_{\xi,t,j} \ge \overleftarrow{u}^{z}_{\xi,t,j} - u^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com}}_{t,j} ``` **`Process_com_up_time`** ```math -\sum_{t' \in \mathcal{T} \,:\, 0 \le t - t' < \mathrm{UT}^{z}} \mathit{up}^{z}_{\xi,t',j} \le u^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \mathrm{UT}^{z}_{\xi,j} > 0 \wedge \mathrm{pos}(t) > 0 \wedge \mathrm{on}^{z}_{t,j} +\sum_{t' \in \mathcal{T} \,:\, 0 \le t - t' < \mathrm{UT}^{z}} \mathit{up}^{z}_{\xi,t',j} \le u^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com}}_{t,j} \wedge \mathrm{UT}^{z}_{\xi,j} > 0 \wedge \mathrm{pos}(t) > 0 ``` **`Process_com_down_time`** ```math -\sum_{t' \in \mathcal{T} \,:\, 0 \le t - t' < \mathrm{DT}^{z}} \mathit{dn}^{z}_{\xi,t',j} \le 1 - u^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \mathrm{DT}^{z}_{\xi,j} > 0 \wedge \mathrm{pos}(t) > 0 \wedge \mathrm{on}^{z}_{t,j} +\sum_{t' \in \mathcal{T} \,:\, 0 \le t - t' < \mathrm{DT}^{z}} \mathit{dn}^{z}_{\xi,t',j} \le 1 - u^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com}}_{t,j} \wedge \mathrm{DT}^{z}_{\xi,j} > 0 \wedge \mathrm{pos}(t) > 0 ``` **`Process_com_status_must_stay_up`** ```math -u^{z}_{\xi,t,j} = 1 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \mathrm{hold}^{z}_{\xi,t,j} \wedge \mathrm{on}^{z}_{t,j} +u^{z}_{\xi,t,j} = 1 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com}}_{t,j} \wedge \mathrm{hold}^{z}_{\xi,t,j} ``` **`Process_com_status_must_stay_down`** ```math -u^{z}_{\xi,t,j} = 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \mathrm{rest}^{z}_{\xi,t,j} \wedge \mathrm{on}^{z}_{t,j} +u^{z}_{\xi,t,j} = 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com}}_{t,j} \wedge \mathrm{rest}^{z}_{\xi,t,j} ``` **`Process_com_ext_p_upper_cap`** ```math -z_{\xi,t,j} \le \overline{\mathrm{z}}_{\xi,t,j} \cdot \left( Z_{j} - \gamma^{z}_{\xi,j} \cdot \mu^{z,\mathrm{nom}}_{\xi,t,j} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \mathrm{ext}^{z}_{j} \wedge \neg \left( \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) \wedge \mathrm{on}^{z}_{t,j} +z_{\xi,t,j} \le \overline{\mathrm{z}}_{\xi,t,j} \cdot \left( Z_{j} - \gamma^{z}_{\xi,j} \cdot \mu^{z,\mathrm{nom}}_{\xi,t,j} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com,ext}}_{t,j} ``` **`Process_com_ext_p_upper_big_m`** ```math -z_{\xi,t,j} \le \mathrm{M}^{z}_{\xi,j} \cdot u^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \mathrm{ext}^{z}_{j} \wedge \neg \left( \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) \wedge \mathrm{on}^{z}_{t,j} +z_{\xi,t,j} \le \mathrm{M}^{z}_{\xi,j} \cdot u^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com,ext}}_{t,j} ``` **`Process_com_ext_p_lower`** ```math -z_{\xi,t,j} \ge \underline{\mathrm{z}}_{\xi,t,j} \cdot \left( Z_{j} - \gamma^{z}_{\xi,j} \cdot \mu^{z,\mathrm{nom}}_{\xi,t,j} \right) + \mathrm{M}^{z}_{\xi,j} \cdot u^{z}_{\xi,t,j} - \mathrm{M}^{z}_{\xi,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \mathrm{ext}^{z}_{j} \wedge \neg \left( \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) \wedge \mathrm{on}^{z}_{t,j} +z_{\xi,t,j} \ge \underline{\mathrm{z}}_{\xi,t,j} \cdot \left( Z_{j} - \gamma^{z}_{\xi,j} \cdot \mu^{z,\mathrm{nom}}_{\xi,t,j} \right) + \mathrm{M}^{z}_{\xi,j} \cdot u^{z}_{\xi,t,j} - \mathrm{M}^{z}_{\xi,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com,ext}}_{t,j} ``` **`Process_com_ext_p_lower_nonneg`** ```math -z_{\xi,t,j} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \mathrm{ext}^{z}_{j} \wedge \mathrm{nonneg}^{z}_{j} \wedge \neg \left( \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) \wedge \mathrm{on}^{z}_{t,j} +z_{\xi,t,j} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com,ext}}_{t,j} \wedge \mathrm{nonneg}^{z}_{j} ``` **`Process_com_mod_p_lower`** ```math -z_{\xi,t,j} \ge \underline{\mathrm{z}}_{\xi,t,j} \cdot \mathrm{z}^{\mathrm{mod}}_{j} \cdot \left( u^{z}_{\xi,t,j} - \gamma^{z}_{\xi,j} \cdot \mu^{z,u}_{\xi,t,j} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 \wedge \mathrm{on}^{z}_{t,j} +z_{\xi,t,j} \ge \underline{\mathrm{z}}_{\xi,t,j} \cdot \mathrm{z}^{\mathrm{mod}}_{j} \cdot \left( u^{z}_{\xi,t,j} - \gamma^{z}_{\xi,j} \cdot \mu^{z,u}_{\xi,t,j} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com}}_{t,j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 ``` **`Process_com_mod_p_upper`** ```math -z_{\xi,t,j} \le \overline{\mathrm{z}}_{\xi,t,j} \cdot \mathrm{z}^{\mathrm{mod}}_{j} \cdot \left( u^{z}_{\xi,t,j} - \gamma^{z}_{\xi,j} \cdot \mu^{z,u}_{\xi,t,j} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 \wedge \mathrm{on}^{z}_{t,j} +z_{\xi,t,j} \le \overline{\mathrm{z}}_{\xi,t,j} \cdot \mathrm{z}^{\mathrm{mod}}_{j} \cdot \left( u^{z}_{\xi,t,j} - \gamma^{z}_{\xi,j} \cdot \mu^{z,u}_{\xi,t,j} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com}}_{t,j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 ``` **`Process_status_p_fixed_upper`** ```math -u^{z}_{\xi,t,j} \le \mathrm{N}^{z,\mathrm{fix}}_{\xi,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \neg \left( \mathrm{ext}^{z}_{j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) \wedge \mathrm{on}^{z}_{t,j} +u^{z}_{\xi,t,j} \le \mathrm{N}^{z,\mathrm{fix}}_{\xi,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com}}_{t,j} \wedge \neg \left( \mathrm{ext}^{z}_{j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) ``` **`Process_start_up_p_fixed_upper`** ```math -\mathit{up}^{z}_{\xi,t,j} \le \mathrm{N}^{z,\mathrm{fix}}_{\xi,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \neg \left( \mathrm{ext}^{z}_{j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) \wedge \mathrm{on}^{z}_{t,j} +\mathit{up}^{z}_{\xi,t,j} \le \mathrm{N}^{z,\mathrm{fix}}_{\xi,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com}}_{t,j} \wedge \neg \left( \mathrm{ext}^{z}_{j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) ``` **`Process_shut_down_p_fixed_upper`** ```math -\mathit{dn}^{z}_{\xi,t,j} \le \mathrm{N}^{z,\mathrm{fix}}_{\xi,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \neg \left( \mathrm{ext}^{z}_{j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) \wedge \mathrm{on}^{z}_{t,j} +\mathit{dn}^{z}_{\xi,t,j} \le \mathrm{N}^{z,\mathrm{fix}}_{\xi,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com}}_{t,j} \wedge \neg \left( \mathrm{ext}^{z}_{j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) ``` **`Process_status_p_nom_variable_upper`** ```math -u^{z}_{\xi,t,j} \le N^{z}_{j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \mathrm{ext}^{z}_{j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 \wedge \mathrm{on}^{z}_{t,j} +u^{z}_{\xi,t,j} \le N^{z}_{j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com}}_{t,j} \wedge \mathrm{ext}^{z}_{j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 ``` **`Process_start_up_p_nom_variable_upper`** ```math -\mathit{up}^{z}_{\xi,t,j} \le N^{z}_{j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \mathrm{ext}^{z}_{j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 \wedge \mathrm{on}^{z}_{t,j} +\mathit{up}^{z}_{\xi,t,j} \le N^{z}_{j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com}}_{t,j} \wedge \mathrm{ext}^{z}_{j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 ``` **`Process_shut_down_p_nom_variable_upper`** ```math -\mathit{dn}^{z}_{\xi,t,j} \le N^{z}_{j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \mathrm{ext}^{z}_{j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 \wedge \mathrm{on}^{z}_{t,j} +\mathit{dn}^{z}_{\xi,t,j} \le N^{z}_{j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com}}_{t,j} \wedge \mathrm{ext}^{z}_{j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 ``` #### Definitions @@ -470,23 +489,31 @@ u^{z}_{\xi,t,j} \le N^{z}_{j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ \mathit{Process\_commitment\_opex}_{\xi} = \sum_{t \in \mathcal{T}} \sum_{j \in \mathcal{J}} u^{z}_{\xi,t,j} \cdot \mathrm{c}^{z,\mathrm{on}}_{\xi,t,j} \cdot \mathrm{w}_{t} \cdot \mathrm{w}^{y}_{\mathrm{snapshot\_period}(t)} + \sum_{t \in \mathcal{T}} \sum_{j \in \mathcal{J}} \mathit{up}^{z}_{\xi,t,j} \cdot \mathrm{c}^{z,\mathrm{up}}_{\xi,j} + \sum_{t \in \mathcal{T}} \sum_{j \in \mathcal{J}} \mathit{dn}^{z}_{\xi,t,j} \cdot \mathrm{c}^{z,\mathrm{dn}}_{\xi,j} \qquad \forall\, \xi \in \Xi ``` +#### Masks + +**`Process_com_ext`** + +```math +\mathrm{on}^{z,\mathrm{com,ext}}_{t,j} \iff \mathrm{com}^{z}_{j} \wedge \mathrm{ext}^{z}_{j} \wedge \neg \left( \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) \wedge \mathrm{on}^{z}_{t,j} \qquad \forall\, t \in \mathcal{T},\ j \in \mathcal{J} +``` + #### Variable domains **`Process_status`** ```math -u^{z}_{\xi,t,j} \ge 0, u^{z}_{\xi,t,j} \in \mathbb{Z} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \mathrm{on}^{z}_{t,j} +u^{z}_{\xi,t,j} \ge 0, u^{z}_{\xi,t,j} \in \mathbb{Z} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com}}_{t,j} ``` **`Process_start_up`** ```math -\mathit{up}^{z}_{\xi,t,j} \ge 0, \mathit{up}^{z}_{\xi,t,j} \in \mathbb{Z} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \mathrm{on}^{z}_{t,j} +\mathit{up}^{z}_{\xi,t,j} \ge 0, \mathit{up}^{z}_{\xi,t,j} \in \mathbb{Z} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com}}_{t,j} ``` **`Process_shut_down`** ```math -\mathit{dn}^{z}_{\xi,t,j} \ge 0, \mathit{dn}^{z}_{\xi,t,j} \in \mathbb{Z} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \mathrm{on}^{z}_{t,j} +\mathit{dn}^{z}_{\xi,t,j} \ge 0, \mathit{dn}^{z}_{\xi,t,j} \in \mathbb{Z} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com}}_{t,j} ``` diff --git a/docs/examples/pypsa/process_maintenance.md b/docs/examples/pypsa/process_maintenance.md index 0c223e1b..eff8bd65 100644 --- a/docs/examples/pypsa/process_maintenance.md +++ b/docs/examples/pypsa/process_maintenance.md @@ -81,9 +81,7 @@ variables: `Process-maintenance_capacity` — the chosen build while in maintenance, zero otherwise: the product the `maintcap` rows linearize dims: [scenario, snapshot, process] - where: >- - Process_maintainable AND Process_p_nom_extendable - AND NOT (Process_committable AND Process_p_nom_mod > 0) AND Process_active + where: Process_maint_ext absence: zero bounds: lower: 0 @@ -94,8 +92,9 @@ variables: maintenance may also be off dims: [scenario, snapshot, process] where: >- - Process_maintainable AND Process_committable - AND NOT (Process_p_nom_extendable AND NOT (Process_p_nom_mod > 0)) AND Process_active + Process_maintainable + AND Process_committed + AND NOT (Process_p_nom_extendable AND NOT (Process_p_nom_mod > 0)) absence: zero bounds: lower: 0 @@ -112,6 +111,20 @@ given: variables: Process_status: { dims: [scenario, snapshot, process], domain: integer } Process_p_nom_ext: { dims: [process] } + masks: + Process_committed: { dims: [snapshot, process] } + +masks: + Process_maint_ext: + description: >- + a maintainable process with an extendable build, unless it is + committable and modular, that stands in the snapshot's period — the + maintenance rows against the chosen build + where: >- + Process_maintainable + AND Process_p_nom_extendable + AND NOT (Process_committable AND Process_p_nom_mod > 0) + AND Process_active constraints: Process_maint_event_count: @@ -137,54 +150,54 @@ constraints: `Process-maintcap_upper` — the build taken off is at most the chosen build in maintenance, and at most the build less its floor out of it dims: [scenario, snapshot, process] - where: Process_maintainable AND Process_p_nom_extendable AND NOT (Process_committable AND Process_p_nom_mod > 0) AND Process_active + where: Process_maint_ext expression: Process_maintenance_capacity <= Process_p_nom_ext - Process_p_nom_min * (1 - Process_maintenance) Process_maintcap_upper_nommax: description: "`Process-maintcap_upper_nommax` — out of maintenance, no build is taken off" dims: [scenario, snapshot, process] - where: Process_maintainable AND Process_p_nom_extendable AND NOT (Process_committable AND Process_p_nom_mod > 0) AND Process_active + where: Process_maint_ext expression: Process_maintenance_capacity <= Process_p_nom_max * Process_maintenance Process_maintcap_lower_nommax: description: "`Process-maintcap_lower_nommax` — in maintenance, the whole chosen build is taken off" dims: [scenario, snapshot, process] - where: Process_maintainable AND Process_p_nom_extendable AND NOT (Process_committable AND Process_p_nom_mod > 0) AND Process_active + where: Process_maint_ext expression: Process_maintenance_capacity >= Process_p_nom_ext - Process_p_nom_max * (1 - Process_maintenance) Process_maintcap_lower_nommin: description: "`Process-maintcap_lower_nommin` — in maintenance, at least the floor of the build is taken off" dims: [scenario, snapshot, process] - where: Process_maintainable AND Process_p_nom_extendable AND NOT (Process_committable AND Process_p_nom_mod > 0) AND Process_active AND Process_p_nom_min > 0 + where: Process_maint_ext AND Process_p_nom_min > 0 expression: Process_maintenance_capacity >= Process_p_nom_min * Process_maintenance Process_maint_status_le_status: description: "`Process-maint-status-le-status` — the status in maintenance is at most the status" dims: [scenario, snapshot, process] - where: Process_maintainable AND Process_committable AND NOT Process_p_nom_extendable AND Process_active + where: Process_maintainable AND Process_committed AND NOT Process_p_nom_extendable expression: Process_maintenance_status <= Process_status Process_maint_status_le_maint: description: "`Process-maint-status-le-maint` — out of maintenance, the status in maintenance is zero" dims: [scenario, snapshot, process] - where: Process_maintainable AND Process_committable AND NOT Process_p_nom_extendable AND Process_active + where: Process_maintainable AND Process_committed AND NOT Process_p_nom_extendable expression: Process_maintenance_status <= Process_maintenance Process_maint_status_lb: description: "`Process-maint-status-lb` — on and in maintenance, the status in maintenance is one" dims: [scenario, snapshot, process] - where: Process_maintainable AND Process_committable AND NOT Process_p_nom_extendable AND Process_active + where: Process_maintainable AND Process_committed AND NOT Process_p_nom_extendable expression: Process_maintenance_status >= Process_status + Process_maintenance - 1 Process_maint_modstatus_le_status: description: "`Process-maint-modstatus-le-status` — the modules on in maintenance are at most the modules on" dims: [scenario, snapshot, process] - where: Process_maintainable AND Process_committable AND Process_p_nom_mod > 0 AND Process_active + where: Process_maintainable AND Process_committed AND Process_p_nom_mod > 0 expression: Process_maintenance_status <= Process_status Process_maint_modstatus_le_maint: description: >- `Process-maint-modstatus-le-maint` — out of maintenance, no module is on in maintenance; in it, at most the modules the build cap holds dims: [scenario, snapshot, process] - where: Process_maintainable AND Process_committable AND Process_p_nom_mod > 0 AND Process_active + where: Process_maintainable AND Process_committed AND Process_p_nom_mod > 0 expression: Process_maintenance_status <= Process_p_nom_max / Process_p_nom_mod * Process_maintenance Process_maint_modstatus_lb: description: "`Process-maint-modstatus-lb` — in maintenance, every module on is on in maintenance" dims: [scenario, snapshot, process] - where: Process_maintainable AND Process_committable AND Process_p_nom_mod > 0 AND Process_active + where: Process_maintainable AND Process_committed AND Process_p_nom_mod > 0 expression: Process_maintenance_status >= Process_status - Process_p_nom_max / Process_p_nom_mod * (1 - Process_maintenance) assumptions: @@ -271,6 +284,13 @@ assumptions: | $`\mathrm{w}^{\mathrm{gen}}`$ | `snapshot_weightings_generators` over $`\mathcal{T}`$, data another file declares | | $`u^{z}`$ | `Process_status` over $`\Xi \times \mathcal{T} \times \mathcal{J}`$ | | $`Z`$ | `Process_p_nom_ext` over $`\mathcal{J}`$ | +| $`\mathrm{on}^{z,\mathrm{com}}`$ | `Process_committed` over $`\mathcal{T} \times \mathcal{J}`$, a mask another file defines | + +#### Masks + +| Symbol | Meaning | +|---|---| +| $`\mathrm{on}^{z,\mathrm{mnt,ext}}`$ | `Process_maint_ext` over $`\mathcal{T} \times \mathcal{J}`$ — a maintainable process with an extendable build, unless it is committable and modular, that stands in the snapshot's period — the maintenance rows against the chosen build | #### Subject to @@ -295,61 +315,69 @@ assumptions: **`Process_maintcap_upper`** ```math -\mu^{z,\mathrm{nom}}_{\xi,t,j} \le Z_{j} - \underline{\mathrm{z}}^{\mathrm{nom}}_{\xi,j} \cdot \left( 1 - \mu^{z}_{\xi,t,j} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{mnt}^{z}_{j} \wedge \mathrm{ext}^{z}_{j} \wedge \neg \left( \mathrm{com}^{z}_{j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) \wedge \mathrm{on}^{z}_{t,j} +\mu^{z,\mathrm{nom}}_{\xi,t,j} \le Z_{j} - \underline{\mathrm{z}}^{\mathrm{nom}}_{\xi,j} \cdot \left( 1 - \mu^{z}_{\xi,t,j} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{mnt,ext}}_{t,j} ``` **`Process_maintcap_upper_nommax`** ```math -\mu^{z,\mathrm{nom}}_{\xi,t,j} \le \overline{\mathrm{z}}^{\mathrm{nom}}_{\xi,j} \cdot \mu^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{mnt}^{z}_{j} \wedge \mathrm{ext}^{z}_{j} \wedge \neg \left( \mathrm{com}^{z}_{j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) \wedge \mathrm{on}^{z}_{t,j} +\mu^{z,\mathrm{nom}}_{\xi,t,j} \le \overline{\mathrm{z}}^{\mathrm{nom}}_{\xi,j} \cdot \mu^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{mnt,ext}}_{t,j} ``` **`Process_maintcap_lower_nommax`** ```math -\mu^{z,\mathrm{nom}}_{\xi,t,j} \ge Z_{j} - \overline{\mathrm{z}}^{\mathrm{nom}}_{\xi,j} \cdot \left( 1 - \mu^{z}_{\xi,t,j} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{mnt}^{z}_{j} \wedge \mathrm{ext}^{z}_{j} \wedge \neg \left( \mathrm{com}^{z}_{j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) \wedge \mathrm{on}^{z}_{t,j} +\mu^{z,\mathrm{nom}}_{\xi,t,j} \ge Z_{j} - \overline{\mathrm{z}}^{\mathrm{nom}}_{\xi,j} \cdot \left( 1 - \mu^{z}_{\xi,t,j} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{mnt,ext}}_{t,j} ``` **`Process_maintcap_lower_nommin`** ```math -\mu^{z,\mathrm{nom}}_{\xi,t,j} \ge \underline{\mathrm{z}}^{\mathrm{nom}}_{\xi,j} \cdot \mu^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{mnt}^{z}_{j} \wedge \mathrm{ext}^{z}_{j} \wedge \neg \left( \mathrm{com}^{z}_{j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) \wedge \mathrm{on}^{z}_{t,j} \wedge \underline{\mathrm{z}}^{\mathrm{nom}}_{\xi,j} > 0 +\mu^{z,\mathrm{nom}}_{\xi,t,j} \ge \underline{\mathrm{z}}^{\mathrm{nom}}_{\xi,j} \cdot \mu^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{mnt,ext}}_{t,j} \wedge \underline{\mathrm{z}}^{\mathrm{nom}}_{\xi,j} > 0 ``` **`Process_maint_status_le_status`** ```math -\mu^{z,u}_{\xi,t,j} \le u^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{mnt}^{z}_{j} \wedge \mathrm{com}^{z}_{j} \wedge \neg \mathrm{ext}^{z}_{j} \wedge \mathrm{on}^{z}_{t,j} +\mu^{z,u}_{\xi,t,j} \le u^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{mnt}^{z}_{j} \wedge \mathrm{on}^{z,\mathrm{com}}_{t,j} \wedge \neg \mathrm{ext}^{z}_{j} ``` **`Process_maint_status_le_maint`** ```math -\mu^{z,u}_{\xi,t,j} \le \mu^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{mnt}^{z}_{j} \wedge \mathrm{com}^{z}_{j} \wedge \neg \mathrm{ext}^{z}_{j} \wedge \mathrm{on}^{z}_{t,j} +\mu^{z,u}_{\xi,t,j} \le \mu^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{mnt}^{z}_{j} \wedge \mathrm{on}^{z,\mathrm{com}}_{t,j} \wedge \neg \mathrm{ext}^{z}_{j} ``` **`Process_maint_status_lb`** ```math -\mu^{z,u}_{\xi,t,j} \ge u^{z}_{\xi,t,j} + \mu^{z}_{\xi,t,j} - 1 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{mnt}^{z}_{j} \wedge \mathrm{com}^{z}_{j} \wedge \neg \mathrm{ext}^{z}_{j} \wedge \mathrm{on}^{z}_{t,j} +\mu^{z,u}_{\xi,t,j} \ge u^{z}_{\xi,t,j} + \mu^{z}_{\xi,t,j} - 1 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{mnt}^{z}_{j} \wedge \mathrm{on}^{z,\mathrm{com}}_{t,j} \wedge \neg \mathrm{ext}^{z}_{j} ``` **`Process_maint_modstatus_le_status`** ```math -\mu^{z,u}_{\xi,t,j} \le u^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{mnt}^{z}_{j} \wedge \mathrm{com}^{z}_{j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 \wedge \mathrm{on}^{z}_{t,j} +\mu^{z,u}_{\xi,t,j} \le u^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{mnt}^{z}_{j} \wedge \mathrm{on}^{z,\mathrm{com}}_{t,j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 ``` **`Process_maint_modstatus_le_maint`** ```math -\mu^{z,u}_{\xi,t,j} \le \frac{\overline{\mathrm{z}}^{\mathrm{nom}}_{\xi,j}}{\mathrm{z}^{\mathrm{mod}}_{j}} \cdot \mu^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{mnt}^{z}_{j} \wedge \mathrm{com}^{z}_{j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 \wedge \mathrm{on}^{z}_{t,j} +\mu^{z,u}_{\xi,t,j} \le \frac{\overline{\mathrm{z}}^{\mathrm{nom}}_{\xi,j}}{\mathrm{z}^{\mathrm{mod}}_{j}} \cdot \mu^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{mnt}^{z}_{j} \wedge \mathrm{on}^{z,\mathrm{com}}_{t,j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 ``` **`Process_maint_modstatus_lb`** ```math -\mu^{z,u}_{\xi,t,j} \ge u^{z}_{\xi,t,j} - \frac{\overline{\mathrm{z}}^{\mathrm{nom}}_{\xi,j}}{\mathrm{z}^{\mathrm{mod}}_{j}} \cdot \left( 1 - \mu^{z}_{\xi,t,j} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{mnt}^{z}_{j} \wedge \mathrm{com}^{z}_{j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 \wedge \mathrm{on}^{z}_{t,j} +\mu^{z,u}_{\xi,t,j} \ge u^{z}_{\xi,t,j} - \frac{\overline{\mathrm{z}}^{\mathrm{nom}}_{\xi,j}}{\mathrm{z}^{\mathrm{mod}}_{j}} \cdot \left( 1 - \mu^{z}_{\xi,t,j} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{mnt}^{z}_{j} \wedge \mathrm{on}^{z,\mathrm{com}}_{t,j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 +``` + +#### Masks + +**`Process_maint_ext`** + +```math +\mathrm{on}^{z,\mathrm{mnt,ext}}_{t,j} \iff \mathrm{mnt}^{z}_{j} \wedge \mathrm{ext}^{z}_{j} \wedge \neg \left( \mathrm{com}^{z}_{j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) \wedge \mathrm{on}^{z}_{t,j} \qquad \forall\, t \in \mathcal{T},\ j \in \mathcal{J} ``` #### Variable domains @@ -369,13 +397,13 @@ assumptions: **`Process_maintenance_capacity`** ```math -\mu^{z,\mathrm{nom}}_{\xi,t,j} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{mnt}^{z}_{j} \wedge \mathrm{ext}^{z}_{j} \wedge \neg \left( \mathrm{com}^{z}_{j} \wedge \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) \wedge \mathrm{on}^{z}_{t,j} +\mu^{z,\mathrm{nom}}_{\xi,t,j} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{mnt,ext}}_{t,j} ``` **`Process_maintenance_status`** ```math -\mu^{z,u}_{\xi,t,j} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{mnt}^{z}_{j} \wedge \mathrm{com}^{z}_{j} \wedge \neg \left( \mathrm{ext}^{z}_{j} \wedge \neg \left( \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) \right) \wedge \mathrm{on}^{z}_{t,j} +\mu^{z,u}_{\xi,t,j} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{mnt}^{z}_{j} \wedge \mathrm{on}^{z,\mathrm{com}}_{t,j} \wedge \neg \left( \mathrm{ext}^{z}_{j} \wedge \neg \left( \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) \right) ``` #### Assumptions diff --git a/docs/examples/pypsa/process_ramping.md b/docs/examples/pypsa/process_ramping.md index ab830860..21fc5685 100644 --- a/docs/examples/pypsa/process_ramping.md +++ b/docs/examples/pypsa/process_ramping.md @@ -65,6 +65,8 @@ given: Process_p_nom_effective: { dims: [scenario, process] } Process_previous_status: { dims: [scenario, snapshot, process] } Process_p_nom_committed: { dims: [scenario, process] } + masks: + Process_com_ext: { dims: [snapshot, process] } expressions: Process_previous_p: @@ -137,6 +139,16 @@ expressions: * (Process_previous_status - Process_status) otherwise: Process_ramp_down_rate * Process_p_nom_effective +masks: + Process_ramps_from_previous: + description: >- + a snapshot whose ramp reads a previous output — any snapshot but its + period's first, and the horizon's first where the process comes in off + or carries an initial output + where: >- + position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND + (Process_status_initial == 0 OR Process_p_init)) + constraints: Process_p_ramp_limit_up_run_big_m: description: >- @@ -144,11 +156,7 @@ constraints: raises internal power no faster than its limit of the chosen build; the big M releases the row in the snapshot it turns on dims: [scenario, snapshot, process] - where: >- - Process_committable AND Process_p_nom_extendable AND NOT (Process_p_nom_mod > 0) - AND (Process_ramp_limit_up OR Process_ramp_limit_start_up) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Process_status_initial == 0 OR Process_p_init))) - AND Process_active + where: Process_com_ext AND (Process_ramp_limit_up OR Process_ramp_limit_start_up) AND Process_ramps_from_previous expression: >- Process_p - Process_previous_p <= Process_ramp_up_rate * Process_p_nom_ext @@ -159,11 +167,7 @@ constraints: committed extendable process ramps no further than its start-up ramp of the chosen build; the big M releases the row everywhere else dims: [scenario, snapshot, process] - where: >- - Process_committable AND Process_p_nom_extendable AND NOT (Process_p_nom_mod > 0) - AND (Process_ramp_limit_up OR Process_ramp_limit_start_up) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Process_status_initial == 0 OR Process_p_init))) - AND Process_active + where: Process_com_ext AND (Process_ramp_limit_up OR Process_ramp_limit_start_up) AND Process_ramps_from_previous expression: >- Process_p - Process_previous_p <= Process_start_up_rate * Process_p_nom_ext @@ -175,10 +179,9 @@ constraints: releases the row in the snapshot it turns off dims: [scenario, snapshot, process] where: >- - Process_committable AND Process_p_nom_extendable AND NOT (Process_p_nom_mod > 0) + Process_com_ext AND (Process_ramp_limit_down OR Process_ramp_limit_shut_down) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Process_status_initial == 0 OR Process_p_init))) - AND Process_active + AND Process_ramps_from_previous expression: >- Process_previous_p - Process_p <= Process_ramp_down_rate * Process_p_nom_ext @@ -190,10 +193,9 @@ constraints: the chosen build; the big M releases the row everywhere else dims: [scenario, snapshot, process] where: >- - Process_committable AND Process_p_nom_extendable AND NOT (Process_p_nom_mod > 0) + Process_com_ext AND (Process_ramp_limit_down OR Process_ramp_limit_shut_down) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Process_status_initial == 0 OR Process_p_init))) - AND Process_active + AND Process_ramps_from_previous expression: >- Process_previous_p - Process_p <= Process_shut_down_rate * Process_p_nom_ext @@ -210,7 +212,7 @@ constraints: where: >- (Process_ramp_limit_up OR Process_ramp_limit_start_up) AND NOT (Process_committable AND Process_p_nom_extendable AND NOT (Process_p_nom_mod > 0)) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Process_status_initial == 0 OR Process_p_init))) + AND Process_ramps_from_previous AND Process_active expression: Process_p - Process_previous_p <= Process_ramp_up_allowance Process_p_ramp_limit_down: @@ -225,7 +227,7 @@ constraints: where: >- (Process_ramp_limit_down OR Process_ramp_limit_shut_down) AND NOT (Process_committable AND Process_p_nom_extendable AND NOT (Process_p_nom_mod > 0)) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Process_status_initial == 0 OR Process_p_init))) + AND Process_ramps_from_previous AND Process_active expression: Process_previous_p - Process_p <= Process_ramp_down_allowance @@ -281,6 +283,7 @@ assumptions: | $`\widetilde{\mathrm{z}}^{\mathrm{nom}}`$ | `Process_p_nom_effective` over $`\Xi \times \mathcal{J}`$, an expression another file defines | | $`\overleftarrow{u}^{z}`$ | `Process_previous_status` over $`\Xi \times \mathcal{T} \times \mathcal{J}`$, an expression another file defines | | $`\widehat{\mathrm{z}}^{\mathrm{nom}}`$ | `Process_p_nom_committed` over $`\Xi \times \mathcal{J}`$, an expression another file defines | +| $`\mathrm{on}^{z,\mathrm{com,ext}}`$ | `Process_com_ext` over $`\mathcal{T} \times \mathcal{J}`$, a mask another file defines | #### Definitions @@ -294,6 +297,12 @@ assumptions: | $`\Delta^{z,+}`$ | `Process_ramp_up_allowance` over $`\Xi \times \mathcal{T} \times \mathcal{J}`$ — how far a process may raise internal power between two snapshots — its ramp limit of the build while it stays on, plus its start-up ramp in the snapshot it turns on | | $`\Delta^{z,-}`$ | `Process_ramp_down_allowance` over $`\Xi \times \mathcal{T} \times \mathcal{J}`$ — how far a process may lower internal power between two snapshots — its ramp limit of the build while it stays on, plus its shut-down ramp in the snapshot it turns off | +#### Masks + +| Symbol | Meaning | +|---|---| +| $`\mathrm{prev}^{z}`$ | `Process_ramps_from_previous` over $`\Xi \times \mathcal{T} \times \mathcal{J}`$ — a snapshot whose ramp reads a previous output — any snapshot but its period's first, and the horizon's first where the process comes in off or carries an initial output | + $`\mathrm{pos}(t)`$ denotes where index $`t`$ sits along its dimension's own order — the order `shift` steps along, not the order labels sort in — counted from $`0`$. The index itself stays the coordinate, so $`t`$ compares against labels and $`\mathrm{pos}(t)`$ against positions. $`\mathrm{pos}_{\mathrm{relation}(t)}(t)`$ counts within the group a relation puts $`t`$ in: the subscript names the map, $`\mathcal{T}_{\mathrm{relation}(t)}`$ is the group it lands in, and that group has a first position of its own. @@ -303,37 +312,37 @@ $`\mathrm{pos}_{\mathrm{relation}(t)}(t)`$ counts within the group a relation pu **`Process_p_ramp_limit_up_run_big_m`** ```math -z_{\xi,t,j} - \overleftarrow{z}_{\xi,t,j} \le \widetilde{\mathrm{ru}}^{z}_{\xi,t,j} \cdot Z_{j} + \mathrm{M}^{z}_{\xi,j} - \mathrm{M}^{z}_{\xi,j} \cdot \overleftarrow{u}^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \mathrm{ext}^{z}_{j} \wedge \neg \left( \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) \wedge \left( \mathrm{ru}^{z}_{\xi,t,j} \text{ is defined} \vee \mathrm{ru}^{z,\mathrm{up}}_{\xi,j} \text{ is defined} \right) \wedge \left( \mathrm{pos}_{\mathrm{snapshot\_period}(t)}(t) > 0 \vee \mathrm{pos}(t) = 0 \wedge \left( \mathrm{u}^{z,0}_{\xi,j} = 0 \vee \mathrm{z}^{0}_{\xi,j} \text{ is defined} \right) \right) \wedge \mathrm{on}^{z}_{t,j} +z_{\xi,t,j} - \overleftarrow{z}_{\xi,t,j} \le \widetilde{\mathrm{ru}}^{z}_{\xi,t,j} \cdot Z_{j} + \mathrm{M}^{z}_{\xi,j} - \mathrm{M}^{z}_{\xi,j} \cdot \overleftarrow{u}^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com,ext}}_{t,j} \wedge \left( \mathrm{ru}^{z}_{\xi,t,j} \text{ is defined} \vee \mathrm{ru}^{z,\mathrm{up}}_{\xi,j} \text{ is defined} \right) \wedge \mathrm{prev}^{z}_{\xi,t,j} ``` **`Process_p_ramp_limit_up_start_big_m`** ```math -z_{\xi,t,j} - \overleftarrow{z}_{\xi,t,j} \le \widetilde{\mathrm{ru}}^{z,\mathrm{up}}_{\xi,j} \cdot Z_{j} + \mathrm{M}^{z}_{\xi,j} - \mathrm{M}^{z}_{\xi,j} \cdot \mathit{up}^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \mathrm{ext}^{z}_{j} \wedge \neg \left( \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) \wedge \left( \mathrm{ru}^{z}_{\xi,t,j} \text{ is defined} \vee \mathrm{ru}^{z,\mathrm{up}}_{\xi,j} \text{ is defined} \right) \wedge \left( \mathrm{pos}_{\mathrm{snapshot\_period}(t)}(t) > 0 \vee \mathrm{pos}(t) = 0 \wedge \left( \mathrm{u}^{z,0}_{\xi,j} = 0 \vee \mathrm{z}^{0}_{\xi,j} \text{ is defined} \right) \right) \wedge \mathrm{on}^{z}_{t,j} +z_{\xi,t,j} - \overleftarrow{z}_{\xi,t,j} \le \widetilde{\mathrm{ru}}^{z,\mathrm{up}}_{\xi,j} \cdot Z_{j} + \mathrm{M}^{z}_{\xi,j} - \mathrm{M}^{z}_{\xi,j} \cdot \mathit{up}^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com,ext}}_{t,j} \wedge \left( \mathrm{ru}^{z}_{\xi,t,j} \text{ is defined} \vee \mathrm{ru}^{z,\mathrm{up}}_{\xi,j} \text{ is defined} \right) \wedge \mathrm{prev}^{z}_{\xi,t,j} ``` **`Process_p_ramp_limit_down_run_big_m`** ```math -\overleftarrow{z}_{\xi,t,j} - z_{\xi,t,j} \le \widetilde{\mathrm{rd}}^{z}_{\xi,t,j} \cdot Z_{j} + \mathrm{M}^{z}_{\xi,j} - \mathrm{M}^{z}_{\xi,j} \cdot u^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \mathrm{ext}^{z}_{j} \wedge \neg \left( \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) \wedge \left( \mathrm{rd}^{z}_{\xi,t,j} \text{ is defined} \vee \mathrm{rd}^{z,\mathrm{dn}}_{\xi,j} \text{ is defined} \right) \wedge \left( \mathrm{pos}_{\mathrm{snapshot\_period}(t)}(t) > 0 \vee \mathrm{pos}(t) = 0 \wedge \left( \mathrm{u}^{z,0}_{\xi,j} = 0 \vee \mathrm{z}^{0}_{\xi,j} \text{ is defined} \right) \right) \wedge \mathrm{on}^{z}_{t,j} +\overleftarrow{z}_{\xi,t,j} - z_{\xi,t,j} \le \widetilde{\mathrm{rd}}^{z}_{\xi,t,j} \cdot Z_{j} + \mathrm{M}^{z}_{\xi,j} - \mathrm{M}^{z}_{\xi,j} \cdot u^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com,ext}}_{t,j} \wedge \left( \mathrm{rd}^{z}_{\xi,t,j} \text{ is defined} \vee \mathrm{rd}^{z,\mathrm{dn}}_{\xi,j} \text{ is defined} \right) \wedge \mathrm{prev}^{z}_{\xi,t,j} ``` **`Process_p_ramp_limit_down_shut_big_m`** ```math -\overleftarrow{z}_{\xi,t,j} - z_{\xi,t,j} \le \widetilde{\mathrm{rd}}^{z,\mathrm{dn}}_{\xi,j} \cdot Z_{j} + \mathrm{M}^{z}_{\xi,j} - \mathrm{M}^{z}_{\xi,j} \cdot \mathit{dn}^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \mathrm{ext}^{z}_{j} \wedge \neg \left( \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) \wedge \left( \mathrm{rd}^{z}_{\xi,t,j} \text{ is defined} \vee \mathrm{rd}^{z,\mathrm{dn}}_{\xi,j} \text{ is defined} \right) \wedge \left( \mathrm{pos}_{\mathrm{snapshot\_period}(t)}(t) > 0 \vee \mathrm{pos}(t) = 0 \wedge \left( \mathrm{u}^{z,0}_{\xi,j} = 0 \vee \mathrm{z}^{0}_{\xi,j} \text{ is defined} \right) \right) \wedge \mathrm{on}^{z}_{t,j} +\overleftarrow{z}_{\xi,t,j} - z_{\xi,t,j} \le \widetilde{\mathrm{rd}}^{z,\mathrm{dn}}_{\xi,j} \cdot Z_{j} + \mathrm{M}^{z}_{\xi,j} - \mathrm{M}^{z}_{\xi,j} \cdot \mathit{dn}^{z}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \mathrm{on}^{z,\mathrm{com,ext}}_{t,j} \wedge \left( \mathrm{rd}^{z}_{\xi,t,j} \text{ is defined} \vee \mathrm{rd}^{z,\mathrm{dn}}_{\xi,j} \text{ is defined} \right) \wedge \mathrm{prev}^{z}_{\xi,t,j} ``` **`Process_p_ramp_limit_up`** ```math -z_{\xi,t,j} - \overleftarrow{z}_{\xi,t,j} \le \Delta^{z,+}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \left( \mathrm{ru}^{z}_{\xi,t,j} \text{ is defined} \vee \mathrm{ru}^{z,\mathrm{up}}_{\xi,j} \text{ is defined} \right) \wedge \neg \left( \mathrm{com}^{z}_{j} \wedge \mathrm{ext}^{z}_{j} \wedge \neg \left( \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) \right) \wedge \left( \mathrm{pos}_{\mathrm{snapshot\_period}(t)}(t) > 0 \vee \mathrm{pos}(t) = 0 \wedge \left( \mathrm{u}^{z,0}_{\xi,j} = 0 \vee \mathrm{z}^{0}_{\xi,j} \text{ is defined} \right) \right) \wedge \mathrm{on}^{z}_{t,j} +z_{\xi,t,j} - \overleftarrow{z}_{\xi,t,j} \le \Delta^{z,+}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \left( \mathrm{ru}^{z}_{\xi,t,j} \text{ is defined} \vee \mathrm{ru}^{z,\mathrm{up}}_{\xi,j} \text{ is defined} \right) \wedge \neg \left( \mathrm{com}^{z}_{j} \wedge \mathrm{ext}^{z}_{j} \wedge \neg \left( \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) \right) \wedge \mathrm{prev}^{z}_{\xi,t,j} \wedge \mathrm{on}^{z}_{t,j} ``` **`Process_p_ramp_limit_down`** ```math -\overleftarrow{z}_{\xi,t,j} - z_{\xi,t,j} \le \Delta^{z,-}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \left( \mathrm{rd}^{z}_{\xi,t,j} \text{ is defined} \vee \mathrm{rd}^{z,\mathrm{dn}}_{\xi,j} \text{ is defined} \right) \wedge \neg \left( \mathrm{com}^{z}_{j} \wedge \mathrm{ext}^{z}_{j} \wedge \neg \left( \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) \right) \wedge \left( \mathrm{pos}_{\mathrm{snapshot\_period}(t)}(t) > 0 \vee \mathrm{pos}(t) = 0 \wedge \left( \mathrm{u}^{z,0}_{\xi,j} = 0 \vee \mathrm{z}^{0}_{\xi,j} \text{ is defined} \right) \right) \wedge \mathrm{on}^{z}_{t,j} +\overleftarrow{z}_{\xi,t,j} - z_{\xi,t,j} \le \Delta^{z,-}_{\xi,t,j} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} \,:\, \left( \mathrm{rd}^{z}_{\xi,t,j} \text{ is defined} \vee \mathrm{rd}^{z,\mathrm{dn}}_{\xi,j} \text{ is defined} \right) \wedge \neg \left( \mathrm{com}^{z}_{j} \wedge \mathrm{ext}^{z}_{j} \wedge \neg \left( \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) \right) \wedge \mathrm{prev}^{z}_{\xi,t,j} \wedge \mathrm{on}^{z}_{t,j} ``` #### Definitions @@ -380,6 +389,14 @@ z_{\xi,t,j} - \overleftarrow{z}_{\xi,t,j} \le \Delta^{z,+}_{\xi,t,j} \qquad \for \Delta^{z,-}_{\xi,t,j} = \begin{cases} \widetilde{\mathrm{rd}}^{z}_{\xi,t,j} \cdot \widehat{\mathrm{z}}^{\mathrm{nom}}_{\xi,j} \cdot u^{z}_{\xi,t,j} + \widetilde{\mathrm{rd}}^{z,\mathrm{dn}}_{\xi,j} \cdot \widehat{\mathrm{z}}^{\mathrm{nom}}_{\xi,j} \cdot \left( \overleftarrow{u}^{z}_{\xi,t,j} - u^{z}_{\xi,t,j} \right) & \text{if } \mathrm{com}^{z}_{j} \\ \widetilde{\mathrm{rd}}^{z}_{\xi,t,j} \cdot \widetilde{\mathrm{z}}^{\mathrm{nom}}_{\xi,j} & \text{otherwise} \end{cases} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} ``` +#### Masks + +**`Process_ramps_from_previous`** + +```math +\mathrm{prev}^{z}_{\xi,t,j} \iff \mathrm{pos}_{\mathrm{snapshot\_period}(t)}(t) > 0 \vee \mathrm{pos}(t) = 0 \wedge \left( \mathrm{u}^{z,0}_{\xi,j} = 0 \vee \mathrm{z}^{0}_{\xi,j} \text{ is defined} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ j \in \mathcal{J} +``` + #### Assumptions **`Process_came_in_running_unless_committable`** diff --git a/docs/examples/pypsa/storage_unit.md b/docs/examples/pypsa/storage_unit.md index f02ac6e1..16663ed0 100644 --- a/docs/examples/pypsa/storage_unit.md +++ b/docs/examples/pypsa/storage_unit.md @@ -308,70 +308,82 @@ expressions: sum(StorageUnit_sign * (StorageUnit_p_dispatch - StorageUnit_p_store), by=StorageUnit_bus, over=storage_unit, into=bus) adds_to: Bus_injection +masks: + StorageUnit_fix: + description: >- + a storage unit with a fixed build that stands in the snapshot's period — + PyPSA's `fix` rows + where: NOT StorageUnit_p_nom_extendable AND StorageUnit_active + StorageUnit_ext: + description: >- + a storage unit with an extendable build that stands in the snapshot's + period — PyPSA's `ext` rows + where: StorageUnit_p_nom_extendable AND StorageUnit_active + constraints: StorageUnit_fix_p_dispatch_lower: description: "`StorageUnit-fix-p_dispatch-lower` — dispatch is non-negative" dims: [scenario, snapshot, storage_unit] - where: not StorageUnit_p_nom_extendable AND StorageUnit_active + where: StorageUnit_fix 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" dims: [scenario, snapshot, storage_unit] - where: not StorageUnit_p_nom_extendable AND StorageUnit_active + where: StorageUnit_fix 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" dims: [scenario, snapshot, storage_unit] - where: not StorageUnit_p_nom_extendable AND StorageUnit_active + where: StorageUnit_fix 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 dims: [scenario, snapshot, storage_unit] - where: not StorageUnit_p_nom_extendable AND StorageUnit_active + where: StorageUnit_fix 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" dims: [scenario, snapshot, storage_unit] - where: not StorageUnit_p_nom_extendable AND StorageUnit_active + where: StorageUnit_fix 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" dims: [scenario, snapshot, storage_unit] - where: not StorageUnit_p_nom_extendable AND StorageUnit_active + where: StorageUnit_fix expression: StorageUnit_state_of_charge <= StorageUnit_max_hours * StorageUnit_p_nom StorageUnit_ext_p_dispatch_lower: description: "`StorageUnit-ext-p_dispatch-lower` — dispatch is non-negative" dims: [scenario, snapshot, storage_unit] - where: StorageUnit_p_nom_extendable AND StorageUnit_active + where: StorageUnit_ext 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" dims: [scenario, snapshot, storage_unit] - where: StorageUnit_p_nom_extendable AND StorageUnit_active + where: StorageUnit_ext 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" dims: [scenario, snapshot, storage_unit] - where: StorageUnit_p_nom_extendable AND StorageUnit_active + where: StorageUnit_ext 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 dims: [scenario, snapshot, storage_unit] - where: StorageUnit_p_nom_extendable AND StorageUnit_active + where: StorageUnit_ext 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" dims: [scenario, snapshot, storage_unit] - where: StorageUnit_p_nom_extendable AND StorageUnit_active + where: StorageUnit_ext 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" dims: [scenario, snapshot, storage_unit] - where: StorageUnit_p_nom_extendable AND StorageUnit_active + where: StorageUnit_ext 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 in every scenario" @@ -569,6 +581,13 @@ assumptions: | $`\mathit{StorageUnit\_additions}`$ | `StorageUnit_additions` over $`\mathcal{Y} \times \mathcal{I}`$ | | $`\mathit{StorageUnit\_injection}`$ | `StorageUnit_injection` over $`\Xi \times \mathcal{T} \times \mathcal{N}`$ | +#### Masks + +| Symbol | Meaning | +|---|---| +| $`\mathrm{on}^{h,\mathrm{fix}}`$ | `StorageUnit_fix` over $`\mathcal{T} \times \mathcal{S}`$ — a storage unit with a fixed build that stands in the snapshot's period — PyPSA's `fix` rows | +| $`\mathrm{on}^{h,\mathrm{ext}}`$ | `StorageUnit_ext` over $`\mathcal{T} \times \mathcal{S}`$ — a storage unit with an extendable build that stands in the snapshot's period — PyPSA's `ext` rows | + $`t \ominus k`$ denotes cyclic translation: index $`t-k`$ taken modulo the size of the dimension (`roll`). Plain $`t-k`$ (`shift`) has no wraparound — terms translated past the edge are simply absent. $`t \ominus^{\mathrm{relation}(t)} k`$ denotes a translation counted inside the group a relation puts $`t`$ in (`shift(by=relation)`), so a term never crosses out of its own group. @@ -584,73 +603,73 @@ $`\lvert \mathcal{T} \rvert`$ denotes the size of the set being counted along, a **`StorageUnit_fix_p_dispatch_lower`** ```math -h^{+}_{\xi,t,s} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \neg \mathrm{ext}^{h}_{s} \wedge \mathrm{on}^{h}_{t,s} +h^{+}_{\xi,t,s} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \mathrm{on}^{h,\mathrm{fix}}_{t,s} ``` **`StorageUnit_fix_p_dispatch_upper`** ```math -h^{+}_{\xi,t,s} \le \overline{\mathrm{h}}_{\xi,t,s} \cdot \mathrm{h}^{\mathrm{nom}}_{\xi,s} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \neg \mathrm{ext}^{h}_{s} \wedge \mathrm{on}^{h}_{t,s} +h^{+}_{\xi,t,s} \le \overline{\mathrm{h}}_{\xi,t,s} \cdot \mathrm{h}^{\mathrm{nom}}_{\xi,s} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \mathrm{on}^{h,\mathrm{fix}}_{t,s} ``` **`StorageUnit_fix_p_store_lower`** ```math -h^{-}_{\xi,t,s} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \neg \mathrm{ext}^{h}_{s} \wedge \mathrm{on}^{h}_{t,s} +h^{-}_{\xi,t,s} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \mathrm{on}^{h,\mathrm{fix}}_{t,s} ``` **`StorageUnit_fix_p_store_upper`** ```math -h^{-}_{\xi,t,s} \le -\underline{\mathrm{h}}_{\xi,t,s} \cdot \mathrm{h}^{\mathrm{nom}}_{\xi,s} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \neg \mathrm{ext}^{h}_{s} \wedge \mathrm{on}^{h}_{t,s} +h^{-}_{\xi,t,s} \le -\underline{\mathrm{h}}_{\xi,t,s} \cdot \mathrm{h}^{\mathrm{nom}}_{\xi,s} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \mathrm{on}^{h,\mathrm{fix}}_{t,s} ``` **`StorageUnit_fix_state_of_charge_lower`** ```math -\mathit{soc}_{\xi,t,s} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \neg \mathrm{ext}^{h}_{s} \wedge \mathrm{on}^{h}_{t,s} +\mathit{soc}_{\xi,t,s} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \mathrm{on}^{h,\mathrm{fix}}_{t,s} ``` **`StorageUnit_fix_state_of_charge_upper`** ```math -\mathit{soc}_{\xi,t,s} \le \mathrm{T}^{h}_{\xi,s} \cdot \mathrm{h}^{\mathrm{nom}}_{\xi,s} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \neg \mathrm{ext}^{h}_{s} \wedge \mathrm{on}^{h}_{t,s} +\mathit{soc}_{\xi,t,s} \le \mathrm{T}^{h}_{\xi,s} \cdot \mathrm{h}^{\mathrm{nom}}_{\xi,s} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \mathrm{on}^{h,\mathrm{fix}}_{t,s} ``` **`StorageUnit_ext_p_dispatch_lower`** ```math -h^{+}_{\xi,t,s} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \mathrm{ext}^{h}_{s} \wedge \mathrm{on}^{h}_{t,s} +h^{+}_{\xi,t,s} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \mathrm{on}^{h,\mathrm{ext}}_{t,s} ``` **`StorageUnit_ext_p_dispatch_upper`** ```math -h^{+}_{\xi,t,s} \le \overline{\mathrm{h}}_{\xi,t,s} \cdot H_{s} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \mathrm{ext}^{h}_{s} \wedge \mathrm{on}^{h}_{t,s} +h^{+}_{\xi,t,s} \le \overline{\mathrm{h}}_{\xi,t,s} \cdot H_{s} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \mathrm{on}^{h,\mathrm{ext}}_{t,s} ``` **`StorageUnit_ext_p_store_lower`** ```math -h^{-}_{\xi,t,s} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \mathrm{ext}^{h}_{s} \wedge \mathrm{on}^{h}_{t,s} +h^{-}_{\xi,t,s} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \mathrm{on}^{h,\mathrm{ext}}_{t,s} ``` **`StorageUnit_ext_p_store_upper`** ```math -h^{-}_{\xi,t,s} \le -\underline{\mathrm{h}}_{\xi,t,s} \cdot H_{s} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \mathrm{ext}^{h}_{s} \wedge \mathrm{on}^{h}_{t,s} +h^{-}_{\xi,t,s} \le -\underline{\mathrm{h}}_{\xi,t,s} \cdot H_{s} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \mathrm{on}^{h,\mathrm{ext}}_{t,s} ``` **`StorageUnit_ext_state_of_charge_lower`** ```math -\mathit{soc}_{\xi,t,s} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \mathrm{ext}^{h}_{s} \wedge \mathrm{on}^{h}_{t,s} +\mathit{soc}_{\xi,t,s} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \mathrm{on}^{h,\mathrm{ext}}_{t,s} ``` **`StorageUnit_ext_state_of_charge_upper`** ```math -\mathit{soc}_{\xi,t,s} \le \mathrm{T}^{h}_{\xi,s} \cdot H_{s} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \mathrm{ext}^{h}_{s} \wedge \mathrm{on}^{h}_{t,s} +\mathit{soc}_{\xi,t,s} \le \mathrm{T}^{h}_{\xi,s} \cdot H_{s} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ s \in \mathcal{S} \,:\, \mathrm{on}^{h,\mathrm{ext}}_{t,s} ``` **`StorageUnit_ext_p_nom_lower`** @@ -757,6 +776,20 @@ h^{-}_{\xi,t,s} = \mathrm{h}^{-,\mathrm{set}}_{\xi,t,s} \qquad \forall\, \xi \in \mathit{StorageUnit\_injection}_{\xi,t,n} = \sum_{s \in \mathcal{S} \,:\, \mathrm{StorageUnit\_bus}(s) = n} \mathrm{sgn}^{h}_{s} \cdot \left( h^{+}_{\xi,t,s} - h^{-}_{\xi,t,s} \right) \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ n \in \mathcal{N} ``` +#### Masks + +**`StorageUnit_fix`** + +```math +\mathrm{on}^{h,\mathrm{fix}}_{t,s} \iff \neg \mathrm{ext}^{h}_{s} \wedge \mathrm{on}^{h}_{t,s} \qquad \forall\, t \in \mathcal{T},\ s \in \mathcal{S} +``` + +**`StorageUnit_ext`** + +```math +\mathrm{on}^{h,\mathrm{ext}}_{t,s} \iff \mathrm{ext}^{h}_{s} \wedge \mathrm{on}^{h}_{t,s} \qquad \forall\, t \in \mathcal{T},\ s \in \mathcal{S} +``` + #### Variable domains **`StorageUnit_p_dispatch`** diff --git a/docs/examples/pypsa/transformer.md b/docs/examples/pypsa/transformer.md index c8c87e20..c28299af 100644 --- a/docs/examples/pypsa/transformer.md +++ b/docs/examples/pypsa/transformer.md @@ -144,7 +144,7 @@ variables: a line does; absent, and zero in the balance, where the network is lossless dims: [scenario, snapshot, transformer] - where: transmission_losses AND Transformer_active + where: Transformer_lossy absence: zero bounds: lower: 0 @@ -200,6 +200,13 @@ expressions: + sum(Transformer_phase_shift * Transformer_phase_shift_cycle_weight, over=transformer) adds_to: Cycle_angle_sum +masks: + Transformer_lossy: + description: >- + a transformer that stands in the snapshot's period, where the run models + transmission losses + where: transmission_losses AND Transformer_active + constraints: Transformer_fix_s_lower: description: "`Transformer-fix-s-lower` — a fixed transformer carries at least the negative of its rating, the loss counted against it" @@ -244,7 +251,7 @@ constraints: Transformer_loss_upper: description: "`Transformer-loss_upper` — a transformer dissipates at most the loss at its rating" dims: [scenario, snapshot, transformer] - where: transmission_losses AND Transformer_active + where: Transformer_lossy expression: Transformer_loss <= Transformer_loss_max Transformer_loss_tangents_forward: description: >- @@ -252,14 +259,14 @@ constraints: loss sits above every cut to its curve for flow one way, as a line's does, over the segment dimension dims: [scenario, snapshot, transformer, segment] - where: transmission_losses AND Transformer_active + where: Transformer_lossy expression: Transformer_loss + Transformer_loss_slope * Transformer_s >= Transformer_loss_offset Transformer_loss_tangents_reverse: description: >- `Transformer-loss_tangents-{k}--1`, `Transformer-loss_secants-neg` — the same fan mirrored, the loss depending on the flow's magnitude dims: [scenario, snapshot, transformer, segment] - where: transmission_losses AND Transformer_active + where: Transformer_lossy expression: Transformer_loss - Transformer_loss_slope * Transformer_s >= Transformer_loss_offset ``` @@ -326,6 +333,12 @@ constraints: | $`\mathit{Transformer\_injection}`$ | `Transformer_injection` over $`\Xi \times \mathcal{T} \times \mathcal{N}`$ | | $`\mathit{Transformer\_angle\_sum}`$ | `Transformer_angle_sum` over $`\Xi \times \mathcal{T} \times \mathcal{C}`$ | +#### Masks + +| Symbol | Meaning | +|---|---| +| $`\mathrm{on}^{\sigma,\mathrm{lossy}}`$ | `Transformer_lossy` over $`\mathcal{T} \times \mathcal{M}`$ — a transformer that stands in the snapshot's period, where the run models transmission losses | + Upright is what the data supplies — a parameter such as $`\mathrm{Transformer\_phase\_shift\_varying}`$, a coordinate map, a label — and italic is what the solver chooses, such as $`\mathit{Transformer\_phase\_shift}`$. An index is italic too, being what a quantifier chooses, and a set is script. #### Subject to @@ -381,19 +394,19 @@ Upright is what the data supplies — a parameter such as $`\mathrm{Transformer\ **`Transformer_loss_upper`** ```math -\ell^{\sigma}_{\xi,t,m} \le \overline{\ell}^{\sigma}_{\xi,t,m} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ m \in \mathcal{M} \,:\, \mathrm{lossy} \wedge \mathrm{on}^{\sigma}_{t,m} +\ell^{\sigma}_{\xi,t,m} \le \overline{\ell}^{\sigma}_{\xi,t,m} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ m \in \mathcal{M} \,:\, \mathrm{on}^{\sigma,\mathrm{lossy}}_{t,m} ``` **`Transformer_loss_tangents_forward`** ```math -\ell^{\sigma}_{\xi,t,m} + \mathrm{a}^{\sigma}_{\xi,t,m,s} \cdot \sigma_{\xi,t,m} \ge \mathrm{b}^{\sigma}_{\xi,t,m,s} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ m \in \mathcal{M},\ s \in \mathcal{S} \,:\, \mathrm{lossy} \wedge \mathrm{on}^{\sigma}_{t,m} +\ell^{\sigma}_{\xi,t,m} + \mathrm{a}^{\sigma}_{\xi,t,m,s} \cdot \sigma_{\xi,t,m} \ge \mathrm{b}^{\sigma}_{\xi,t,m,s} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ m \in \mathcal{M},\ s \in \mathcal{S} \,:\, \mathrm{on}^{\sigma,\mathrm{lossy}}_{t,m} ``` **`Transformer_loss_tangents_reverse`** ```math -\ell^{\sigma}_{\xi,t,m} - \mathrm{a}^{\sigma}_{\xi,t,m,s} \cdot \sigma_{\xi,t,m} \ge \mathrm{b}^{\sigma}_{\xi,t,m,s} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ m \in \mathcal{M},\ s \in \mathcal{S} \,:\, \mathrm{lossy} \wedge \mathrm{on}^{\sigma}_{t,m} +\ell^{\sigma}_{\xi,t,m} - \mathrm{a}^{\sigma}_{\xi,t,m,s} \cdot \sigma_{\xi,t,m} \ge \mathrm{b}^{\sigma}_{\xi,t,m,s} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ m \in \mathcal{M},\ s \in \mathcal{S} \,:\, \mathrm{on}^{\sigma,\mathrm{lossy}}_{t,m} ``` #### Definitions @@ -422,6 +435,14 @@ Upright is what the data supplies — a parameter such as $`\mathrm{Transformer\ \mathit{Transformer\_angle\_sum}_{\xi,t,c} = \sum_{m \in \mathcal{M}} \sigma_{\xi,t,m} \cdot \mathrm{x}^{\sigma}_{m,c} + \sum_{m \in \mathcal{M}} \vartheta_{t,m,c} + \sum_{m \in \mathcal{M}} \mathit{Transformer\_phase\_shift}_{\xi,t,m} \cdot \mathrm{Transformer\_phase\_shift\_cycle\_weight}_{m,c} \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ c \in \mathcal{C} ``` +#### Masks + +**`Transformer_lossy`** + +```math +\mathrm{on}^{\sigma,\mathrm{lossy}}_{t,m} \iff \mathrm{lossy} \wedge \mathrm{on}^{\sigma}_{t,m} \qquad \forall\, t \in \mathcal{T},\ m \in \mathcal{M} +``` + #### Variable domains **`Transformer_s`** @@ -433,7 +454,7 @@ Upright is what the data supplies — a parameter such as $`\mathrm{Transformer\ **`Transformer_loss`** ```math -\ell^{\sigma}_{\xi,t,m} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ m \in \mathcal{M} \,:\, \mathrm{lossy} \wedge \mathrm{on}^{\sigma}_{t,m} +\ell^{\sigma}_{\xi,t,m} \ge 0 \qquad \forall\, \xi \in \Xi,\ t \in \mathcal{T},\ m \in \mathcal{M} \,:\, \mathrm{on}^{\sigma,\mathrm{lossy}}_{t,m} ``` **`Transformer_phase_shift`** diff --git a/examples/pypsa.yaml b/examples/pypsa.yaml index d6e7712e..eb144fdb 100644 --- a/examples/pypsa.yaml +++ b/examples/pypsa.yaml @@ -1351,21 +1351,21 @@ variables: the rows below cap at one, or at the module count where the build is modular dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_active + where: Generator_committed 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" dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_active + where: Generator_committed 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" dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_active + where: Generator_committed domain: integer bounds: lower: 0 @@ -1390,9 +1390,7 @@ variables: `Generator-maintenance_capacity` — the chosen build while in maintenance, zero otherwise: the product the `maintcap` rows linearize dims: [scenario, snapshot, generator] - where: >- - Generator_maintainable AND Generator_p_nom_extendable - AND NOT (Generator_committable AND Generator_p_nom_mod > 0) AND Generator_active + where: Generator_maint_ext absence: zero bounds: lower: 0 @@ -1403,8 +1401,9 @@ variables: maintenance may also be off dims: [scenario, snapshot, generator] where: >- - Generator_maintainable AND Generator_committable - AND NOT (Generator_p_nom_extendable AND NOT (Generator_p_nom_mod > 0)) AND Generator_active + Generator_maintainable + AND Generator_committed + AND NOT (Generator_p_nom_extendable AND NOT (Generator_p_nom_mod > 0)) absence: zero bounds: lower: 0 @@ -1421,21 +1420,21 @@ variables: the rows below cap at one, or at the module count where the build is modular dims: [scenario, snapshot, link] - where: Link_committable AND Link_active + where: Link_committed domain: integer bounds: lower: 0 Link_start_up: description: "`Link-start_up` — how much of a committable link turns on this snapshot, capped as the status is" dims: [scenario, snapshot, link] - where: Link_committable AND Link_active + where: Link_committed domain: integer bounds: lower: 0 Link_shut_down: description: "`Link-shut_down` — how much of a committable link turns off this snapshot, capped as the status is" dims: [scenario, snapshot, link] - where: Link_committable AND Link_active + where: Link_committed domain: integer bounds: lower: 0 @@ -1460,9 +1459,7 @@ variables: `Link-maintenance_capacity` — the chosen build while in maintenance, zero otherwise: the product the `maintcap` rows linearize dims: [scenario, snapshot, link] - where: >- - Link_maintainable AND Link_p_nom_extendable - AND NOT (Link_committable AND Link_p_nom_mod > 0) AND Link_active + where: Link_maint_ext absence: zero bounds: lower: 0 @@ -1472,9 +1469,7 @@ variables: otherwise: the product the `maint-status` rows linearize, so a unit in maintenance may also be off dims: [scenario, snapshot, link] - where: >- - Link_maintainable AND Link_committable - AND NOT (Link_p_nom_extendable AND NOT (Link_p_nom_mod > 0)) AND Link_active + where: Link_maintainable AND Link_committed AND NOT (Link_p_nom_extendable AND NOT (Link_p_nom_mod > 0)) absence: zero bounds: lower: 0 @@ -1491,21 +1486,21 @@ variables: the rows below cap at one, or at the module count where the build is modular dims: [scenario, snapshot, process] - where: Process_committable AND Process_active + where: Process_committed domain: integer bounds: lower: 0 Process_start_up: description: "`Process-start_up` — how much of a committable process turns on this snapshot, capped as the status is" dims: [scenario, snapshot, process] - where: Process_committable AND Process_active + where: Process_committed domain: integer bounds: lower: 0 Process_shut_down: description: "`Process-shut_down` — how much of a committable process turns off this snapshot, capped as the status is" dims: [scenario, snapshot, process] - where: Process_committable AND Process_active + where: Process_committed domain: integer bounds: lower: 0 @@ -1530,9 +1525,7 @@ variables: `Process-maintenance_capacity` — the chosen build while in maintenance, zero otherwise: the product the `maintcap` rows linearize dims: [scenario, snapshot, process] - where: >- - Process_maintainable AND Process_p_nom_extendable - AND NOT (Process_committable AND Process_p_nom_mod > 0) AND Process_active + where: Process_maint_ext absence: zero bounds: lower: 0 @@ -1543,8 +1536,9 @@ variables: maintenance may also be off dims: [scenario, snapshot, process] where: >- - Process_maintainable AND Process_committable - AND NOT (Process_p_nom_extendable AND NOT (Process_p_nom_mod > 0)) AND Process_active + Process_maintainable + AND Process_committed + AND NOT (Process_p_nom_extendable AND NOT (Process_p_nom_mod > 0)) absence: zero bounds: lower: 0 @@ -1560,7 +1554,7 @@ variables: cost and held up by the cuts; absent, and zero in the balance, where the network is lossless dims: [scenario, snapshot, line] - where: transmission_losses AND Line_active + where: Line_lossy absence: zero bounds: lower: 0 @@ -1577,7 +1571,7 @@ variables: a line does; absent, and zero in the balance, where the network is lossless dims: [scenario, snapshot, transformer] - where: transmission_losses AND Transformer_active + where: Transformer_lossy absence: zero bounds: lower: 0 @@ -2360,6 +2354,127 @@ expressions: the voltage angle differences around a cycle: every branch flow times its cycle weight, and every transformer phase shift +masks: + Generator_com_ext: + description: >- + a committable generator with an extendable, non-modular build that + stands in the snapshot's period — PyPSA's `com-ext` rows, whose status + is relaxed against the chosen build + where: >- + Generator_committable + AND Generator_p_nom_extendable + AND NOT (Generator_p_nom_mod > 0) + AND Generator_active + Generator_maint_ext: + description: >- + a maintainable generator with an extendable build, unless it is + committable and modular, that stands in the snapshot's period — the + maintenance rows against the chosen build + where: >- + Generator_maintainable + AND Generator_p_nom_extendable + AND NOT (Generator_committable AND Generator_p_nom_mod > 0) + AND Generator_active + Generator_committed: + description: >- + a committable generator that stands in the snapshot's period — every + unit-commitment row's set + where: Generator_committable AND Generator_active + Generator_ramps_from_previous: + description: >- + a snapshot whose ramp reads a previous output — any snapshot but its + period's first, and the horizon's first where the generator comes in off + or carries an initial output + where: >- + position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND + (Generator_status_initial == 0 OR Generator_p_init)) + Link_com_ext: + description: >- + a committable link with an extendable, non-modular build that stands in + the snapshot's period — PyPSA's `com-ext` rows, whose status is relaxed + against the chosen build + where: >- + Link_committable + AND Link_p_nom_extendable + AND NOT (Link_p_nom_mod > 0) + AND Link_active + Link_maint_ext: + description: >- + a maintainable link with an extendable build, unless it is committable + and modular, that stands in the snapshot's period — the maintenance rows + against the chosen build + where: >- + Link_maintainable + AND Link_p_nom_extendable + AND NOT (Link_committable AND Link_p_nom_mod > 0) + AND Link_active + Link_committed: + description: >- + a committable link that stands in the snapshot's period — every + unit-commitment row's set + where: Link_committable AND Link_active + Link_ramps_from_previous: + description: >- + a snapshot whose ramp reads a previous output — any snapshot but its + period's first, and the horizon's first where the link comes in off or + carries an initial output + where: >- + position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND + (Link_status_initial == 0 OR Link_p_init)) + Process_com_ext: + description: >- + a committable process with an extendable, non-modular build that stands + in the snapshot's period — PyPSA's `com-ext` rows, whose status is + relaxed against the chosen build + where: >- + Process_committable + AND Process_p_nom_extendable + AND NOT (Process_p_nom_mod > 0) + AND Process_active + Process_maint_ext: + description: >- + a maintainable process with an extendable build, unless it is + committable and modular, that stands in the snapshot's period — the + maintenance rows against the chosen build + where: >- + Process_maintainable + AND Process_p_nom_extendable + AND NOT (Process_committable AND Process_p_nom_mod > 0) + AND Process_active + Process_committed: + description: >- + a committable process that stands in the snapshot's period — every + unit-commitment row's set + where: Process_committable AND Process_active + Process_ramps_from_previous: + description: >- + a snapshot whose ramp reads a previous output — any snapshot but its + period's first, and the horizon's first where the process comes in off + or carries an initial output + where: >- + position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND + (Process_status_initial == 0 OR Process_p_init)) + StorageUnit_fix: + description: >- + a storage unit with a fixed build that stands in the snapshot's period — + PyPSA's `fix` rows + where: NOT StorageUnit_p_nom_extendable AND StorageUnit_active + StorageUnit_ext: + description: >- + a storage unit with an extendable build that stands in the snapshot's + period — PyPSA's `ext` rows + where: StorageUnit_p_nom_extendable AND StorageUnit_active + Line_lossy: + description: >- + a line that stands in the snapshot's period, where the run models + transmission losses + where: transmission_losses AND Line_active + Transformer_lossy: + description: >- + a transformer that stands in the snapshot's period, where the run models + transmission losses + where: transmission_losses AND Transformer_active + constraints: Generator_fix_p_lower: description: "`Generator-fix-p-lower` — a fixed generator outputs at least its minimum" @@ -2479,54 +2594,54 @@ constraints: StorageUnit_fix_p_dispatch_lower: description: "`StorageUnit-fix-p_dispatch-lower` — dispatch is non-negative" dims: [scenario, snapshot, storage_unit] - where: not StorageUnit_p_nom_extendable AND StorageUnit_active + where: StorageUnit_fix 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" dims: [scenario, snapshot, storage_unit] - where: not StorageUnit_p_nom_extendable AND StorageUnit_active + where: StorageUnit_fix 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" dims: [scenario, snapshot, storage_unit] - where: not StorageUnit_p_nom_extendable AND StorageUnit_active + where: StorageUnit_fix 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 dims: [scenario, snapshot, storage_unit] - where: not StorageUnit_p_nom_extendable AND StorageUnit_active + where: StorageUnit_fix 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" dims: [scenario, snapshot, storage_unit] - where: not StorageUnit_p_nom_extendable AND StorageUnit_active + where: StorageUnit_fix 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" dims: [scenario, snapshot, storage_unit] - where: not StorageUnit_p_nom_extendable AND StorageUnit_active + where: StorageUnit_fix 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" dims: [scenario, snapshot, generator] - where: Generator_committable AND not Generator_p_nom_extendable AND Generator_active + where: Generator_committed AND NOT Generator_p_nom_extendable expression: Generator_p >= Generator_p_min_pu * Generator_p_nom * (Generator_status - Generator_maintenance_pu * Generator_maintenance_status) Generator_com_p_upper: description: "`Generator-com-p-upper` — a committed unit outputs at most what is available; off, at most nothing" dims: [scenario, snapshot, generator] - where: Generator_committable AND not Generator_p_nom_extendable AND Generator_active + where: Generator_committed AND NOT Generator_p_nom_extendable expression: Generator_p <= Generator_p_max_pu * Generator_p_nom * (Generator_status - Generator_maintenance_pu * Generator_maintenance_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" dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_active + where: Generator_committed 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" dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_active + where: Generator_committed expression: Generator_shut_down >= Generator_previous_status - Generator_status Generator_com_up_time: description: >- @@ -2534,7 +2649,7 @@ constraints: 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 dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_min_up_time > 0 AND position(snapshot) > 0 AND Generator_active + where: Generator_committed AND Generator_min_up_time > 0 AND position(snapshot) > 0 expression: sum_back(Generator_start_up, along=snapshot, window=Generator_min_up_time) <= Generator_status Generator_com_down_time: description: >- @@ -2542,19 +2657,19 @@ constraints: time is still off. The first snapshot's share of the window is the brought-in down time's, which the must-stay-down mask carries dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_min_down_time > 0 AND position(snapshot) > 0 AND Generator_active + where: Generator_committed AND Generator_min_down_time > 0 AND position(snapshot) > 0 expression: sum_back(Generator_shut_down, along=snapshot, window=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" dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_must_stay_up AND Generator_active + where: Generator_committed AND Generator_must_stay_up expression: Generator_status == 1 Generator_com_status_must_stay_down: description: >- `Generator-com-status-min_down_time_must_stay_up` — a unit still serving the down time it brought in stays off; PyPSA names the row `_must_stay_up` dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_must_stay_down AND Generator_active + where: Generator_committed AND Generator_must_stay_down expression: Generator_status == 0 Generator_p_ramp_limit_up_run_big_m: description: >- @@ -2563,10 +2678,9 @@ constraints: releases the row in the snapshot it turns on dims: [scenario, snapshot, generator] where: >- - Generator_committable AND Generator_p_nom_extendable AND NOT (Generator_p_nom_mod > 0) + Generator_com_ext AND (Generator_ramp_limit_up OR Generator_ramp_limit_start_up) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Generator_status_initial == 0 OR Generator_p_init))) - AND Generator_active + AND Generator_ramps_from_previous expression: >- Generator_p - Generator_previous_p <= Generator_ramp_up_rate * Generator_p_nom_ext @@ -2578,10 +2692,9 @@ constraints: the chosen build; the big M releases the row everywhere else dims: [scenario, snapshot, generator] where: >- - Generator_committable AND Generator_p_nom_extendable AND NOT (Generator_p_nom_mod > 0) + Generator_com_ext AND (Generator_ramp_limit_up OR Generator_ramp_limit_start_up) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Generator_status_initial == 0 OR Generator_p_init))) - AND Generator_active + AND Generator_ramps_from_previous expression: >- Generator_p - Generator_previous_p <= Generator_start_up_rate * Generator_p_nom_ext @@ -2593,10 +2706,9 @@ constraints: releases the row in the snapshot it turns off dims: [scenario, snapshot, generator] where: >- - Generator_committable AND Generator_p_nom_extendable AND NOT (Generator_p_nom_mod > 0) + Generator_com_ext AND (Generator_ramp_limit_down OR Generator_ramp_limit_shut_down) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Generator_status_initial == 0 OR Generator_p_init))) - AND Generator_active + AND Generator_ramps_from_previous expression: >- Generator_previous_p - Generator_p <= Generator_ramp_down_rate * Generator_p_nom_ext @@ -2608,10 +2720,9 @@ constraints: the chosen build; the big M releases the row everywhere else dims: [scenario, snapshot, generator] where: >- - Generator_committable AND Generator_p_nom_extendable AND NOT (Generator_p_nom_mod > 0) + Generator_com_ext AND (Generator_ramp_limit_down OR Generator_ramp_limit_shut_down) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Generator_status_initial == 0 OR Generator_p_init))) - AND Generator_active + AND Generator_ramps_from_previous expression: >- Generator_previous_p - Generator_p <= Generator_shut_down_rate * Generator_p_nom_ext @@ -2626,19 +2737,19 @@ constraints: `Generator-com-ext-p-upper-cap` — a committed extendable unit outputs at most what is available of the chosen build, whatever its status dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_p_nom_extendable AND NOT (Generator_p_nom_mod > 0) AND Generator_active + where: Generator_com_ext expression: Generator_p <= Generator_p_max_pu * (Generator_p_nom_ext - Generator_maintenance_pu * Generator_maintenance_capacity) 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" dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_p_nom_extendable AND NOT (Generator_p_nom_mod > 0) AND Generator_active + where: Generator_com_ext 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 dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_p_nom_extendable AND NOT (Generator_p_nom_mod > 0) AND Generator_active + where: Generator_com_ext expression: >- Generator_p >= Generator_p_min_pu * (Generator_p_nom_ext - Generator_maintenance_pu * Generator_maintenance_capacity) @@ -2649,23 +2760,21 @@ constraints: negative, output is also plainly non-negative, a row the big-M lower cannot assert while the unit is off dims: [scenario, snapshot, generator] - where: >- - Generator_committable AND Generator_p_nom_extendable - AND Generator_p_min_pu_nonneg AND NOT (Generator_p_nom_mod > 0) AND Generator_active + where: Generator_com_ext AND Generator_p_min_pu_nonneg expression: Generator_p >= 0 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 dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_p_nom_mod > 0 AND Generator_active + where: Generator_committed AND Generator_p_nom_mod > 0 expression: Generator_p >= Generator_p_min_pu * Generator_p_nom_mod * (Generator_status - Generator_maintenance_pu * Generator_maintenance_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 dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_p_nom_mod > 0 AND Generator_active + where: Generator_committed AND Generator_p_nom_mod > 0 expression: Generator_p <= Generator_p_max_pu * Generator_p_nom_mod * (Generator_status - Generator_maintenance_pu * Generator_maintenance_status) Generator_status_p_fixed_upper: description: >- @@ -2673,7 +2782,7 @@ constraints: 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 dims: [scenario, snapshot, generator] - where: Generator_committable AND NOT (Generator_p_nom_extendable AND Generator_p_nom_mod > 0) AND Generator_active + where: Generator_committed AND NOT (Generator_p_nom_extendable AND Generator_p_nom_mod > 0) expression: Generator_status <= Generator_modules_installed Generator_start_up_p_fixed_upper: description: >- @@ -2681,7 +2790,7 @@ constraints: 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 dims: [scenario, snapshot, generator] - where: Generator_committable AND NOT (Generator_p_nom_extendable AND Generator_p_nom_mod > 0) AND Generator_active + where: Generator_committed 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: description: >- @@ -2689,22 +2798,22 @@ constraints: 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 dims: [scenario, snapshot, generator] - where: Generator_committable AND NOT (Generator_p_nom_extendable AND Generator_p_nom_mod > 0) AND Generator_active + where: Generator_committed 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" dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_p_nom_extendable AND Generator_p_nom_mod > 0 AND Generator_active + where: Generator_committed 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" dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_p_nom_extendable AND Generator_p_nom_mod > 0 AND Generator_active + where: Generator_committed 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" dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_p_nom_extendable AND Generator_p_nom_mod > 0 AND Generator_active + where: Generator_committed AND Generator_p_nom_extendable AND Generator_p_nom_mod > 0 expression: Generator_shut_down <= Generator_n_mod Generator_maint_event_count: description: "`Generator-maint-event-count` — a maintainable generator holds its number of maintenance events over the horizon" @@ -2729,74 +2838,74 @@ constraints: `Generator-maintcap_upper` — the build taken off is at most the chosen build in maintenance, and at most the build less its floor out of it dims: [scenario, snapshot, generator] - where: Generator_maintainable AND Generator_p_nom_extendable AND NOT (Generator_committable AND Generator_p_nom_mod > 0) AND Generator_active + where: Generator_maint_ext expression: Generator_maintenance_capacity <= Generator_p_nom_ext - Generator_p_nom_min * (1 - Generator_maintenance) Generator_maintcap_upper_nommax: description: "`Generator-maintcap_upper_nommax` — out of maintenance, no build is taken off" dims: [scenario, snapshot, generator] - where: Generator_maintainable AND Generator_p_nom_extendable AND NOT (Generator_committable AND Generator_p_nom_mod > 0) AND Generator_active + where: Generator_maint_ext expression: Generator_maintenance_capacity <= Generator_p_nom_max * Generator_maintenance Generator_maintcap_lower_nommax: description: "`Generator-maintcap_lower_nommax` — in maintenance, the whole chosen build is taken off" dims: [scenario, snapshot, generator] - where: Generator_maintainable AND Generator_p_nom_extendable AND NOT (Generator_committable AND Generator_p_nom_mod > 0) AND Generator_active + where: Generator_maint_ext expression: Generator_maintenance_capacity >= Generator_p_nom_ext - Generator_p_nom_max * (1 - Generator_maintenance) Generator_maintcap_lower_nommin: description: "`Generator-maintcap_lower_nommin` — in maintenance, at least the floor of the build is taken off" dims: [scenario, snapshot, generator] - where: Generator_maintainable AND Generator_p_nom_extendable AND NOT (Generator_committable AND Generator_p_nom_mod > 0) AND Generator_active AND Generator_p_nom_min > 0 + where: Generator_maint_ext AND Generator_p_nom_min > 0 expression: Generator_maintenance_capacity >= Generator_p_nom_min * Generator_maintenance Generator_maint_status_le_status: description: "`Generator-maint-status-le-status` — the status in maintenance is at most the status" dims: [scenario, snapshot, generator] - where: Generator_maintainable AND Generator_committable AND NOT Generator_p_nom_extendable AND Generator_active + where: Generator_maintainable AND Generator_committed AND NOT Generator_p_nom_extendable expression: Generator_maintenance_status <= Generator_status Generator_maint_status_le_maint: description: "`Generator-maint-status-le-maint` — out of maintenance, the status in maintenance is zero" dims: [scenario, snapshot, generator] - where: Generator_maintainable AND Generator_committable AND NOT Generator_p_nom_extendable AND Generator_active + where: Generator_maintainable AND Generator_committed AND NOT Generator_p_nom_extendable expression: Generator_maintenance_status <= Generator_maintenance Generator_maint_status_lb: description: "`Generator-maint-status-lb` — on and in maintenance, the status in maintenance is one" dims: [scenario, snapshot, generator] - where: Generator_maintainable AND Generator_committable AND NOT Generator_p_nom_extendable AND Generator_active + where: Generator_maintainable AND Generator_committed AND NOT Generator_p_nom_extendable expression: Generator_maintenance_status >= Generator_status + Generator_maintenance - 1 Generator_maint_modstatus_le_status: description: "`Generator-maint-modstatus-le-status` — the modules on in maintenance are at most the modules on" dims: [scenario, snapshot, generator] - where: Generator_maintainable AND Generator_committable AND Generator_p_nom_mod > 0 AND Generator_active + where: Generator_maintainable AND Generator_committed AND Generator_p_nom_mod > 0 expression: Generator_maintenance_status <= Generator_status Generator_maint_modstatus_le_maint: description: >- `Generator-maint-modstatus-le-maint` — out of maintenance, no module is on in maintenance; in it, at most the modules the build cap holds dims: [scenario, snapshot, generator] - where: Generator_maintainable AND Generator_committable AND Generator_p_nom_mod > 0 AND Generator_active + where: Generator_maintainable AND Generator_committed AND Generator_p_nom_mod > 0 expression: Generator_maintenance_status <= Generator_p_nom_max / Generator_p_nom_mod * Generator_maintenance Generator_maint_modstatus_lb: description: "`Generator-maint-modstatus-lb` — in maintenance, every module on is on in maintenance" dims: [scenario, snapshot, generator] - where: Generator_maintainable AND Generator_committable AND Generator_p_nom_mod > 0 AND Generator_active + where: Generator_maintainable AND Generator_committed AND Generator_p_nom_mod > 0 expression: Generator_maintenance_status >= Generator_status - Generator_p_nom_max / Generator_p_nom_mod * (1 - Generator_maintenance) Link_com_p_lower: description: "`Link-com-p-lower` — a committed link flows at least its minimum; off, at least nothing" dims: [scenario, snapshot, link] - where: Link_committable AND not Link_p_nom_extendable AND Link_active + where: Link_committed AND NOT Link_p_nom_extendable expression: Link_p >= Link_p_min_pu * Link_p_nom * (Link_status - Link_maintenance_pu * Link_maintenance_status) Link_com_p_upper: description: "`Link-com-p-upper` — a committed link flows at most what is available; off, at most nothing" dims: [scenario, snapshot, link] - where: Link_committable AND not Link_p_nom_extendable AND Link_active + where: Link_committed AND NOT Link_p_nom_extendable expression: Link_p <= Link_p_max_pu * Link_p_nom * (Link_status - Link_maintenance_pu * Link_maintenance_status) Link_com_transition_start_up: description: "`Link-com-transition-start-up` — turning on is a start, counted against the state the link carried into the snapshot" dims: [scenario, snapshot, link] - where: Link_committable AND Link_active + where: Link_committed expression: Link_start_up >= Link_status - Link_previous_status Link_com_transition_shut_down: description: "`Link-com-transition-shut-down` — turning off is a stop, counted against the state the link carried into the snapshot" dims: [scenario, snapshot, link] - where: Link_committable AND Link_active + where: Link_committed expression: Link_shut_down >= Link_previous_status - Link_status Link_com_up_time: description: >- @@ -2804,7 +2913,7 @@ constraints: 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 dims: [scenario, snapshot, link] - where: Link_committable AND Link_min_up_time > 0 AND position(snapshot) > 0 AND Link_active + where: Link_committed AND Link_min_up_time > 0 AND position(snapshot) > 0 expression: sum_back(Link_start_up, along=snapshot, window=Link_min_up_time) <= Link_status Link_com_down_time: description: >- @@ -2812,19 +2921,19 @@ constraints: time is still off. The first snapshot's share of the window is the brought-in down time's, which the must-stay-down mask carries dims: [scenario, snapshot, link] - where: Link_committable AND Link_min_down_time > 0 AND position(snapshot) > 0 AND Link_active + where: Link_committed AND Link_min_down_time > 0 AND position(snapshot) > 0 expression: sum_back(Link_shut_down, along=snapshot, window=Link_min_down_time) <= 1 - Link_status Link_com_status_must_stay_up: description: "`Link-com-status-min_up_time_must_stay_up` — a link still serving the up time it brought in stays on" dims: [scenario, snapshot, link] - where: Link_committable AND Link_must_stay_up AND Link_active + where: Link_committed AND Link_must_stay_up expression: Link_status == 1 Link_com_status_must_stay_down: description: >- `Link-com-status-min_down_time_must_stay_up` — a link still serving the down time it brought in stays off; PyPSA names the row `_must_stay_up` dims: [scenario, snapshot, link] - where: Link_committable AND Link_must_stay_down AND Link_active + where: Link_committed AND Link_must_stay_down expression: Link_status == 0 Link_p_ramp_limit_up_run_big_m: description: >- @@ -2832,11 +2941,7 @@ constraints: raises flow no faster than its limit of the chosen build; the big M releases the row in the snapshot it turns on dims: [scenario, snapshot, link] - where: >- - Link_committable AND Link_p_nom_extendable AND NOT (Link_p_nom_mod > 0) - AND (Link_ramp_limit_up OR Link_ramp_limit_start_up) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Link_status_initial == 0 OR Link_p_init))) - AND Link_active + where: Link_com_ext AND (Link_ramp_limit_up OR Link_ramp_limit_start_up) AND Link_ramps_from_previous expression: >- Link_p - Link_previous_p <= Link_ramp_up_rate * Link_p_nom_ext @@ -2847,11 +2952,7 @@ constraints: committed extendable link ramps no further than its start-up ramp of the chosen build; the big M releases the row everywhere else dims: [scenario, snapshot, link] - where: >- - Link_committable AND Link_p_nom_extendable AND NOT (Link_p_nom_mod > 0) - AND (Link_ramp_limit_up OR Link_ramp_limit_start_up) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Link_status_initial == 0 OR Link_p_init))) - AND Link_active + where: Link_com_ext AND (Link_ramp_limit_up OR Link_ramp_limit_start_up) AND Link_ramps_from_previous expression: >- Link_p - Link_previous_p <= Link_start_up_rate * Link_p_nom_ext @@ -2862,11 +2963,7 @@ constraints: lowers flow no faster than its limit of the chosen build; the big M releases the row in the snapshot it turns off dims: [scenario, snapshot, link] - where: >- - Link_committable AND Link_p_nom_extendable AND NOT (Link_p_nom_mod > 0) - AND (Link_ramp_limit_down OR Link_ramp_limit_shut_down) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Link_status_initial == 0 OR Link_p_init))) - AND Link_active + where: Link_com_ext AND (Link_ramp_limit_down OR Link_ramp_limit_shut_down) AND Link_ramps_from_previous expression: >- Link_previous_p - Link_p <= Link_ramp_down_rate * Link_p_nom_ext @@ -2877,11 +2974,7 @@ constraints: a committed extendable link ramps no further than its shut-down ramp of the chosen build; the big M releases the row everywhere else dims: [scenario, snapshot, link] - where: >- - Link_committable AND Link_p_nom_extendable AND NOT (Link_p_nom_mod > 0) - AND (Link_ramp_limit_down OR Link_ramp_limit_shut_down) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Link_status_initial == 0 OR Link_p_init))) - AND Link_active + where: Link_com_ext AND (Link_ramp_limit_down OR Link_ramp_limit_shut_down) AND Link_ramps_from_previous expression: >- Link_previous_p - Link_p <= Link_shut_down_rate * Link_p_nom_ext @@ -2896,19 +2989,19 @@ constraints: `Link-com-ext-p-upper-cap` — a committed extendable link flows at most what is available of the chosen build, whatever its status dims: [scenario, snapshot, link] - where: Link_committable AND Link_p_nom_extendable AND NOT (Link_p_nom_mod > 0) AND Link_active + where: Link_com_ext expression: Link_p <= Link_p_max_pu * (Link_p_nom_ext - Link_maintenance_pu * Link_maintenance_capacity) Link_com_ext_p_upper_big_m: description: "`Link-com-ext-p-upper-bigM` — off, a link flows nothing; on, the big M is no bound" dims: [scenario, snapshot, link] - where: Link_committable AND Link_p_nom_extendable AND NOT (Link_p_nom_mod > 0) AND Link_active + where: Link_com_ext expression: Link_p <= Link_big_m * Link_status Link_com_ext_p_lower: description: >- `Link-com-ext-p-lower` — a committed extendable link flows at least its minimum of the chosen build; off, the big M releases the row dims: [scenario, snapshot, link] - where: Link_committable AND Link_p_nom_extendable AND NOT (Link_p_nom_mod > 0) AND Link_active + where: Link_com_ext expression: >- Link_p >= Link_p_min_pu * (Link_p_nom_ext - Link_maintenance_pu * Link_maintenance_capacity) @@ -2919,23 +3012,21 @@ constraints: negative, flow is also plainly non-negative, a row the big-M lower cannot assert while the link is off dims: [scenario, snapshot, link] - where: >- - Link_committable AND Link_p_nom_extendable - AND Link_p_min_pu_nonneg AND NOT (Link_p_nom_mod > 0) AND Link_active + where: Link_com_ext AND Link_p_min_pu_nonneg expression: Link_p >= 0 Link_com_mod_p_lower: description: >- `Link-com-mod-p-lower` — a committed modular link flows at least its minimum of one module, whether the build is fixed or a decision dims: [scenario, snapshot, link] - where: Link_committable AND Link_p_nom_mod > 0 AND Link_active + where: Link_committed AND Link_p_nom_mod > 0 expression: Link_p >= Link_p_min_pu * Link_p_nom_mod * (Link_status - Link_maintenance_pu * Link_maintenance_status) Link_com_mod_p_upper: description: >- `Link-com-mod-p-upper` — a committed modular link flows at most one module's share, whether the build is fixed or a decision dims: [scenario, snapshot, link] - where: Link_committable AND Link_p_nom_mod > 0 AND Link_active + where: Link_committed AND Link_p_nom_mod > 0 expression: Link_p <= Link_p_max_pu * Link_p_nom_mod * (Link_status - Link_maintenance_pu * Link_maintenance_status) Link_status_p_fixed_upper: description: >- @@ -2943,7 +3034,7 @@ constraints: 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 dims: [scenario, snapshot, link] - where: Link_committable AND NOT (Link_p_nom_extendable AND Link_p_nom_mod > 0) AND Link_active + where: Link_committed AND NOT (Link_p_nom_extendable AND Link_p_nom_mod > 0) expression: Link_status <= Link_modules_installed Link_start_up_p_fixed_upper: description: >- @@ -2951,7 +3042,7 @@ constraints: 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 dims: [scenario, snapshot, link] - where: Link_committable AND NOT (Link_p_nom_extendable AND Link_p_nom_mod > 0) AND Link_active + where: Link_committed AND NOT (Link_p_nom_extendable AND Link_p_nom_mod > 0) expression: Link_start_up <= Link_modules_installed Link_shut_down_p_fixed_upper: description: >- @@ -2959,22 +3050,22 @@ constraints: 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 dims: [scenario, snapshot, link] - where: Link_committable AND NOT (Link_p_nom_extendable AND Link_p_nom_mod > 0) AND Link_active + where: Link_committed AND NOT (Link_p_nom_extendable AND Link_p_nom_mod > 0) expression: Link_shut_down <= Link_modules_installed Link_status_p_nom_variable_upper: description: "`Link-status-p_nom-variable-upper` — a modular link is on only where a module is built" dims: [scenario, snapshot, link] - where: Link_committable AND Link_p_nom_extendable AND Link_p_nom_mod > 0 AND Link_active + where: Link_committed AND Link_p_nom_extendable AND Link_p_nom_mod > 0 expression: Link_status <= Link_n_mod Link_start_up_p_nom_variable_upper: description: "`Link-start_up-p_nom-variable-upper` — a modular link starts only where a module is built" dims: [scenario, snapshot, link] - where: Link_committable AND Link_p_nom_extendable AND Link_p_nom_mod > 0 AND Link_active + where: Link_committed AND Link_p_nom_extendable AND Link_p_nom_mod > 0 expression: Link_start_up <= Link_n_mod Link_shut_down_p_nom_variable_upper: description: "`Link-shut_down-p_nom-variable-upper` — a modular link stops only where a module is built" dims: [scenario, snapshot, link] - where: Link_committable AND Link_p_nom_extendable AND Link_p_nom_mod > 0 AND Link_active + where: Link_committed AND Link_p_nom_extendable AND Link_p_nom_mod > 0 expression: Link_shut_down <= Link_n_mod Link_maint_event_count: description: "`Link-maint-event-count` — a maintainable link holds its number of maintenance events over the horizon" @@ -2999,74 +3090,74 @@ constraints: `Link-maintcap_upper` — the build taken off is at most the chosen build in maintenance, and at most the build less its floor out of it dims: [scenario, snapshot, link] - where: Link_maintainable AND Link_p_nom_extendable AND NOT (Link_committable AND Link_p_nom_mod > 0) AND Link_active + where: Link_maint_ext expression: Link_maintenance_capacity <= Link_p_nom_ext - Link_p_nom_min * (1 - Link_maintenance) Link_maintcap_upper_nommax: description: "`Link-maintcap_upper_nommax` — out of maintenance, no build is taken off" dims: [scenario, snapshot, link] - where: Link_maintainable AND Link_p_nom_extendable AND NOT (Link_committable AND Link_p_nom_mod > 0) AND Link_active + where: Link_maint_ext expression: Link_maintenance_capacity <= Link_p_nom_max * Link_maintenance Link_maintcap_lower_nommax: description: "`Link-maintcap_lower_nommax` — in maintenance, the whole chosen build is taken off" dims: [scenario, snapshot, link] - where: Link_maintainable AND Link_p_nom_extendable AND NOT (Link_committable AND Link_p_nom_mod > 0) AND Link_active + where: Link_maint_ext expression: Link_maintenance_capacity >= Link_p_nom_ext - Link_p_nom_max * (1 - Link_maintenance) Link_maintcap_lower_nommin: description: "`Link-maintcap_lower_nommin` — in maintenance, at least the floor of the build is taken off" dims: [scenario, snapshot, link] - where: Link_maintainable AND Link_p_nom_extendable AND NOT (Link_committable AND Link_p_nom_mod > 0) AND Link_active AND Link_p_nom_min > 0 + where: Link_maint_ext AND Link_p_nom_min > 0 expression: Link_maintenance_capacity >= Link_p_nom_min * Link_maintenance Link_maint_status_le_status: description: "`Link-maint-status-le-status` — the status in maintenance is at most the status" dims: [scenario, snapshot, link] - where: Link_maintainable AND Link_committable AND NOT Link_p_nom_extendable AND Link_active + where: Link_maintainable AND Link_committed AND NOT Link_p_nom_extendable expression: Link_maintenance_status <= Link_status Link_maint_status_le_maint: description: "`Link-maint-status-le-maint` — out of maintenance, the status in maintenance is zero" dims: [scenario, snapshot, link] - where: Link_maintainable AND Link_committable AND NOT Link_p_nom_extendable AND Link_active + where: Link_maintainable AND Link_committed AND NOT Link_p_nom_extendable expression: Link_maintenance_status <= Link_maintenance Link_maint_status_lb: description: "`Link-maint-status-lb` — on and in maintenance, the status in maintenance is one" dims: [scenario, snapshot, link] - where: Link_maintainable AND Link_committable AND NOT Link_p_nom_extendable AND Link_active + where: Link_maintainable AND Link_committed AND NOT Link_p_nom_extendable expression: Link_maintenance_status >= Link_status + Link_maintenance - 1 Link_maint_modstatus_le_status: description: "`Link-maint-modstatus-le-status` — the modules on in maintenance are at most the modules on" dims: [scenario, snapshot, link] - where: Link_maintainable AND Link_committable AND Link_p_nom_mod > 0 AND Link_active + where: Link_maintainable AND Link_committed AND Link_p_nom_mod > 0 expression: Link_maintenance_status <= Link_status Link_maint_modstatus_le_maint: description: >- `Link-maint-modstatus-le-maint` — out of maintenance, no module is on in maintenance; in it, at most the modules the build cap holds dims: [scenario, snapshot, link] - where: Link_maintainable AND Link_committable AND Link_p_nom_mod > 0 AND Link_active + where: Link_maintainable AND Link_committed AND Link_p_nom_mod > 0 expression: Link_maintenance_status <= Link_p_nom_max / Link_p_nom_mod * Link_maintenance Link_maint_modstatus_lb: description: "`Link-maint-modstatus-lb` — in maintenance, every module on is on in maintenance" dims: [scenario, snapshot, link] - where: Link_maintainable AND Link_committable AND Link_p_nom_mod > 0 AND Link_active + where: Link_maintainable AND Link_committed AND Link_p_nom_mod > 0 expression: Link_maintenance_status >= Link_status - Link_p_nom_max / Link_p_nom_mod * (1 - Link_maintenance) Process_com_p_lower: description: "`Process-com-p-lower` — a committed process runs at least its minimum; off, at least nothing" dims: [scenario, snapshot, process] - where: Process_committable AND not Process_p_nom_extendable AND Process_active + where: Process_committed AND NOT Process_p_nom_extendable expression: Process_p >= Process_p_min_pu * Process_p_nom * (Process_status - Process_maintenance_pu * Process_maintenance_status) Process_com_p_upper: description: "`Process-com-p-upper` — a committed process runs at most what is available; off, at most nothing" dims: [scenario, snapshot, process] - where: Process_committable AND not Process_p_nom_extendable AND Process_active + where: Process_committed AND NOT Process_p_nom_extendable expression: Process_p <= Process_p_max_pu * Process_p_nom * (Process_status - Process_maintenance_pu * Process_maintenance_status) Process_com_transition_start_up: description: "`Process-com-transition-start-up` — turning on is a start, counted against the state the process carried into the snapshot" dims: [scenario, snapshot, process] - where: Process_committable AND Process_active + where: Process_committed expression: Process_start_up >= Process_status - Process_previous_status Process_com_transition_shut_down: description: "`Process-com-transition-shut-down` — turning off is a stop, counted against the state the process carried into the snapshot" dims: [scenario, snapshot, process] - where: Process_committable AND Process_active + where: Process_committed expression: Process_shut_down >= Process_previous_status - Process_status Process_com_up_time: description: >- @@ -3074,7 +3165,7 @@ constraints: 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 dims: [scenario, snapshot, process] - where: Process_committable AND Process_min_up_time > 0 AND position(snapshot) > 0 AND Process_active + where: Process_committed AND Process_min_up_time > 0 AND position(snapshot) > 0 expression: sum_back(Process_start_up, along=snapshot, window=Process_min_up_time) <= Process_status Process_com_down_time: description: >- @@ -3082,19 +3173,19 @@ constraints: time is still off. The first snapshot's share of the window is the brought-in down time's, which the must-stay-down mask carries dims: [scenario, snapshot, process] - where: Process_committable AND Process_min_down_time > 0 AND position(snapshot) > 0 AND Process_active + where: Process_committed AND Process_min_down_time > 0 AND position(snapshot) > 0 expression: sum_back(Process_shut_down, along=snapshot, window=Process_min_down_time) <= 1 - Process_status Process_com_status_must_stay_up: description: "`Process-com-status-min_up_time_must_stay_up` — a process still serving the up time it brought in stays on" dims: [scenario, snapshot, process] - where: Process_committable AND Process_must_stay_up AND Process_active + where: Process_committed AND Process_must_stay_up expression: Process_status == 1 Process_com_status_must_stay_down: description: >- `Process-com-status-min_down_time_must_stay_up` — a process still serving the down time it brought in stays off; PyPSA names the row `_must_stay_up` dims: [scenario, snapshot, process] - where: Process_committable AND Process_must_stay_down AND Process_active + where: Process_committed AND Process_must_stay_down expression: Process_status == 0 Process_p_ramp_limit_up_run_big_m: description: >- @@ -3102,11 +3193,7 @@ constraints: raises internal power no faster than its limit of the chosen build; the big M releases the row in the snapshot it turns on dims: [scenario, snapshot, process] - where: >- - Process_committable AND Process_p_nom_extendable AND NOT (Process_p_nom_mod > 0) - AND (Process_ramp_limit_up OR Process_ramp_limit_start_up) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Process_status_initial == 0 OR Process_p_init))) - AND Process_active + where: Process_com_ext AND (Process_ramp_limit_up OR Process_ramp_limit_start_up) AND Process_ramps_from_previous expression: >- Process_p - Process_previous_p <= Process_ramp_up_rate * Process_p_nom_ext @@ -3117,11 +3204,7 @@ constraints: committed extendable process ramps no further than its start-up ramp of the chosen build; the big M releases the row everywhere else dims: [scenario, snapshot, process] - where: >- - Process_committable AND Process_p_nom_extendable AND NOT (Process_p_nom_mod > 0) - AND (Process_ramp_limit_up OR Process_ramp_limit_start_up) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Process_status_initial == 0 OR Process_p_init))) - AND Process_active + where: Process_com_ext AND (Process_ramp_limit_up OR Process_ramp_limit_start_up) AND Process_ramps_from_previous expression: >- Process_p - Process_previous_p <= Process_start_up_rate * Process_p_nom_ext @@ -3133,10 +3216,9 @@ constraints: releases the row in the snapshot it turns off dims: [scenario, snapshot, process] where: >- - Process_committable AND Process_p_nom_extendable AND NOT (Process_p_nom_mod > 0) + Process_com_ext AND (Process_ramp_limit_down OR Process_ramp_limit_shut_down) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Process_status_initial == 0 OR Process_p_init))) - AND Process_active + AND Process_ramps_from_previous expression: >- Process_previous_p - Process_p <= Process_ramp_down_rate * Process_p_nom_ext @@ -3148,10 +3230,9 @@ constraints: the chosen build; the big M releases the row everywhere else dims: [scenario, snapshot, process] where: >- - Process_committable AND Process_p_nom_extendable AND NOT (Process_p_nom_mod > 0) + Process_com_ext AND (Process_ramp_limit_down OR Process_ramp_limit_shut_down) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Process_status_initial == 0 OR Process_p_init))) - AND Process_active + AND Process_ramps_from_previous expression: >- Process_previous_p - Process_p <= Process_shut_down_rate * Process_p_nom_ext @@ -3166,19 +3247,19 @@ constraints: `Process-com-ext-p-upper-cap` — a committed extendable process runs at most what is available of the chosen build, whatever its status dims: [scenario, snapshot, process] - where: Process_committable AND Process_p_nom_extendable AND NOT (Process_p_nom_mod > 0) AND Process_active + where: Process_com_ext expression: Process_p <= Process_p_max_pu * (Process_p_nom_ext - Process_maintenance_pu * Process_maintenance_capacity) Process_com_ext_p_upper_big_m: description: "`Process-com-ext-p-upper-bigM` — off, a process does not run; on, the big M is no bound" dims: [scenario, snapshot, process] - where: Process_committable AND Process_p_nom_extendable AND NOT (Process_p_nom_mod > 0) AND Process_active + where: Process_com_ext expression: Process_p <= Process_big_m * Process_status Process_com_ext_p_lower: description: >- `Process-com-ext-p-lower` — a committed extendable process runs at least its minimum of the chosen build; off, the big M releases the row dims: [scenario, snapshot, process] - where: Process_committable AND Process_p_nom_extendable AND NOT (Process_p_nom_mod > 0) AND Process_active + where: Process_com_ext expression: >- Process_p >= Process_p_min_pu * (Process_p_nom_ext - Process_maintenance_pu * Process_maintenance_capacity) @@ -3189,23 +3270,21 @@ constraints: negative, internal power is also plainly non-negative, a row the big-M lower cannot assert while the process is off dims: [scenario, snapshot, process] - where: >- - Process_committable AND Process_p_nom_extendable - AND Process_p_min_pu_nonneg AND NOT (Process_p_nom_mod > 0) AND Process_active + where: Process_com_ext AND Process_p_min_pu_nonneg expression: Process_p >= 0 Process_com_mod_p_lower: description: >- `Process-com-mod-p-lower` — a committed modular process runs at least its minimum of one module, whether the build is fixed or a decision dims: [scenario, snapshot, process] - where: Process_committable AND Process_p_nom_mod > 0 AND Process_active + where: Process_committed AND Process_p_nom_mod > 0 expression: Process_p >= Process_p_min_pu * Process_p_nom_mod * (Process_status - Process_maintenance_pu * Process_maintenance_status) Process_com_mod_p_upper: description: >- `Process-com-mod-p-upper` — a committed modular process runs at most one module's share, whether the build is fixed or a decision dims: [scenario, snapshot, process] - where: Process_committable AND Process_p_nom_mod > 0 AND Process_active + where: Process_committed AND Process_p_nom_mod > 0 expression: Process_p <= Process_p_max_pu * Process_p_nom_mod * (Process_status - Process_maintenance_pu * Process_maintenance_status) Process_status_p_fixed_upper: description: >- @@ -3213,7 +3292,7 @@ constraints: 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 dims: [scenario, snapshot, process] - where: Process_committable AND NOT (Process_p_nom_extendable AND Process_p_nom_mod > 0) AND Process_active + where: Process_committed AND NOT (Process_p_nom_extendable AND Process_p_nom_mod > 0) expression: Process_status <= Process_modules_installed Process_start_up_p_fixed_upper: description: >- @@ -3221,7 +3300,7 @@ constraints: 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 dims: [scenario, snapshot, process] - where: Process_committable AND NOT (Process_p_nom_extendable AND Process_p_nom_mod > 0) AND Process_active + where: Process_committed AND NOT (Process_p_nom_extendable AND Process_p_nom_mod > 0) expression: Process_start_up <= Process_modules_installed Process_shut_down_p_fixed_upper: description: >- @@ -3229,22 +3308,22 @@ constraints: 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 dims: [scenario, snapshot, process] - where: Process_committable AND NOT (Process_p_nom_extendable AND Process_p_nom_mod > 0) AND Process_active + where: Process_committed AND NOT (Process_p_nom_extendable AND Process_p_nom_mod > 0) expression: Process_shut_down <= Process_modules_installed Process_status_p_nom_variable_upper: description: "`Process-status-p_nom-variable-upper` — a modular process is on only where a module is built" dims: [scenario, snapshot, process] - where: Process_committable AND Process_p_nom_extendable AND Process_p_nom_mod > 0 AND Process_active + where: Process_committed AND Process_p_nom_extendable AND Process_p_nom_mod > 0 expression: Process_status <= Process_n_mod Process_start_up_p_nom_variable_upper: description: "`Process-start_up-p_nom-variable-upper` — a modular process starts only where a module is built" dims: [scenario, snapshot, process] - where: Process_committable AND Process_p_nom_extendable AND Process_p_nom_mod > 0 AND Process_active + where: Process_committed AND Process_p_nom_extendable AND Process_p_nom_mod > 0 expression: Process_start_up <= Process_n_mod Process_shut_down_p_nom_variable_upper: description: "`Process-shut_down-p_nom-variable-upper` — a modular process stops only where a module is built" dims: [scenario, snapshot, process] - where: Process_committable AND Process_p_nom_extendable AND Process_p_nom_mod > 0 AND Process_active + where: Process_committed AND Process_p_nom_extendable AND Process_p_nom_mod > 0 expression: Process_shut_down <= Process_n_mod Process_maint_event_count: description: "`Process-maint-event-count` — a maintainable process holds its number of maintenance events over the horizon" @@ -3269,54 +3348,54 @@ constraints: `Process-maintcap_upper` — the build taken off is at most the chosen build in maintenance, and at most the build less its floor out of it dims: [scenario, snapshot, process] - where: Process_maintainable AND Process_p_nom_extendable AND NOT (Process_committable AND Process_p_nom_mod > 0) AND Process_active + where: Process_maint_ext expression: Process_maintenance_capacity <= Process_p_nom_ext - Process_p_nom_min * (1 - Process_maintenance) Process_maintcap_upper_nommax: description: "`Process-maintcap_upper_nommax` — out of maintenance, no build is taken off" dims: [scenario, snapshot, process] - where: Process_maintainable AND Process_p_nom_extendable AND NOT (Process_committable AND Process_p_nom_mod > 0) AND Process_active + where: Process_maint_ext expression: Process_maintenance_capacity <= Process_p_nom_max * Process_maintenance Process_maintcap_lower_nommax: description: "`Process-maintcap_lower_nommax` — in maintenance, the whole chosen build is taken off" dims: [scenario, snapshot, process] - where: Process_maintainable AND Process_p_nom_extendable AND NOT (Process_committable AND Process_p_nom_mod > 0) AND Process_active + where: Process_maint_ext expression: Process_maintenance_capacity >= Process_p_nom_ext - Process_p_nom_max * (1 - Process_maintenance) Process_maintcap_lower_nommin: description: "`Process-maintcap_lower_nommin` — in maintenance, at least the floor of the build is taken off" dims: [scenario, snapshot, process] - where: Process_maintainable AND Process_p_nom_extendable AND NOT (Process_committable AND Process_p_nom_mod > 0) AND Process_active AND Process_p_nom_min > 0 + where: Process_maint_ext AND Process_p_nom_min > 0 expression: Process_maintenance_capacity >= Process_p_nom_min * Process_maintenance Process_maint_status_le_status: description: "`Process-maint-status-le-status` — the status in maintenance is at most the status" dims: [scenario, snapshot, process] - where: Process_maintainable AND Process_committable AND NOT Process_p_nom_extendable AND Process_active + where: Process_maintainable AND Process_committed AND NOT Process_p_nom_extendable expression: Process_maintenance_status <= Process_status Process_maint_status_le_maint: description: "`Process-maint-status-le-maint` — out of maintenance, the status in maintenance is zero" dims: [scenario, snapshot, process] - where: Process_maintainable AND Process_committable AND NOT Process_p_nom_extendable AND Process_active + where: Process_maintainable AND Process_committed AND NOT Process_p_nom_extendable expression: Process_maintenance_status <= Process_maintenance Process_maint_status_lb: description: "`Process-maint-status-lb` — on and in maintenance, the status in maintenance is one" dims: [scenario, snapshot, process] - where: Process_maintainable AND Process_committable AND NOT Process_p_nom_extendable AND Process_active + where: Process_maintainable AND Process_committed AND NOT Process_p_nom_extendable expression: Process_maintenance_status >= Process_status + Process_maintenance - 1 Process_maint_modstatus_le_status: description: "`Process-maint-modstatus-le-status` — the modules on in maintenance are at most the modules on" dims: [scenario, snapshot, process] - where: Process_maintainable AND Process_committable AND Process_p_nom_mod > 0 AND Process_active + where: Process_maintainable AND Process_committed AND Process_p_nom_mod > 0 expression: Process_maintenance_status <= Process_status Process_maint_modstatus_le_maint: description: >- `Process-maint-modstatus-le-maint` — out of maintenance, no module is on in maintenance; in it, at most the modules the build cap holds dims: [scenario, snapshot, process] - where: Process_maintainable AND Process_committable AND Process_p_nom_mod > 0 AND Process_active + where: Process_maintainable AND Process_committed AND Process_p_nom_mod > 0 expression: Process_maintenance_status <= Process_p_nom_max / Process_p_nom_mod * Process_maintenance Process_maint_modstatus_lb: description: "`Process-maint-modstatus-lb` — in maintenance, every module on is on in maintenance" dims: [scenario, snapshot, process] - where: Process_maintainable AND Process_committable AND Process_p_nom_mod > 0 AND Process_active + where: Process_maintainable AND Process_committed AND Process_p_nom_mod > 0 expression: Process_maintenance_status >= Process_status - Process_p_nom_max / Process_p_nom_mod * (1 - Process_maintenance) 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" @@ -3361,7 +3440,7 @@ constraints: Line_loss_upper: description: "`Line-loss_upper` — a line dissipates at most the loss at its rating" dims: [scenario, snapshot, line] - where: transmission_losses AND Line_active + where: Line_lossy expression: Line_loss <= Line_loss_max Line_loss_tangents_forward: description: >- @@ -3370,14 +3449,14 @@ constraints: `k`, or one row stacked over its `secant` axis, and this block states them all over the segment dimension dims: [scenario, snapshot, line, segment] - where: transmission_losses AND Line_active + where: Line_lossy expression: Line_loss + Line_loss_slope * Line_s >= Line_loss_offset Line_loss_tangents_reverse: description: >- `Line-loss_tangents-{k}--1`, `Line-loss_secants-neg` — the same fan mirrored, the loss depending on the flow's magnitude dims: [scenario, snapshot, line, segment] - where: transmission_losses AND Line_active + where: Line_lossy expression: Line_loss - Line_loss_slope * Line_s >= Line_loss_offset Transformer_fix_s_lower: description: "`Transformer-fix-s-lower` — a fixed transformer carries at least the negative of its rating, the loss counted against it" @@ -3422,7 +3501,7 @@ constraints: Transformer_loss_upper: description: "`Transformer-loss_upper` — a transformer dissipates at most the loss at its rating" dims: [scenario, snapshot, transformer] - where: transmission_losses AND Transformer_active + where: Transformer_lossy expression: Transformer_loss <= Transformer_loss_max Transformer_loss_tangents_forward: description: >- @@ -3430,14 +3509,14 @@ constraints: loss sits above every cut to its curve for flow one way, as a line's does, over the segment dimension dims: [scenario, snapshot, transformer, segment] - where: transmission_losses AND Transformer_active + where: Transformer_lossy expression: Transformer_loss + Transformer_loss_slope * Transformer_s >= Transformer_loss_offset Transformer_loss_tangents_reverse: description: >- `Transformer-loss_tangents-{k}--1`, `Transformer-loss_secants-neg` — the same fan mirrored, the loss depending on the flow's magnitude dims: [scenario, snapshot, transformer, segment] - where: transmission_losses AND Transformer_active + where: Transformer_lossy expression: Transformer_loss - Transformer_loss_slope * Transformer_s >= Transformer_loss_offset Line_fix_s_lower_security: description: >- @@ -3547,7 +3626,7 @@ constraints: where: >- (Generator_ramp_limit_up OR Generator_ramp_limit_start_up) AND NOT (Generator_committable AND Generator_p_nom_extendable AND NOT (Generator_p_nom_mod > 0)) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Generator_status_initial == 0 OR Generator_p_init))) + AND Generator_ramps_from_previous AND Generator_active expression: Generator_p - Generator_previous_p <= Generator_ramp_up_allowance Generator_p_ramp_limit_down: @@ -3562,7 +3641,7 @@ constraints: where: >- (Generator_ramp_limit_down OR Generator_ramp_limit_shut_down) AND NOT (Generator_committable AND Generator_p_nom_extendable AND NOT (Generator_p_nom_mod > 0)) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Generator_status_initial == 0 OR Generator_p_init))) + AND Generator_ramps_from_previous AND Generator_active expression: Generator_previous_p - Generator_p <= Generator_ramp_down_allowance Link_p_ramp_limit_up: @@ -3577,7 +3656,7 @@ constraints: where: >- (Link_ramp_limit_up OR Link_ramp_limit_start_up) AND NOT (Link_committable AND Link_p_nom_extendable AND NOT (Link_p_nom_mod > 0)) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Link_status_initial == 0 OR Link_p_init))) + AND Link_ramps_from_previous AND Link_active expression: Link_p - Link_previous_p <= Link_ramp_up_allowance Link_p_ramp_limit_down: @@ -3592,7 +3671,7 @@ constraints: where: >- (Link_ramp_limit_down OR Link_ramp_limit_shut_down) AND NOT (Link_committable AND Link_p_nom_extendable AND NOT (Link_p_nom_mod > 0)) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Link_status_initial == 0 OR Link_p_init))) + AND Link_ramps_from_previous AND Link_active expression: Link_previous_p - Link_p <= Link_ramp_down_allowance Process_p_ramp_limit_up: @@ -3607,7 +3686,7 @@ constraints: where: >- (Process_ramp_limit_up OR Process_ramp_limit_start_up) AND NOT (Process_committable AND Process_p_nom_extendable AND NOT (Process_p_nom_mod > 0)) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Process_status_initial == 0 OR Process_p_init))) + AND Process_ramps_from_previous AND Process_active expression: Process_p - Process_previous_p <= Process_ramp_up_allowance Process_p_ramp_limit_down: @@ -3622,40 +3701,40 @@ constraints: where: >- (Process_ramp_limit_down OR Process_ramp_limit_shut_down) AND NOT (Process_committable AND Process_p_nom_extendable AND NOT (Process_p_nom_mod > 0)) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Process_status_initial == 0 OR Process_p_init))) + AND Process_ramps_from_previous AND Process_active expression: Process_previous_p - Process_p <= Process_ramp_down_allowance StorageUnit_ext_p_dispatch_lower: description: "`StorageUnit-ext-p_dispatch-lower` — dispatch is non-negative" dims: [scenario, snapshot, storage_unit] - where: StorageUnit_p_nom_extendable AND StorageUnit_active + where: StorageUnit_ext 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" dims: [scenario, snapshot, storage_unit] - where: StorageUnit_p_nom_extendable AND StorageUnit_active + where: StorageUnit_ext 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" dims: [scenario, snapshot, storage_unit] - where: StorageUnit_p_nom_extendable AND StorageUnit_active + where: StorageUnit_ext 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 dims: [scenario, snapshot, storage_unit] - where: StorageUnit_p_nom_extendable AND StorageUnit_active + where: StorageUnit_ext 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" dims: [scenario, snapshot, storage_unit] - where: StorageUnit_p_nom_extendable AND StorageUnit_active + where: StorageUnit_ext 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" dims: [scenario, snapshot, storage_unit] - where: StorageUnit_p_nom_extendable AND StorageUnit_active + where: StorageUnit_ext 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 in every scenario" diff --git a/examples/pypsa/generator.yaml b/examples/pypsa/generator.yaml index 36379bcb..e3c95774 100644 --- a/examples/pypsa/generator.yaml +++ b/examples/pypsa/generator.yaml @@ -210,6 +210,13 @@ expressions: expression: sum(Generator_sign * Generator_p, by=Generator_bus, over=generator, into=bus) adds_to: Bus_injection +masks: + Generator_committed: + description: >- + a committable generator that stands in the snapshot's period — every + unit-commitment row's set + where: Generator_committable AND Generator_active + constraints: Generator_fix_p_lower: description: "`Generator-fix-p-lower` — a fixed generator outputs at least its minimum" diff --git a/examples/pypsa/generator_commitment.yaml b/examples/pypsa/generator_commitment.yaml index 8551031e..3320aa3e 100644 --- a/examples/pypsa/generator_commitment.yaml +++ b/examples/pypsa/generator_commitment.yaml @@ -71,21 +71,21 @@ variables: the rows below cap at one, or at the module count where the build is modular dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_active + where: Generator_committed 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" dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_active + where: Generator_committed 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" dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_active + where: Generator_committed domain: integer bounds: lower: 0 @@ -111,6 +111,8 @@ given: Generator_p_nom_ext: { dims: [generator] } expressions: scenario_opex: { dims: [scenario] } + masks: + Generator_committed: { dims: [snapshot, generator] } expressions: Generator_previous_status: @@ -128,26 +130,38 @@ expressions: + sum(sum(Generator_shut_down * Generator_shut_down_cost, over=generator), over=snapshot) adds_to: scenario_opex +masks: + Generator_com_ext: + description: >- + a committable generator with an extendable, non-modular build that + stands in the snapshot's period — PyPSA's `com-ext` rows, whose status + is relaxed against the chosen build + where: >- + Generator_committable + AND Generator_p_nom_extendable + AND NOT (Generator_p_nom_mod > 0) + AND Generator_active + constraints: Generator_com_p_lower: description: "`Generator-com-p-lower` — a committed unit outputs at least its minimum; off, at least nothing" dims: [scenario, snapshot, generator] - where: Generator_committable AND not Generator_p_nom_extendable AND Generator_active + where: Generator_committed AND NOT Generator_p_nom_extendable expression: Generator_p >= Generator_p_min_pu * Generator_p_nom * (Generator_status - Generator_maintenance_pu * Generator_maintenance_status) Generator_com_p_upper: description: "`Generator-com-p-upper` — a committed unit outputs at most what is available; off, at most nothing" dims: [scenario, snapshot, generator] - where: Generator_committable AND not Generator_p_nom_extendable AND Generator_active + where: Generator_committed AND NOT Generator_p_nom_extendable expression: Generator_p <= Generator_p_max_pu * Generator_p_nom * (Generator_status - Generator_maintenance_pu * Generator_maintenance_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" dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_active + where: Generator_committed 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" dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_active + where: Generator_committed expression: Generator_shut_down >= Generator_previous_status - Generator_status Generator_com_up_time: description: >- @@ -155,7 +169,7 @@ constraints: 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 dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_min_up_time > 0 AND position(snapshot) > 0 AND Generator_active + where: Generator_committed AND Generator_min_up_time > 0 AND position(snapshot) > 0 expression: sum_back(Generator_start_up, along=snapshot, window=Generator_min_up_time) <= Generator_status Generator_com_down_time: description: >- @@ -163,38 +177,38 @@ constraints: time is still off. The first snapshot's share of the window is the brought-in down time's, which the must-stay-down mask carries dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_min_down_time > 0 AND position(snapshot) > 0 AND Generator_active + where: Generator_committed AND Generator_min_down_time > 0 AND position(snapshot) > 0 expression: sum_back(Generator_shut_down, along=snapshot, window=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" dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_must_stay_up AND Generator_active + where: Generator_committed AND Generator_must_stay_up expression: Generator_status == 1 Generator_com_status_must_stay_down: description: >- `Generator-com-status-min_down_time_must_stay_up` — a unit still serving the down time it brought in stays off; PyPSA names the row `_must_stay_up` dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_must_stay_down AND Generator_active + where: Generator_committed AND Generator_must_stay_down expression: Generator_status == 0 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 dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_p_nom_extendable AND NOT (Generator_p_nom_mod > 0) AND Generator_active + where: Generator_com_ext expression: Generator_p <= Generator_p_max_pu * (Generator_p_nom_ext - Generator_maintenance_pu * Generator_maintenance_capacity) 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" dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_p_nom_extendable AND NOT (Generator_p_nom_mod > 0) AND Generator_active + where: Generator_com_ext 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 dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_p_nom_extendable AND NOT (Generator_p_nom_mod > 0) AND Generator_active + where: Generator_com_ext expression: >- Generator_p >= Generator_p_min_pu * (Generator_p_nom_ext - Generator_maintenance_pu * Generator_maintenance_capacity) @@ -205,23 +219,21 @@ constraints: negative, output is also plainly non-negative, a row the big-M lower cannot assert while the unit is off dims: [scenario, snapshot, generator] - where: >- - Generator_committable AND Generator_p_nom_extendable - AND Generator_p_min_pu_nonneg AND NOT (Generator_p_nom_mod > 0) AND Generator_active + where: Generator_com_ext AND Generator_p_min_pu_nonneg expression: Generator_p >= 0 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 dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_p_nom_mod > 0 AND Generator_active + where: Generator_committed AND Generator_p_nom_mod > 0 expression: Generator_p >= Generator_p_min_pu * Generator_p_nom_mod * (Generator_status - Generator_maintenance_pu * Generator_maintenance_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 dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_p_nom_mod > 0 AND Generator_active + where: Generator_committed AND Generator_p_nom_mod > 0 expression: Generator_p <= Generator_p_max_pu * Generator_p_nom_mod * (Generator_status - Generator_maintenance_pu * Generator_maintenance_status) Generator_status_p_fixed_upper: description: >- @@ -229,7 +241,7 @@ constraints: 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 dims: [scenario, snapshot, generator] - where: Generator_committable AND NOT (Generator_p_nom_extendable AND Generator_p_nom_mod > 0) AND Generator_active + where: Generator_committed AND NOT (Generator_p_nom_extendable AND Generator_p_nom_mod > 0) expression: Generator_status <= Generator_modules_installed Generator_start_up_p_fixed_upper: description: >- @@ -237,7 +249,7 @@ constraints: 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 dims: [scenario, snapshot, generator] - where: Generator_committable AND NOT (Generator_p_nom_extendable AND Generator_p_nom_mod > 0) AND Generator_active + where: Generator_committed 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: description: >- @@ -245,20 +257,20 @@ constraints: 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 dims: [scenario, snapshot, generator] - where: Generator_committable AND NOT (Generator_p_nom_extendable AND Generator_p_nom_mod > 0) AND Generator_active + where: Generator_committed 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" dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_p_nom_extendable AND Generator_p_nom_mod > 0 AND Generator_active + where: Generator_committed 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" dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_p_nom_extendable AND Generator_p_nom_mod > 0 AND Generator_active + where: Generator_committed 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" dims: [scenario, snapshot, generator] - where: Generator_committable AND Generator_p_nom_extendable AND Generator_p_nom_mod > 0 AND Generator_active + where: Generator_committed AND Generator_p_nom_extendable AND Generator_p_nom_mod > 0 expression: Generator_shut_down <= Generator_n_mod diff --git a/examples/pypsa/generator_maintenance.yaml b/examples/pypsa/generator_maintenance.yaml index 1f8ca404..33957456 100644 --- a/examples/pypsa/generator_maintenance.yaml +++ b/examples/pypsa/generator_maintenance.yaml @@ -74,9 +74,7 @@ variables: `Generator-maintenance_capacity` — the chosen build while in maintenance, zero otherwise: the product the `maintcap` rows linearize dims: [scenario, snapshot, generator] - where: >- - Generator_maintainable AND Generator_p_nom_extendable - AND NOT (Generator_committable AND Generator_p_nom_mod > 0) AND Generator_active + where: Generator_maint_ext absence: zero bounds: lower: 0 @@ -87,8 +85,9 @@ variables: maintenance may also be off dims: [scenario, snapshot, generator] where: >- - Generator_maintainable AND Generator_committable - AND NOT (Generator_p_nom_extendable AND NOT (Generator_p_nom_mod > 0)) AND Generator_active + Generator_maintainable + AND Generator_committed + AND NOT (Generator_p_nom_extendable AND NOT (Generator_p_nom_mod > 0)) absence: zero bounds: lower: 0 @@ -105,6 +104,20 @@ given: variables: Generator_status: { dims: [scenario, snapshot, generator], domain: integer } Generator_p_nom_ext: { dims: [generator] } + masks: + Generator_committed: { dims: [snapshot, generator] } + +masks: + Generator_maint_ext: + description: >- + a maintainable generator with an extendable build, unless it is + committable and modular, that stands in the snapshot's period — the + maintenance rows against the chosen build + where: >- + Generator_maintainable + AND Generator_p_nom_extendable + AND NOT (Generator_committable AND Generator_p_nom_mod > 0) + AND Generator_active constraints: Generator_maint_event_count: @@ -130,54 +143,54 @@ constraints: `Generator-maintcap_upper` — the build taken off is at most the chosen build in maintenance, and at most the build less its floor out of it dims: [scenario, snapshot, generator] - where: Generator_maintainable AND Generator_p_nom_extendable AND NOT (Generator_committable AND Generator_p_nom_mod > 0) AND Generator_active + where: Generator_maint_ext expression: Generator_maintenance_capacity <= Generator_p_nom_ext - Generator_p_nom_min * (1 - Generator_maintenance) Generator_maintcap_upper_nommax: description: "`Generator-maintcap_upper_nommax` — out of maintenance, no build is taken off" dims: [scenario, snapshot, generator] - where: Generator_maintainable AND Generator_p_nom_extendable AND NOT (Generator_committable AND Generator_p_nom_mod > 0) AND Generator_active + where: Generator_maint_ext expression: Generator_maintenance_capacity <= Generator_p_nom_max * Generator_maintenance Generator_maintcap_lower_nommax: description: "`Generator-maintcap_lower_nommax` — in maintenance, the whole chosen build is taken off" dims: [scenario, snapshot, generator] - where: Generator_maintainable AND Generator_p_nom_extendable AND NOT (Generator_committable AND Generator_p_nom_mod > 0) AND Generator_active + where: Generator_maint_ext expression: Generator_maintenance_capacity >= Generator_p_nom_ext - Generator_p_nom_max * (1 - Generator_maintenance) Generator_maintcap_lower_nommin: description: "`Generator-maintcap_lower_nommin` — in maintenance, at least the floor of the build is taken off" dims: [scenario, snapshot, generator] - where: Generator_maintainable AND Generator_p_nom_extendable AND NOT (Generator_committable AND Generator_p_nom_mod > 0) AND Generator_active AND Generator_p_nom_min > 0 + where: Generator_maint_ext AND Generator_p_nom_min > 0 expression: Generator_maintenance_capacity >= Generator_p_nom_min * Generator_maintenance Generator_maint_status_le_status: description: "`Generator-maint-status-le-status` — the status in maintenance is at most the status" dims: [scenario, snapshot, generator] - where: Generator_maintainable AND Generator_committable AND NOT Generator_p_nom_extendable AND Generator_active + where: Generator_maintainable AND Generator_committed AND NOT Generator_p_nom_extendable expression: Generator_maintenance_status <= Generator_status Generator_maint_status_le_maint: description: "`Generator-maint-status-le-maint` — out of maintenance, the status in maintenance is zero" dims: [scenario, snapshot, generator] - where: Generator_maintainable AND Generator_committable AND NOT Generator_p_nom_extendable AND Generator_active + where: Generator_maintainable AND Generator_committed AND NOT Generator_p_nom_extendable expression: Generator_maintenance_status <= Generator_maintenance Generator_maint_status_lb: description: "`Generator-maint-status-lb` — on and in maintenance, the status in maintenance is one" dims: [scenario, snapshot, generator] - where: Generator_maintainable AND Generator_committable AND NOT Generator_p_nom_extendable AND Generator_active + where: Generator_maintainable AND Generator_committed AND NOT Generator_p_nom_extendable expression: Generator_maintenance_status >= Generator_status + Generator_maintenance - 1 Generator_maint_modstatus_le_status: description: "`Generator-maint-modstatus-le-status` — the modules on in maintenance are at most the modules on" dims: [scenario, snapshot, generator] - where: Generator_maintainable AND Generator_committable AND Generator_p_nom_mod > 0 AND Generator_active + where: Generator_maintainable AND Generator_committed AND Generator_p_nom_mod > 0 expression: Generator_maintenance_status <= Generator_status Generator_maint_modstatus_le_maint: description: >- `Generator-maint-modstatus-le-maint` — out of maintenance, no module is on in maintenance; in it, at most the modules the build cap holds dims: [scenario, snapshot, generator] - where: Generator_maintainable AND Generator_committable AND Generator_p_nom_mod > 0 AND Generator_active + where: Generator_maintainable AND Generator_committed AND Generator_p_nom_mod > 0 expression: Generator_maintenance_status <= Generator_p_nom_max / Generator_p_nom_mod * Generator_maintenance Generator_maint_modstatus_lb: description: "`Generator-maint-modstatus-lb` — in maintenance, every module on is on in maintenance" dims: [scenario, snapshot, generator] - where: Generator_maintainable AND Generator_committable AND Generator_p_nom_mod > 0 AND Generator_active + where: Generator_maintainable AND Generator_committed AND Generator_p_nom_mod > 0 expression: Generator_maintenance_status >= Generator_status - Generator_p_nom_max / Generator_p_nom_mod * (1 - Generator_maintenance) assumptions: diff --git a/examples/pypsa/generator_ramping.yaml b/examples/pypsa/generator_ramping.yaml index c128b6b6..3f701176 100644 --- a/examples/pypsa/generator_ramping.yaml +++ b/examples/pypsa/generator_ramping.yaml @@ -58,6 +58,8 @@ given: Generator_previous_status: { dims: [scenario, snapshot, generator] } Generator_p_nom_effective: { dims: [scenario, generator] } Generator_p_nom_committed: { dims: [scenario, generator] } + masks: + Generator_com_ext: { dims: [snapshot, generator] } expressions: Generator_previous_p: @@ -130,6 +132,16 @@ expressions: * (Generator_previous_status - Generator_status) otherwise: Generator_ramp_down_rate * Generator_p_nom_effective +masks: + Generator_ramps_from_previous: + description: >- + a snapshot whose ramp reads a previous output — any snapshot but its + period's first, and the horizon's first where the generator comes in off + or carries an initial output + where: >- + position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND + (Generator_status_initial == 0 OR Generator_p_init)) + constraints: Generator_p_ramp_limit_up_run_big_m: description: >- @@ -138,10 +150,9 @@ constraints: releases the row in the snapshot it turns on dims: [scenario, snapshot, generator] where: >- - Generator_committable AND Generator_p_nom_extendable AND NOT (Generator_p_nom_mod > 0) + Generator_com_ext AND (Generator_ramp_limit_up OR Generator_ramp_limit_start_up) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Generator_status_initial == 0 OR Generator_p_init))) - AND Generator_active + AND Generator_ramps_from_previous expression: >- Generator_p - Generator_previous_p <= Generator_ramp_up_rate * Generator_p_nom_ext @@ -153,10 +164,9 @@ constraints: the chosen build; the big M releases the row everywhere else dims: [scenario, snapshot, generator] where: >- - Generator_committable AND Generator_p_nom_extendable AND NOT (Generator_p_nom_mod > 0) + Generator_com_ext AND (Generator_ramp_limit_up OR Generator_ramp_limit_start_up) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Generator_status_initial == 0 OR Generator_p_init))) - AND Generator_active + AND Generator_ramps_from_previous expression: >- Generator_p - Generator_previous_p <= Generator_start_up_rate * Generator_p_nom_ext @@ -168,10 +178,9 @@ constraints: releases the row in the snapshot it turns off dims: [scenario, snapshot, generator] where: >- - Generator_committable AND Generator_p_nom_extendable AND NOT (Generator_p_nom_mod > 0) + Generator_com_ext AND (Generator_ramp_limit_down OR Generator_ramp_limit_shut_down) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Generator_status_initial == 0 OR Generator_p_init))) - AND Generator_active + AND Generator_ramps_from_previous expression: >- Generator_previous_p - Generator_p <= Generator_ramp_down_rate * Generator_p_nom_ext @@ -183,10 +192,9 @@ constraints: the chosen build; the big M releases the row everywhere else dims: [scenario, snapshot, generator] where: >- - Generator_committable AND Generator_p_nom_extendable AND NOT (Generator_p_nom_mod > 0) + Generator_com_ext AND (Generator_ramp_limit_down OR Generator_ramp_limit_shut_down) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Generator_status_initial == 0 OR Generator_p_init))) - AND Generator_active + AND Generator_ramps_from_previous expression: >- Generator_previous_p - Generator_p <= Generator_shut_down_rate * Generator_p_nom_ext @@ -203,7 +211,7 @@ constraints: where: >- (Generator_ramp_limit_up OR Generator_ramp_limit_start_up) AND NOT (Generator_committable AND Generator_p_nom_extendable AND NOT (Generator_p_nom_mod > 0)) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Generator_status_initial == 0 OR Generator_p_init))) + AND Generator_ramps_from_previous AND Generator_active expression: Generator_p - Generator_previous_p <= Generator_ramp_up_allowance Generator_p_ramp_limit_down: @@ -218,7 +226,7 @@ constraints: where: >- (Generator_ramp_limit_down OR Generator_ramp_limit_shut_down) AND NOT (Generator_committable AND Generator_p_nom_extendable AND NOT (Generator_p_nom_mod > 0)) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Generator_status_initial == 0 OR Generator_p_init))) + AND Generator_ramps_from_previous AND Generator_active expression: Generator_previous_p - Generator_p <= Generator_ramp_down_allowance diff --git a/examples/pypsa/line.yaml b/examples/pypsa/line.yaml index dd325ecd..2c2cb331 100644 --- a/examples/pypsa/line.yaml +++ b/examples/pypsa/line.yaml @@ -138,7 +138,7 @@ variables: cost and held up by the cuts; absent, and zero in the balance, where the network is lossless dims: [scenario, snapshot, line] - where: transmission_losses AND Line_active + where: Line_lossy absence: zero bounds: lower: 0 @@ -199,6 +199,13 @@ expressions: expression: sum(Line_s * Line_cycle_weight, over=line) adds_to: Cycle_angle_sum +masks: + Line_lossy: + description: >- + a line that stands in the snapshot's period, where the run models + transmission losses + where: transmission_losses AND Line_active + constraints: 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" @@ -243,7 +250,7 @@ constraints: Line_loss_upper: description: "`Line-loss_upper` — a line dissipates at most the loss at its rating" dims: [scenario, snapshot, line] - where: transmission_losses AND Line_active + where: Line_lossy expression: Line_loss <= Line_loss_max Line_loss_tangents_forward: description: >- @@ -252,12 +259,12 @@ constraints: `k`, or one row stacked over its `secant` axis, and this block states them all over the segment dimension dims: [scenario, snapshot, line, segment] - where: transmission_losses AND Line_active + where: Line_lossy expression: Line_loss + Line_loss_slope * Line_s >= Line_loss_offset Line_loss_tangents_reverse: description: >- `Line-loss_tangents-{k}--1`, `Line-loss_secants-neg` — the same fan mirrored, the loss depending on the flow's magnitude dims: [scenario, snapshot, line, segment] - where: transmission_losses AND Line_active + where: Line_lossy expression: Line_loss - Line_loss_slope * Line_s >= Line_loss_offset diff --git a/examples/pypsa/link.yaml b/examples/pypsa/link.yaml index cc46eafe..58fd4ab1 100644 --- a/examples/pypsa/link.yaml +++ b/examples/pypsa/link.yaml @@ -263,6 +263,13 @@ expressions: + sum(Link_output_arrival, by=Link_output_bus, over=link_output, into=bus) adds_to: Bus_injection +masks: + Link_committed: + description: >- + a committable link that stands in the snapshot's period — every + unit-commitment row's set + where: Link_committable AND Link_active + constraints: Link_fix_p_lower: description: "`Link-fix-p-lower` — a fixed link carries at least its minimum, negative for the other way" diff --git a/examples/pypsa/link_commitment.yaml b/examples/pypsa/link_commitment.yaml index 9c5cea68..9b99467d 100644 --- a/examples/pypsa/link_commitment.yaml +++ b/examples/pypsa/link_commitment.yaml @@ -71,21 +71,21 @@ variables: the rows below cap at one, or at the module count where the build is modular dims: [scenario, snapshot, link] - where: Link_committable AND Link_active + where: Link_committed domain: integer bounds: lower: 0 Link_start_up: description: "`Link-start_up` — how much of a committable link turns on this snapshot, capped as the status is" dims: [scenario, snapshot, link] - where: Link_committable AND Link_active + where: Link_committed domain: integer bounds: lower: 0 Link_shut_down: description: "`Link-shut_down` — how much of a committable link turns off this snapshot, capped as the status is" dims: [scenario, snapshot, link] - where: Link_committable AND Link_active + where: Link_committed domain: integer bounds: lower: 0 @@ -111,6 +111,8 @@ given: Link_p_nom_ext: { dims: [link] } expressions: scenario_opex: { dims: [scenario] } + masks: + Link_committed: { dims: [snapshot, link] } expressions: Link_previous_status: @@ -128,26 +130,38 @@ expressions: + sum(sum(Link_shut_down * Link_shut_down_cost, over=link), over=snapshot) adds_to: scenario_opex +masks: + Link_com_ext: + description: >- + a committable link with an extendable, non-modular build that stands in + the snapshot's period — PyPSA's `com-ext` rows, whose status is relaxed + against the chosen build + where: >- + Link_committable + AND Link_p_nom_extendable + AND NOT (Link_p_nom_mod > 0) + AND Link_active + constraints: Link_com_p_lower: description: "`Link-com-p-lower` — a committed link flows at least its minimum; off, at least nothing" dims: [scenario, snapshot, link] - where: Link_committable AND not Link_p_nom_extendable AND Link_active + where: Link_committed AND NOT Link_p_nom_extendable expression: Link_p >= Link_p_min_pu * Link_p_nom * (Link_status - Link_maintenance_pu * Link_maintenance_status) Link_com_p_upper: description: "`Link-com-p-upper` — a committed link flows at most what is available; off, at most nothing" dims: [scenario, snapshot, link] - where: Link_committable AND not Link_p_nom_extendable AND Link_active + where: Link_committed AND NOT Link_p_nom_extendable expression: Link_p <= Link_p_max_pu * Link_p_nom * (Link_status - Link_maintenance_pu * Link_maintenance_status) Link_com_transition_start_up: description: "`Link-com-transition-start-up` — turning on is a start, counted against the state the link carried into the snapshot" dims: [scenario, snapshot, link] - where: Link_committable AND Link_active + where: Link_committed expression: Link_start_up >= Link_status - Link_previous_status Link_com_transition_shut_down: description: "`Link-com-transition-shut-down` — turning off is a stop, counted against the state the link carried into the snapshot" dims: [scenario, snapshot, link] - where: Link_committable AND Link_active + where: Link_committed expression: Link_shut_down >= Link_previous_status - Link_status Link_com_up_time: description: >- @@ -155,7 +169,7 @@ constraints: 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 dims: [scenario, snapshot, link] - where: Link_committable AND Link_min_up_time > 0 AND position(snapshot) > 0 AND Link_active + where: Link_committed AND Link_min_up_time > 0 AND position(snapshot) > 0 expression: sum_back(Link_start_up, along=snapshot, window=Link_min_up_time) <= Link_status Link_com_down_time: description: >- @@ -163,38 +177,38 @@ constraints: time is still off. The first snapshot's share of the window is the brought-in down time's, which the must-stay-down mask carries dims: [scenario, snapshot, link] - where: Link_committable AND Link_min_down_time > 0 AND position(snapshot) > 0 AND Link_active + where: Link_committed AND Link_min_down_time > 0 AND position(snapshot) > 0 expression: sum_back(Link_shut_down, along=snapshot, window=Link_min_down_time) <= 1 - Link_status Link_com_status_must_stay_up: description: "`Link-com-status-min_up_time_must_stay_up` — a link still serving the up time it brought in stays on" dims: [scenario, snapshot, link] - where: Link_committable AND Link_must_stay_up AND Link_active + where: Link_committed AND Link_must_stay_up expression: Link_status == 1 Link_com_status_must_stay_down: description: >- `Link-com-status-min_down_time_must_stay_up` — a link still serving the down time it brought in stays off; PyPSA names the row `_must_stay_up` dims: [scenario, snapshot, link] - where: Link_committable AND Link_must_stay_down AND Link_active + where: Link_committed AND Link_must_stay_down expression: Link_status == 0 Link_com_ext_p_upper_cap: description: >- `Link-com-ext-p-upper-cap` — a committed extendable link flows at most what is available of the chosen build, whatever its status dims: [scenario, snapshot, link] - where: Link_committable AND Link_p_nom_extendable AND NOT (Link_p_nom_mod > 0) AND Link_active + where: Link_com_ext expression: Link_p <= Link_p_max_pu * (Link_p_nom_ext - Link_maintenance_pu * Link_maintenance_capacity) Link_com_ext_p_upper_big_m: description: "`Link-com-ext-p-upper-bigM` — off, a link flows nothing; on, the big M is no bound" dims: [scenario, snapshot, link] - where: Link_committable AND Link_p_nom_extendable AND NOT (Link_p_nom_mod > 0) AND Link_active + where: Link_com_ext expression: Link_p <= Link_big_m * Link_status Link_com_ext_p_lower: description: >- `Link-com-ext-p-lower` — a committed extendable link flows at least its minimum of the chosen build; off, the big M releases the row dims: [scenario, snapshot, link] - where: Link_committable AND Link_p_nom_extendable AND NOT (Link_p_nom_mod > 0) AND Link_active + where: Link_com_ext expression: >- Link_p >= Link_p_min_pu * (Link_p_nom_ext - Link_maintenance_pu * Link_maintenance_capacity) @@ -205,23 +219,21 @@ constraints: negative, flow is also plainly non-negative, a row the big-M lower cannot assert while the link is off dims: [scenario, snapshot, link] - where: >- - Link_committable AND Link_p_nom_extendable - AND Link_p_min_pu_nonneg AND NOT (Link_p_nom_mod > 0) AND Link_active + where: Link_com_ext AND Link_p_min_pu_nonneg expression: Link_p >= 0 Link_com_mod_p_lower: description: >- `Link-com-mod-p-lower` — a committed modular link flows at least its minimum of one module, whether the build is fixed or a decision dims: [scenario, snapshot, link] - where: Link_committable AND Link_p_nom_mod > 0 AND Link_active + where: Link_committed AND Link_p_nom_mod > 0 expression: Link_p >= Link_p_min_pu * Link_p_nom_mod * (Link_status - Link_maintenance_pu * Link_maintenance_status) Link_com_mod_p_upper: description: >- `Link-com-mod-p-upper` — a committed modular link flows at most one module's share, whether the build is fixed or a decision dims: [scenario, snapshot, link] - where: Link_committable AND Link_p_nom_mod > 0 AND Link_active + where: Link_committed AND Link_p_nom_mod > 0 expression: Link_p <= Link_p_max_pu * Link_p_nom_mod * (Link_status - Link_maintenance_pu * Link_maintenance_status) Link_status_p_fixed_upper: description: >- @@ -229,7 +241,7 @@ constraints: 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 dims: [scenario, snapshot, link] - where: Link_committable AND NOT (Link_p_nom_extendable AND Link_p_nom_mod > 0) AND Link_active + where: Link_committed AND NOT (Link_p_nom_extendable AND Link_p_nom_mod > 0) expression: Link_status <= Link_modules_installed Link_start_up_p_fixed_upper: description: >- @@ -237,7 +249,7 @@ constraints: 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 dims: [scenario, snapshot, link] - where: Link_committable AND NOT (Link_p_nom_extendable AND Link_p_nom_mod > 0) AND Link_active + where: Link_committed AND NOT (Link_p_nom_extendable AND Link_p_nom_mod > 0) expression: Link_start_up <= Link_modules_installed Link_shut_down_p_fixed_upper: description: >- @@ -245,20 +257,20 @@ constraints: 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 dims: [scenario, snapshot, link] - where: Link_committable AND NOT (Link_p_nom_extendable AND Link_p_nom_mod > 0) AND Link_active + where: Link_committed AND NOT (Link_p_nom_extendable AND Link_p_nom_mod > 0) expression: Link_shut_down <= Link_modules_installed Link_status_p_nom_variable_upper: description: "`Link-status-p_nom-variable-upper` — a modular link is on only where a module is built" dims: [scenario, snapshot, link] - where: Link_committable AND Link_p_nom_extendable AND Link_p_nom_mod > 0 AND Link_active + where: Link_committed AND Link_p_nom_extendable AND Link_p_nom_mod > 0 expression: Link_status <= Link_n_mod Link_start_up_p_nom_variable_upper: description: "`Link-start_up-p_nom-variable-upper` — a modular link starts only where a module is built" dims: [scenario, snapshot, link] - where: Link_committable AND Link_p_nom_extendable AND Link_p_nom_mod > 0 AND Link_active + where: Link_committed AND Link_p_nom_extendable AND Link_p_nom_mod > 0 expression: Link_start_up <= Link_n_mod Link_shut_down_p_nom_variable_upper: description: "`Link-shut_down-p_nom-variable-upper` — a modular link stops only where a module is built" dims: [scenario, snapshot, link] - where: Link_committable AND Link_p_nom_extendable AND Link_p_nom_mod > 0 AND Link_active + where: Link_committed AND Link_p_nom_extendable AND Link_p_nom_mod > 0 expression: Link_shut_down <= Link_n_mod diff --git a/examples/pypsa/link_maintenance.yaml b/examples/pypsa/link_maintenance.yaml index e81eefbf..7c5a1327 100644 --- a/examples/pypsa/link_maintenance.yaml +++ b/examples/pypsa/link_maintenance.yaml @@ -74,9 +74,7 @@ variables: `Link-maintenance_capacity` — the chosen build while in maintenance, zero otherwise: the product the `maintcap` rows linearize dims: [scenario, snapshot, link] - where: >- - Link_maintainable AND Link_p_nom_extendable - AND NOT (Link_committable AND Link_p_nom_mod > 0) AND Link_active + where: Link_maint_ext absence: zero bounds: lower: 0 @@ -86,9 +84,7 @@ variables: otherwise: the product the `maint-status` rows linearize, so a unit in maintenance may also be off dims: [scenario, snapshot, link] - where: >- - Link_maintainable AND Link_committable - AND NOT (Link_p_nom_extendable AND NOT (Link_p_nom_mod > 0)) AND Link_active + where: Link_maintainable AND Link_committed AND NOT (Link_p_nom_extendable AND NOT (Link_p_nom_mod > 0)) absence: zero bounds: lower: 0 @@ -105,6 +101,20 @@ given: variables: Link_status: { dims: [scenario, snapshot, link], domain: integer } Link_p_nom_ext: { dims: [link] } + masks: + Link_committed: { dims: [snapshot, link] } + +masks: + Link_maint_ext: + description: >- + a maintainable link with an extendable build, unless it is committable + and modular, that stands in the snapshot's period — the maintenance rows + against the chosen build + where: >- + Link_maintainable + AND Link_p_nom_extendable + AND NOT (Link_committable AND Link_p_nom_mod > 0) + AND Link_active constraints: Link_maint_event_count: @@ -130,54 +140,54 @@ constraints: `Link-maintcap_upper` — the build taken off is at most the chosen build in maintenance, and at most the build less its floor out of it dims: [scenario, snapshot, link] - where: Link_maintainable AND Link_p_nom_extendable AND NOT (Link_committable AND Link_p_nom_mod > 0) AND Link_active + where: Link_maint_ext expression: Link_maintenance_capacity <= Link_p_nom_ext - Link_p_nom_min * (1 - Link_maintenance) Link_maintcap_upper_nommax: description: "`Link-maintcap_upper_nommax` — out of maintenance, no build is taken off" dims: [scenario, snapshot, link] - where: Link_maintainable AND Link_p_nom_extendable AND NOT (Link_committable AND Link_p_nom_mod > 0) AND Link_active + where: Link_maint_ext expression: Link_maintenance_capacity <= Link_p_nom_max * Link_maintenance Link_maintcap_lower_nommax: description: "`Link-maintcap_lower_nommax` — in maintenance, the whole chosen build is taken off" dims: [scenario, snapshot, link] - where: Link_maintainable AND Link_p_nom_extendable AND NOT (Link_committable AND Link_p_nom_mod > 0) AND Link_active + where: Link_maint_ext expression: Link_maintenance_capacity >= Link_p_nom_ext - Link_p_nom_max * (1 - Link_maintenance) Link_maintcap_lower_nommin: description: "`Link-maintcap_lower_nommin` — in maintenance, at least the floor of the build is taken off" dims: [scenario, snapshot, link] - where: Link_maintainable AND Link_p_nom_extendable AND NOT (Link_committable AND Link_p_nom_mod > 0) AND Link_active AND Link_p_nom_min > 0 + where: Link_maint_ext AND Link_p_nom_min > 0 expression: Link_maintenance_capacity >= Link_p_nom_min * Link_maintenance Link_maint_status_le_status: description: "`Link-maint-status-le-status` — the status in maintenance is at most the status" dims: [scenario, snapshot, link] - where: Link_maintainable AND Link_committable AND NOT Link_p_nom_extendable AND Link_active + where: Link_maintainable AND Link_committed AND NOT Link_p_nom_extendable expression: Link_maintenance_status <= Link_status Link_maint_status_le_maint: description: "`Link-maint-status-le-maint` — out of maintenance, the status in maintenance is zero" dims: [scenario, snapshot, link] - where: Link_maintainable AND Link_committable AND NOT Link_p_nom_extendable AND Link_active + where: Link_maintainable AND Link_committed AND NOT Link_p_nom_extendable expression: Link_maintenance_status <= Link_maintenance Link_maint_status_lb: description: "`Link-maint-status-lb` — on and in maintenance, the status in maintenance is one" dims: [scenario, snapshot, link] - where: Link_maintainable AND Link_committable AND NOT Link_p_nom_extendable AND Link_active + where: Link_maintainable AND Link_committed AND NOT Link_p_nom_extendable expression: Link_maintenance_status >= Link_status + Link_maintenance - 1 Link_maint_modstatus_le_status: description: "`Link-maint-modstatus-le-status` — the modules on in maintenance are at most the modules on" dims: [scenario, snapshot, link] - where: Link_maintainable AND Link_committable AND Link_p_nom_mod > 0 AND Link_active + where: Link_maintainable AND Link_committed AND Link_p_nom_mod > 0 expression: Link_maintenance_status <= Link_status Link_maint_modstatus_le_maint: description: >- `Link-maint-modstatus-le-maint` — out of maintenance, no module is on in maintenance; in it, at most the modules the build cap holds dims: [scenario, snapshot, link] - where: Link_maintainable AND Link_committable AND Link_p_nom_mod > 0 AND Link_active + where: Link_maintainable AND Link_committed AND Link_p_nom_mod > 0 expression: Link_maintenance_status <= Link_p_nom_max / Link_p_nom_mod * Link_maintenance Link_maint_modstatus_lb: description: "`Link-maint-modstatus-lb` — in maintenance, every module on is on in maintenance" dims: [scenario, snapshot, link] - where: Link_maintainable AND Link_committable AND Link_p_nom_mod > 0 AND Link_active + where: Link_maintainable AND Link_committed AND Link_p_nom_mod > 0 expression: Link_maintenance_status >= Link_status - Link_p_nom_max / Link_p_nom_mod * (1 - Link_maintenance) assumptions: diff --git a/examples/pypsa/link_ramping.yaml b/examples/pypsa/link_ramping.yaml index 4b643a02..945a0fb6 100644 --- a/examples/pypsa/link_ramping.yaml +++ b/examples/pypsa/link_ramping.yaml @@ -58,6 +58,8 @@ given: Link_p_nom_effective: { dims: [scenario, link] } Link_previous_status: { dims: [scenario, snapshot, link] } Link_p_nom_committed: { dims: [scenario, link] } + masks: + Link_com_ext: { dims: [snapshot, link] } expressions: Link_previous_p: @@ -130,6 +132,16 @@ expressions: * (Link_previous_status - Link_status) otherwise: Link_ramp_down_rate * Link_p_nom_effective +masks: + Link_ramps_from_previous: + description: >- + a snapshot whose ramp reads a previous output — any snapshot but its + period's first, and the horizon's first where the link comes in off or + carries an initial output + where: >- + position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND + (Link_status_initial == 0 OR Link_p_init)) + constraints: Link_p_ramp_limit_up_run_big_m: description: >- @@ -137,11 +149,7 @@ constraints: raises flow no faster than its limit of the chosen build; the big M releases the row in the snapshot it turns on dims: [scenario, snapshot, link] - where: >- - Link_committable AND Link_p_nom_extendable AND NOT (Link_p_nom_mod > 0) - AND (Link_ramp_limit_up OR Link_ramp_limit_start_up) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Link_status_initial == 0 OR Link_p_init))) - AND Link_active + where: Link_com_ext AND (Link_ramp_limit_up OR Link_ramp_limit_start_up) AND Link_ramps_from_previous expression: >- Link_p - Link_previous_p <= Link_ramp_up_rate * Link_p_nom_ext @@ -152,11 +160,7 @@ constraints: committed extendable link ramps no further than its start-up ramp of the chosen build; the big M releases the row everywhere else dims: [scenario, snapshot, link] - where: >- - Link_committable AND Link_p_nom_extendable AND NOT (Link_p_nom_mod > 0) - AND (Link_ramp_limit_up OR Link_ramp_limit_start_up) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Link_status_initial == 0 OR Link_p_init))) - AND Link_active + where: Link_com_ext AND (Link_ramp_limit_up OR Link_ramp_limit_start_up) AND Link_ramps_from_previous expression: >- Link_p - Link_previous_p <= Link_start_up_rate * Link_p_nom_ext @@ -167,11 +171,7 @@ constraints: lowers flow no faster than its limit of the chosen build; the big M releases the row in the snapshot it turns off dims: [scenario, snapshot, link] - where: >- - Link_committable AND Link_p_nom_extendable AND NOT (Link_p_nom_mod > 0) - AND (Link_ramp_limit_down OR Link_ramp_limit_shut_down) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Link_status_initial == 0 OR Link_p_init))) - AND Link_active + where: Link_com_ext AND (Link_ramp_limit_down OR Link_ramp_limit_shut_down) AND Link_ramps_from_previous expression: >- Link_previous_p - Link_p <= Link_ramp_down_rate * Link_p_nom_ext @@ -182,11 +182,7 @@ constraints: a committed extendable link ramps no further than its shut-down ramp of the chosen build; the big M releases the row everywhere else dims: [scenario, snapshot, link] - where: >- - Link_committable AND Link_p_nom_extendable AND NOT (Link_p_nom_mod > 0) - AND (Link_ramp_limit_down OR Link_ramp_limit_shut_down) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Link_status_initial == 0 OR Link_p_init))) - AND Link_active + where: Link_com_ext AND (Link_ramp_limit_down OR Link_ramp_limit_shut_down) AND Link_ramps_from_previous expression: >- Link_previous_p - Link_p <= Link_shut_down_rate * Link_p_nom_ext @@ -203,7 +199,7 @@ constraints: where: >- (Link_ramp_limit_up OR Link_ramp_limit_start_up) AND NOT (Link_committable AND Link_p_nom_extendable AND NOT (Link_p_nom_mod > 0)) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Link_status_initial == 0 OR Link_p_init))) + AND Link_ramps_from_previous AND Link_active expression: Link_p - Link_previous_p <= Link_ramp_up_allowance Link_p_ramp_limit_down: @@ -218,7 +214,7 @@ constraints: where: >- (Link_ramp_limit_down OR Link_ramp_limit_shut_down) AND NOT (Link_committable AND Link_p_nom_extendable AND NOT (Link_p_nom_mod > 0)) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Link_status_initial == 0 OR Link_p_init))) + AND Link_ramps_from_previous AND Link_active expression: Link_previous_p - Link_p <= Link_ramp_down_allowance diff --git a/examples/pypsa/process.yaml b/examples/pypsa/process.yaml index 8c16c8a9..7c47886d 100644 --- a/examples/pypsa/process.yaml +++ b/examples/pypsa/process.yaml @@ -233,6 +233,13 @@ expressions: sum(Process_output_arrival, by=Process_output_bus, over=process_output, into=bus) adds_to: Bus_injection +masks: + Process_committed: + description: >- + a committable process that stands in the snapshot's period — every + unit-commitment row's set + where: Process_committable AND Process_active + constraints: Process_fix_p_lower: description: "`Process-fix-p-lower` — a fixed process runs at least its minimum, negative for the other way" diff --git a/examples/pypsa/process_commitment.yaml b/examples/pypsa/process_commitment.yaml index 79c1a12d..e748c65b 100644 --- a/examples/pypsa/process_commitment.yaml +++ b/examples/pypsa/process_commitment.yaml @@ -71,21 +71,21 @@ variables: the rows below cap at one, or at the module count where the build is modular dims: [scenario, snapshot, process] - where: Process_committable AND Process_active + where: Process_committed domain: integer bounds: lower: 0 Process_start_up: description: "`Process-start_up` — how much of a committable process turns on this snapshot, capped as the status is" dims: [scenario, snapshot, process] - where: Process_committable AND Process_active + where: Process_committed domain: integer bounds: lower: 0 Process_shut_down: description: "`Process-shut_down` — how much of a committable process turns off this snapshot, capped as the status is" dims: [scenario, snapshot, process] - where: Process_committable AND Process_active + where: Process_committed domain: integer bounds: lower: 0 @@ -111,6 +111,8 @@ given: Process_p_nom_ext: { dims: [process] } expressions: scenario_opex: { dims: [scenario] } + masks: + Process_committed: { dims: [snapshot, process] } expressions: Process_previous_status: @@ -128,26 +130,38 @@ expressions: + sum(sum(Process_shut_down * Process_shut_down_cost, over=process), over=snapshot) adds_to: scenario_opex +masks: + Process_com_ext: + description: >- + a committable process with an extendable, non-modular build that stands + in the snapshot's period — PyPSA's `com-ext` rows, whose status is + relaxed against the chosen build + where: >- + Process_committable + AND Process_p_nom_extendable + AND NOT (Process_p_nom_mod > 0) + AND Process_active + constraints: Process_com_p_lower: description: "`Process-com-p-lower` — a committed process runs at least its minimum; off, at least nothing" dims: [scenario, snapshot, process] - where: Process_committable AND not Process_p_nom_extendable AND Process_active + where: Process_committed AND NOT Process_p_nom_extendable expression: Process_p >= Process_p_min_pu * Process_p_nom * (Process_status - Process_maintenance_pu * Process_maintenance_status) Process_com_p_upper: description: "`Process-com-p-upper` — a committed process runs at most what is available; off, at most nothing" dims: [scenario, snapshot, process] - where: Process_committable AND not Process_p_nom_extendable AND Process_active + where: Process_committed AND NOT Process_p_nom_extendable expression: Process_p <= Process_p_max_pu * Process_p_nom * (Process_status - Process_maintenance_pu * Process_maintenance_status) Process_com_transition_start_up: description: "`Process-com-transition-start-up` — turning on is a start, counted against the state the process carried into the snapshot" dims: [scenario, snapshot, process] - where: Process_committable AND Process_active + where: Process_committed expression: Process_start_up >= Process_status - Process_previous_status Process_com_transition_shut_down: description: "`Process-com-transition-shut-down` — turning off is a stop, counted against the state the process carried into the snapshot" dims: [scenario, snapshot, process] - where: Process_committable AND Process_active + where: Process_committed expression: Process_shut_down >= Process_previous_status - Process_status Process_com_up_time: description: >- @@ -155,7 +169,7 @@ constraints: 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 dims: [scenario, snapshot, process] - where: Process_committable AND Process_min_up_time > 0 AND position(snapshot) > 0 AND Process_active + where: Process_committed AND Process_min_up_time > 0 AND position(snapshot) > 0 expression: sum_back(Process_start_up, along=snapshot, window=Process_min_up_time) <= Process_status Process_com_down_time: description: >- @@ -163,38 +177,38 @@ constraints: time is still off. The first snapshot's share of the window is the brought-in down time's, which the must-stay-down mask carries dims: [scenario, snapshot, process] - where: Process_committable AND Process_min_down_time > 0 AND position(snapshot) > 0 AND Process_active + where: Process_committed AND Process_min_down_time > 0 AND position(snapshot) > 0 expression: sum_back(Process_shut_down, along=snapshot, window=Process_min_down_time) <= 1 - Process_status Process_com_status_must_stay_up: description: "`Process-com-status-min_up_time_must_stay_up` — a process still serving the up time it brought in stays on" dims: [scenario, snapshot, process] - where: Process_committable AND Process_must_stay_up AND Process_active + where: Process_committed AND Process_must_stay_up expression: Process_status == 1 Process_com_status_must_stay_down: description: >- `Process-com-status-min_down_time_must_stay_up` — a process still serving the down time it brought in stays off; PyPSA names the row `_must_stay_up` dims: [scenario, snapshot, process] - where: Process_committable AND Process_must_stay_down AND Process_active + where: Process_committed AND Process_must_stay_down expression: Process_status == 0 Process_com_ext_p_upper_cap: description: >- `Process-com-ext-p-upper-cap` — a committed extendable process runs at most what is available of the chosen build, whatever its status dims: [scenario, snapshot, process] - where: Process_committable AND Process_p_nom_extendable AND NOT (Process_p_nom_mod > 0) AND Process_active + where: Process_com_ext expression: Process_p <= Process_p_max_pu * (Process_p_nom_ext - Process_maintenance_pu * Process_maintenance_capacity) Process_com_ext_p_upper_big_m: description: "`Process-com-ext-p-upper-bigM` — off, a process does not run; on, the big M is no bound" dims: [scenario, snapshot, process] - where: Process_committable AND Process_p_nom_extendable AND NOT (Process_p_nom_mod > 0) AND Process_active + where: Process_com_ext expression: Process_p <= Process_big_m * Process_status Process_com_ext_p_lower: description: >- `Process-com-ext-p-lower` — a committed extendable process runs at least its minimum of the chosen build; off, the big M releases the row dims: [scenario, snapshot, process] - where: Process_committable AND Process_p_nom_extendable AND NOT (Process_p_nom_mod > 0) AND Process_active + where: Process_com_ext expression: >- Process_p >= Process_p_min_pu * (Process_p_nom_ext - Process_maintenance_pu * Process_maintenance_capacity) @@ -205,23 +219,21 @@ constraints: negative, internal power is also plainly non-negative, a row the big-M lower cannot assert while the process is off dims: [scenario, snapshot, process] - where: >- - Process_committable AND Process_p_nom_extendable - AND Process_p_min_pu_nonneg AND NOT (Process_p_nom_mod > 0) AND Process_active + where: Process_com_ext AND Process_p_min_pu_nonneg expression: Process_p >= 0 Process_com_mod_p_lower: description: >- `Process-com-mod-p-lower` — a committed modular process runs at least its minimum of one module, whether the build is fixed or a decision dims: [scenario, snapshot, process] - where: Process_committable AND Process_p_nom_mod > 0 AND Process_active + where: Process_committed AND Process_p_nom_mod > 0 expression: Process_p >= Process_p_min_pu * Process_p_nom_mod * (Process_status - Process_maintenance_pu * Process_maintenance_status) Process_com_mod_p_upper: description: >- `Process-com-mod-p-upper` — a committed modular process runs at most one module's share, whether the build is fixed or a decision dims: [scenario, snapshot, process] - where: Process_committable AND Process_p_nom_mod > 0 AND Process_active + where: Process_committed AND Process_p_nom_mod > 0 expression: Process_p <= Process_p_max_pu * Process_p_nom_mod * (Process_status - Process_maintenance_pu * Process_maintenance_status) Process_status_p_fixed_upper: description: >- @@ -229,7 +241,7 @@ constraints: 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 dims: [scenario, snapshot, process] - where: Process_committable AND NOT (Process_p_nom_extendable AND Process_p_nom_mod > 0) AND Process_active + where: Process_committed AND NOT (Process_p_nom_extendable AND Process_p_nom_mod > 0) expression: Process_status <= Process_modules_installed Process_start_up_p_fixed_upper: description: >- @@ -237,7 +249,7 @@ constraints: 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 dims: [scenario, snapshot, process] - where: Process_committable AND NOT (Process_p_nom_extendable AND Process_p_nom_mod > 0) AND Process_active + where: Process_committed AND NOT (Process_p_nom_extendable AND Process_p_nom_mod > 0) expression: Process_start_up <= Process_modules_installed Process_shut_down_p_fixed_upper: description: >- @@ -245,20 +257,20 @@ constraints: 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 dims: [scenario, snapshot, process] - where: Process_committable AND NOT (Process_p_nom_extendable AND Process_p_nom_mod > 0) AND Process_active + where: Process_committed AND NOT (Process_p_nom_extendable AND Process_p_nom_mod > 0) expression: Process_shut_down <= Process_modules_installed Process_status_p_nom_variable_upper: description: "`Process-status-p_nom-variable-upper` — a modular process is on only where a module is built" dims: [scenario, snapshot, process] - where: Process_committable AND Process_p_nom_extendable AND Process_p_nom_mod > 0 AND Process_active + where: Process_committed AND Process_p_nom_extendable AND Process_p_nom_mod > 0 expression: Process_status <= Process_n_mod Process_start_up_p_nom_variable_upper: description: "`Process-start_up-p_nom-variable-upper` — a modular process starts only where a module is built" dims: [scenario, snapshot, process] - where: Process_committable AND Process_p_nom_extendable AND Process_p_nom_mod > 0 AND Process_active + where: Process_committed AND Process_p_nom_extendable AND Process_p_nom_mod > 0 expression: Process_start_up <= Process_n_mod Process_shut_down_p_nom_variable_upper: description: "`Process-shut_down-p_nom-variable-upper` — a modular process stops only where a module is built" dims: [scenario, snapshot, process] - where: Process_committable AND Process_p_nom_extendable AND Process_p_nom_mod > 0 AND Process_active + where: Process_committed AND Process_p_nom_extendable AND Process_p_nom_mod > 0 expression: Process_shut_down <= Process_n_mod diff --git a/examples/pypsa/process_maintenance.yaml b/examples/pypsa/process_maintenance.yaml index 2f1b9de8..cff7617f 100644 --- a/examples/pypsa/process_maintenance.yaml +++ b/examples/pypsa/process_maintenance.yaml @@ -74,9 +74,7 @@ variables: `Process-maintenance_capacity` — the chosen build while in maintenance, zero otherwise: the product the `maintcap` rows linearize dims: [scenario, snapshot, process] - where: >- - Process_maintainable AND Process_p_nom_extendable - AND NOT (Process_committable AND Process_p_nom_mod > 0) AND Process_active + where: Process_maint_ext absence: zero bounds: lower: 0 @@ -87,8 +85,9 @@ variables: maintenance may also be off dims: [scenario, snapshot, process] where: >- - Process_maintainable AND Process_committable - AND NOT (Process_p_nom_extendable AND NOT (Process_p_nom_mod > 0)) AND Process_active + Process_maintainable + AND Process_committed + AND NOT (Process_p_nom_extendable AND NOT (Process_p_nom_mod > 0)) absence: zero bounds: lower: 0 @@ -105,6 +104,20 @@ given: variables: Process_status: { dims: [scenario, snapshot, process], domain: integer } Process_p_nom_ext: { dims: [process] } + masks: + Process_committed: { dims: [snapshot, process] } + +masks: + Process_maint_ext: + description: >- + a maintainable process with an extendable build, unless it is + committable and modular, that stands in the snapshot's period — the + maintenance rows against the chosen build + where: >- + Process_maintainable + AND Process_p_nom_extendable + AND NOT (Process_committable AND Process_p_nom_mod > 0) + AND Process_active constraints: Process_maint_event_count: @@ -130,54 +143,54 @@ constraints: `Process-maintcap_upper` — the build taken off is at most the chosen build in maintenance, and at most the build less its floor out of it dims: [scenario, snapshot, process] - where: Process_maintainable AND Process_p_nom_extendable AND NOT (Process_committable AND Process_p_nom_mod > 0) AND Process_active + where: Process_maint_ext expression: Process_maintenance_capacity <= Process_p_nom_ext - Process_p_nom_min * (1 - Process_maintenance) Process_maintcap_upper_nommax: description: "`Process-maintcap_upper_nommax` — out of maintenance, no build is taken off" dims: [scenario, snapshot, process] - where: Process_maintainable AND Process_p_nom_extendable AND NOT (Process_committable AND Process_p_nom_mod > 0) AND Process_active + where: Process_maint_ext expression: Process_maintenance_capacity <= Process_p_nom_max * Process_maintenance Process_maintcap_lower_nommax: description: "`Process-maintcap_lower_nommax` — in maintenance, the whole chosen build is taken off" dims: [scenario, snapshot, process] - where: Process_maintainable AND Process_p_nom_extendable AND NOT (Process_committable AND Process_p_nom_mod > 0) AND Process_active + where: Process_maint_ext expression: Process_maintenance_capacity >= Process_p_nom_ext - Process_p_nom_max * (1 - Process_maintenance) Process_maintcap_lower_nommin: description: "`Process-maintcap_lower_nommin` — in maintenance, at least the floor of the build is taken off" dims: [scenario, snapshot, process] - where: Process_maintainable AND Process_p_nom_extendable AND NOT (Process_committable AND Process_p_nom_mod > 0) AND Process_active AND Process_p_nom_min > 0 + where: Process_maint_ext AND Process_p_nom_min > 0 expression: Process_maintenance_capacity >= Process_p_nom_min * Process_maintenance Process_maint_status_le_status: description: "`Process-maint-status-le-status` — the status in maintenance is at most the status" dims: [scenario, snapshot, process] - where: Process_maintainable AND Process_committable AND NOT Process_p_nom_extendable AND Process_active + where: Process_maintainable AND Process_committed AND NOT Process_p_nom_extendable expression: Process_maintenance_status <= Process_status Process_maint_status_le_maint: description: "`Process-maint-status-le-maint` — out of maintenance, the status in maintenance is zero" dims: [scenario, snapshot, process] - where: Process_maintainable AND Process_committable AND NOT Process_p_nom_extendable AND Process_active + where: Process_maintainable AND Process_committed AND NOT Process_p_nom_extendable expression: Process_maintenance_status <= Process_maintenance Process_maint_status_lb: description: "`Process-maint-status-lb` — on and in maintenance, the status in maintenance is one" dims: [scenario, snapshot, process] - where: Process_maintainable AND Process_committable AND NOT Process_p_nom_extendable AND Process_active + where: Process_maintainable AND Process_committed AND NOT Process_p_nom_extendable expression: Process_maintenance_status >= Process_status + Process_maintenance - 1 Process_maint_modstatus_le_status: description: "`Process-maint-modstatus-le-status` — the modules on in maintenance are at most the modules on" dims: [scenario, snapshot, process] - where: Process_maintainable AND Process_committable AND Process_p_nom_mod > 0 AND Process_active + where: Process_maintainable AND Process_committed AND Process_p_nom_mod > 0 expression: Process_maintenance_status <= Process_status Process_maint_modstatus_le_maint: description: >- `Process-maint-modstatus-le-maint` — out of maintenance, no module is on in maintenance; in it, at most the modules the build cap holds dims: [scenario, snapshot, process] - where: Process_maintainable AND Process_committable AND Process_p_nom_mod > 0 AND Process_active + where: Process_maintainable AND Process_committed AND Process_p_nom_mod > 0 expression: Process_maintenance_status <= Process_p_nom_max / Process_p_nom_mod * Process_maintenance Process_maint_modstatus_lb: description: "`Process-maint-modstatus-lb` — in maintenance, every module on is on in maintenance" dims: [scenario, snapshot, process] - where: Process_maintainable AND Process_committable AND Process_p_nom_mod > 0 AND Process_active + where: Process_maintainable AND Process_committed AND Process_p_nom_mod > 0 expression: Process_maintenance_status >= Process_status - Process_p_nom_max / Process_p_nom_mod * (1 - Process_maintenance) assumptions: diff --git a/examples/pypsa/process_ramping.yaml b/examples/pypsa/process_ramping.yaml index 7bb61fdf..1d9911a7 100644 --- a/examples/pypsa/process_ramping.yaml +++ b/examples/pypsa/process_ramping.yaml @@ -58,6 +58,8 @@ given: Process_p_nom_effective: { dims: [scenario, process] } Process_previous_status: { dims: [scenario, snapshot, process] } Process_p_nom_committed: { dims: [scenario, process] } + masks: + Process_com_ext: { dims: [snapshot, process] } expressions: Process_previous_p: @@ -130,6 +132,16 @@ expressions: * (Process_previous_status - Process_status) otherwise: Process_ramp_down_rate * Process_p_nom_effective +masks: + Process_ramps_from_previous: + description: >- + a snapshot whose ramp reads a previous output — any snapshot but its + period's first, and the horizon's first where the process comes in off + or carries an initial output + where: >- + position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND + (Process_status_initial == 0 OR Process_p_init)) + constraints: Process_p_ramp_limit_up_run_big_m: description: >- @@ -137,11 +149,7 @@ constraints: raises internal power no faster than its limit of the chosen build; the big M releases the row in the snapshot it turns on dims: [scenario, snapshot, process] - where: >- - Process_committable AND Process_p_nom_extendable AND NOT (Process_p_nom_mod > 0) - AND (Process_ramp_limit_up OR Process_ramp_limit_start_up) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Process_status_initial == 0 OR Process_p_init))) - AND Process_active + where: Process_com_ext AND (Process_ramp_limit_up OR Process_ramp_limit_start_up) AND Process_ramps_from_previous expression: >- Process_p - Process_previous_p <= Process_ramp_up_rate * Process_p_nom_ext @@ -152,11 +160,7 @@ constraints: committed extendable process ramps no further than its start-up ramp of the chosen build; the big M releases the row everywhere else dims: [scenario, snapshot, process] - where: >- - Process_committable AND Process_p_nom_extendable AND NOT (Process_p_nom_mod > 0) - AND (Process_ramp_limit_up OR Process_ramp_limit_start_up) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Process_status_initial == 0 OR Process_p_init))) - AND Process_active + where: Process_com_ext AND (Process_ramp_limit_up OR Process_ramp_limit_start_up) AND Process_ramps_from_previous expression: >- Process_p - Process_previous_p <= Process_start_up_rate * Process_p_nom_ext @@ -168,10 +172,9 @@ constraints: releases the row in the snapshot it turns off dims: [scenario, snapshot, process] where: >- - Process_committable AND Process_p_nom_extendable AND NOT (Process_p_nom_mod > 0) + Process_com_ext AND (Process_ramp_limit_down OR Process_ramp_limit_shut_down) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Process_status_initial == 0 OR Process_p_init))) - AND Process_active + AND Process_ramps_from_previous expression: >- Process_previous_p - Process_p <= Process_ramp_down_rate * Process_p_nom_ext @@ -183,10 +186,9 @@ constraints: the chosen build; the big M releases the row everywhere else dims: [scenario, snapshot, process] where: >- - Process_committable AND Process_p_nom_extendable AND NOT (Process_p_nom_mod > 0) + Process_com_ext AND (Process_ramp_limit_down OR Process_ramp_limit_shut_down) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Process_status_initial == 0 OR Process_p_init))) - AND Process_active + AND Process_ramps_from_previous expression: >- Process_previous_p - Process_p <= Process_shut_down_rate * Process_p_nom_ext @@ -203,7 +205,7 @@ constraints: where: >- (Process_ramp_limit_up OR Process_ramp_limit_start_up) AND NOT (Process_committable AND Process_p_nom_extendable AND NOT (Process_p_nom_mod > 0)) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Process_status_initial == 0 OR Process_p_init))) + AND Process_ramps_from_previous AND Process_active expression: Process_p - Process_previous_p <= Process_ramp_up_allowance Process_p_ramp_limit_down: @@ -218,7 +220,7 @@ constraints: where: >- (Process_ramp_limit_down OR Process_ramp_limit_shut_down) AND NOT (Process_committable AND Process_p_nom_extendable AND NOT (Process_p_nom_mod > 0)) - AND (position(snapshot, by=snapshot_period, within=period) > 0 OR (position(snapshot) == 0 AND (Process_status_initial == 0 OR Process_p_init))) + AND Process_ramps_from_previous AND Process_active expression: Process_previous_p - Process_p <= Process_ramp_down_allowance diff --git a/examples/pypsa/storage_unit.yaml b/examples/pypsa/storage_unit.yaml index 0bcc83d9..b640c1f9 100644 --- a/examples/pypsa/storage_unit.yaml +++ b/examples/pypsa/storage_unit.yaml @@ -301,70 +301,82 @@ expressions: sum(StorageUnit_sign * (StorageUnit_p_dispatch - StorageUnit_p_store), by=StorageUnit_bus, over=storage_unit, into=bus) adds_to: Bus_injection +masks: + StorageUnit_fix: + description: >- + a storage unit with a fixed build that stands in the snapshot's period — + PyPSA's `fix` rows + where: NOT StorageUnit_p_nom_extendable AND StorageUnit_active + StorageUnit_ext: + description: >- + a storage unit with an extendable build that stands in the snapshot's + period — PyPSA's `ext` rows + where: StorageUnit_p_nom_extendable AND StorageUnit_active + constraints: StorageUnit_fix_p_dispatch_lower: description: "`StorageUnit-fix-p_dispatch-lower` — dispatch is non-negative" dims: [scenario, snapshot, storage_unit] - where: not StorageUnit_p_nom_extendable AND StorageUnit_active + where: StorageUnit_fix 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" dims: [scenario, snapshot, storage_unit] - where: not StorageUnit_p_nom_extendable AND StorageUnit_active + where: StorageUnit_fix 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" dims: [scenario, snapshot, storage_unit] - where: not StorageUnit_p_nom_extendable AND StorageUnit_active + where: StorageUnit_fix 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 dims: [scenario, snapshot, storage_unit] - where: not StorageUnit_p_nom_extendable AND StorageUnit_active + where: StorageUnit_fix 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" dims: [scenario, snapshot, storage_unit] - where: not StorageUnit_p_nom_extendable AND StorageUnit_active + where: StorageUnit_fix 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" dims: [scenario, snapshot, storage_unit] - where: not StorageUnit_p_nom_extendable AND StorageUnit_active + where: StorageUnit_fix expression: StorageUnit_state_of_charge <= StorageUnit_max_hours * StorageUnit_p_nom StorageUnit_ext_p_dispatch_lower: description: "`StorageUnit-ext-p_dispatch-lower` — dispatch is non-negative" dims: [scenario, snapshot, storage_unit] - where: StorageUnit_p_nom_extendable AND StorageUnit_active + where: StorageUnit_ext 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" dims: [scenario, snapshot, storage_unit] - where: StorageUnit_p_nom_extendable AND StorageUnit_active + where: StorageUnit_ext 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" dims: [scenario, snapshot, storage_unit] - where: StorageUnit_p_nom_extendable AND StorageUnit_active + where: StorageUnit_ext 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 dims: [scenario, snapshot, storage_unit] - where: StorageUnit_p_nom_extendable AND StorageUnit_active + where: StorageUnit_ext 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" dims: [scenario, snapshot, storage_unit] - where: StorageUnit_p_nom_extendable AND StorageUnit_active + where: StorageUnit_ext 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" dims: [scenario, snapshot, storage_unit] - where: StorageUnit_p_nom_extendable AND StorageUnit_active + where: StorageUnit_ext 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 in every scenario" diff --git a/examples/pypsa/transformer.yaml b/examples/pypsa/transformer.yaml index 6f2f3061..922fe930 100644 --- a/examples/pypsa/transformer.yaml +++ b/examples/pypsa/transformer.yaml @@ -137,7 +137,7 @@ variables: a line does; absent, and zero in the balance, where the network is lossless dims: [scenario, snapshot, transformer] - where: transmission_losses AND Transformer_active + where: Transformer_lossy absence: zero bounds: lower: 0 @@ -193,6 +193,13 @@ expressions: + sum(Transformer_phase_shift * Transformer_phase_shift_cycle_weight, over=transformer) adds_to: Cycle_angle_sum +masks: + Transformer_lossy: + description: >- + a transformer that stands in the snapshot's period, where the run models + transmission losses + where: transmission_losses AND Transformer_active + constraints: Transformer_fix_s_lower: description: "`Transformer-fix-s-lower` — a fixed transformer carries at least the negative of its rating, the loss counted against it" @@ -237,7 +244,7 @@ constraints: Transformer_loss_upper: description: "`Transformer-loss_upper` — a transformer dissipates at most the loss at its rating" dims: [scenario, snapshot, transformer] - where: transmission_losses AND Transformer_active + where: Transformer_lossy expression: Transformer_loss <= Transformer_loss_max Transformer_loss_tangents_forward: description: >- @@ -245,12 +252,12 @@ constraints: loss sits above every cut to its curve for flow one way, as a line's does, over the segment dimension dims: [scenario, snapshot, transformer, segment] - where: transmission_losses AND Transformer_active + where: Transformer_lossy expression: Transformer_loss + Transformer_loss_slope * Transformer_s >= Transformer_loss_offset Transformer_loss_tangents_reverse: description: >- `Transformer-loss_tangents-{k}--1`, `Transformer-loss_secants-neg` — the same fan mirrored, the loss depending on the flow's magnitude dims: [scenario, snapshot, transformer, segment] - where: transmission_losses AND Transformer_active + where: Transformer_lossy expression: Transformer_loss - Transformer_loss_slope * Transformer_s >= Transformer_loss_offset diff --git a/examples/symbols/pypsa.yaml b/examples/symbols/pypsa.yaml index c5331425..71212d50 100644 --- a/examples/symbols/pypsa.yaml +++ b/examples/symbols/pypsa.yaml @@ -312,6 +312,22 @@ names: Line_active: '\mathrm{on}^{s}' Process_active: '\mathrm{on}^{z}' Transformer_active: '\mathrm{on}^{\sigma}' + Generator_committed: '\mathrm{on}^{\mathrm{com}}' + Generator_com_ext: '\mathrm{on}^{\mathrm{com,ext}}' + Generator_maint_ext: '\mathrm{on}^{\mathrm{mnt,ext}}' + Generator_ramps_from_previous: '\mathrm{prev}' + Link_committed: '\mathrm{on}^{f,\mathrm{com}}' + Link_com_ext: '\mathrm{on}^{f,\mathrm{com,ext}}' + Link_maint_ext: '\mathrm{on}^{f,\mathrm{mnt,ext}}' + Link_ramps_from_previous: '\mathrm{prev}^{f}' + Process_committed: '\mathrm{on}^{z,\mathrm{com}}' + Process_com_ext: '\mathrm{on}^{z,\mathrm{com,ext}}' + Process_maint_ext: '\mathrm{on}^{z,\mathrm{mnt,ext}}' + Process_ramps_from_previous: '\mathrm{prev}^{z}' + StorageUnit_fix: '\mathrm{on}^{h,\mathrm{fix}}' + StorageUnit_ext: '\mathrm{on}^{h,\mathrm{ext}}' + Line_lossy: '\mathrm{on}^{s,\mathrm{lossy}}' + Transformer_lossy: '\mathrm{on}^{\sigma,\mathrm{lossy}}' Generator_capital_weight: '\mathrm{W}' Link_capital_weight: '\mathrm{W}^{f}' StorageUnit_capital_weight: '\mathrm{W}^{h}' diff --git a/tools/gallery.py b/tools/gallery.py index a6252f75..d6cffc1c 100644 --- a/tools/gallery.py +++ b/tools/gallery.py @@ -104,6 +104,8 @@ def symbols_for(model: Spec, table_path: Path = LIBRARY_SYMBOLS) -> dict[str, An *given.variables, *given.expressions, *given.constraints, + *model.masks, + *given.masks, } return { 'notation': table['notation'], diff --git a/tools/pypsa_split.py b/tools/pypsa_split.py index 17639c0f..e6b5b58f 100644 --- a/tools/pypsa_split.py +++ b/tools/pypsa_split.py @@ -18,7 +18,9 @@ with `adds_to:`, and the fragment reads that hub under `given:`. One fragment reads each hub without adding to it, with its description, so the terms always have a reader, and a new component is one new fragment. The reader of -`total_cost` also sets the objective. +`total_cost` also sets the objective. A mask goes to the topic its name +names, as any declaration does, and a fragment that reads another topic's +mask reads it under `given: masks:`. `merge` then writes each hub as the file does, so `check` is one comparison: the merged fragments and the one file have one canonical form. @@ -46,7 +48,7 @@ from collections.abc import Mapping SOURCE = Path('examples/pypsa.yaml') -SECTIONS = ('dimensions', 'relations', 'parameters', 'variables', 'expressions', 'constraints', 'assumptions') +SECTIONS = ('dimensions', 'relations', 'parameters', 'variables', 'expressions', 'masks', 'constraints', 'assumptions') FRAME = ('dimensions', 'relations') IDENT = re.compile(r'[A-Za-z_][A-Za-z0-9_]*') HEADER = '# SPDX-FileCopyrightText: mathspec Contributors\n#\n# SPDX-License-Identifier: MIT\n' @@ -221,8 +223,12 @@ def keys(self) -> list[Key]: return list(self.blocks) def frames(self) -> dict[str, tuple[str, ...]]: - """The frame of every named expression, read off the one file.""" - return {name: e.dims for name, e in to_spec(self.data).program.expressions.items()} + """The frame of every named expression and mask, read off the one file.""" + program = to_spec(self.data).program + return { + **{name: e.dims for name, e in program.expressions.items()}, + **{name: m.dims for name, m in program.masks.items()}, + } def home(self, name: str) -> str: """The fragment that reads the hub *name* without adding to it, and carries its description.""" @@ -255,7 +261,7 @@ def fragments(model: Model) -> dict[str, str]: given = {model.key(n) for n in read if model.key(n)[0] not in FRAME and model.key(n) not in mine} stated = { **{n: model.data[s][n]['dims'] for s, n in given if s in ('parameters', 'variables')}, - **{n: list(frames[n]) for s, n in given if s == 'expressions'}, + **{n: list(frames[n]) for s, n in given if s in ('expressions', 'masks')}, } frame_reads = read | set().union(*(model.names_in(dims) for dims in stated.values())) frame = {model.key(n) for n in frame_reads if model.key(n)[0] in FRAME} @@ -319,7 +325,7 @@ def _fragment( #: The kinds a fragment reads under `given:`, and the fields of the source #: declaration each restates beside the frame. -GIVEN_KINDS = {'parameters': ('dtype',), 'variables': ('domain',), 'expressions': ()} +GIVEN_KINDS = {'parameters': ('dtype',), 'variables': ('domain',), 'expressions': (), 'masks': ()} def _term_block(block: str, hub: str) -> str: From 9dba511cc329210036c5e0c6e09ef1ff5247f13c Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 2 Oct 2026 22:33:26 +0000 Subject: [PATCH 12/12] a mask takes its frame from its predicate and declares no dims A masks: entry no longer takes dims:. Its frame is the dims its predicate reads, in the order dimensions: declares them, so a use can no longer print an index no quantifier binds. A given: masks: reader states exactly that frame, as a parameter reader states its introducer's. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01CsaSWMofYqJiKmnSiPycV9 --- docs/howto/compose.md | 2 +- docs/reference/language/declarations.md | 13 +++---- docs/reference/language/named.md | 20 +++++------ docs/reference/notation.md | 3 +- docs/reference/typeset.md | 6 ++-- schema/mathspec.schema.json | 19 ++--------- src/mathspec/composition.py | 13 +++---- src/mathspec/lowering.py | 2 +- src/mathspec/program.py | 4 +-- src/mathspec/resolution.py | 11 +----- src/mathspec/spec.py | 23 +++++-------- src/mathspec/validation.py | 1 - tests/test_masks.py | 45 ++++++++++++++++--------- tests/typesetting/golden/model.yaml | 3 +- 14 files changed, 74 insertions(+), 91 deletions(-) diff --git a/docs/howto/compose.md b/docs/howto/compose.md index af1a92f6..b1720195 100644 --- a/docs/howto/compose.md +++ b/docs/howto/compose.md @@ -204,7 +204,7 @@ which each component pins at its own port. | any other declaration | one fragment declares it, and a second is refused | | an entry under `given:` | it is checked against the fragment that introduces the name, then folded into it. Its description fills the declaration where the introducer wrote none | | a given expression | the definition's body carries no dimension the reader's `dims` do not name | -| a given mask | the defining fragment's frame names no dimension the reader's `dims` do not name | +| a given mask | the reader's `dims` name the dimensions the defining fragment's predicate reads, no more and no fewer | | a given entry no fragment introduces | it stays under `given:` until a host model provides it | | an expression with `adds_to:` | the sum it names is the body one fragment defines, if any, followed by every term by its name, in the order of the list. [Terms](../reference/language/declarations.md#terms) gives what is refused | | `objective` | one fragment sets it, and a second is refused. Several fragments contribute to it as terms of a sum the objective reads | diff --git a/docs/reference/language/declarations.md b/docs/reference/language/declarations.md index c05e75e3..b9dc689e 100644 --- a/docs/reference/language/declarations.md +++ b/docs/reference/language/declarations.md @@ -360,10 +360,10 @@ constraints: expression: p - shift(p, along=period, offset=1) <= ramp ``` -| Field | | | -| ------------- | ------------------------------------------- | -------------- | -| `dims` | required. The dimensions the mask runs over | | -| `description` | free text | default `null` | +| Field | | | +| ------------- | ------------------------------------------------------ | -------------- | +| `dims` | required. The dimensions the definer's predicate reads | | +| `description` | free text | default `null` | There is no predicate. A `where` reads the name as data over the frame, true or false at each coordinate. A `where` may read a given mask where it may not @@ -372,8 +372,9 @@ arithmetic the name is refused, as a mask is. The typeset legend lists a given mask under _Given_. [`merge`](../../howto/compose.md#a-library-of-components) folds a given mask -into the mask another fragment defines. The `dims` are an upper bound: the -definer's frame may name no dimension they leave out. +into the mask another fragment defines. The `dims` name the dimensions the +definer's predicate reads, no more and no fewer. A reading that names a +different set is refused, with both frames. ## `constraints` diff --git a/docs/reference/language/named.md b/docs/reference/language/named.md index 4fa811c3..bd90e7f6 100644 --- a/docs/reference/language/named.md +++ b/docs/reference/language/named.md @@ -203,7 +203,6 @@ parameters: p_nom: { dims: [generator] } masks: stands: - dims: [period, generator] where: build_year <= period_year AND period_year < build_year + lifetime description: the generator stands in this period variables: @@ -230,20 +229,21 @@ expression prints =: p_{e,g} - p_{e - 1,g} \le 0.5 \cdot \mathrm{p}^{\mathrm{nom}}_{g} \qquad \forall\, e \in \mathcal{E},\ g \in \mathcal{G} \,:\, \mathrm{stands}_{e,g} \wedge \mathrm{stands}_{e - 1,g} ``` -| Field | | | -| ------------- | ----------------------------------------------- | ------------------- | -| `where` | required. The predicate, in the `where` grammar | | -| `dims` | the frame the mask is read over | the predicate's own | -| `description` | free text | default `null` | +| Field | | | +| ------------- | ----------------------------------------------- | -------------- | +| `where` | required. The predicate, in the `where` grammar | | +| `description` | free text | default `null` | -An entry with no `dims:` and no `description:` may be the bare `where` string. +An entry with no `description:` may be the bare `where` string. - **A bare mask name stands for its predicate.** It does so in a `where:`, a `when:` and a `holds:`, under `NOT`, and inside `count`, `shift` and `at`. The rows are the rows the predicate written out gives. -- **A declared frame bounds the predicate.** The predicate may carry no - dimension the `dims:` do not name, and it may carry fewer: the mask is then - the same along the rest. +- **The predicate gives the frame.** A mask runs over the dimensions its + predicate reads, in the order `dimensions:` declares them, and the symbol + prints with those indices. A mask declares no `dims:`. A use over more + dimensions reads the mask as the same along the rest, and `shift` reads it + back along a dimension of its frame only. - **A mask may read another mask**, and a named expression that reads data only. A cycle is refused with its chain: diff --git a/docs/reference/notation.md b/docs/reference/notation.md index 761cca9b..425d82aa 100644 --- a/docs/reference/notation.md +++ b/docs/reference/notation.md @@ -804,12 +804,11 @@ expressions: #### Named mask -a named predicate with its frame and its words: a use prints the symbol, the predicate prints once +a named predicate with its words: a use prints the symbol over the dims its predicate reads, the predicate prints once ```yaml masks: dispatchable: - dims: [generator] where: "p_max > 0 AND NOT is_flexible" ``` diff --git a/docs/reference/typeset.md b/docs/reference/typeset.md index 82091857..8db03f81 100644 --- a/docs/reference/typeset.md +++ b/docs/reference/typeset.md @@ -83,8 +83,8 @@ dimension, parameter and variable. ## Printing one declaration on its own `typeset_declaration` returns the line the document prints for one named -expression, constraint, assumption or variable, with its quantifier and without -a document, a label, a number or math delimiters: +expression, mask, constraint, assumption, curve or variable, with its +quantifier and without a document, a label, a number or math delimiters: ```python ms.typeset_declaration('spec.yaml', 'spend', 'latex') @@ -106,7 +106,7 @@ A line on its own has no _Definitions_ section beside it, so the plain named expressions it uses are substituted. A cased expression prints by symbol, and a second call with its name prints its block. -A name that is none of the four kinds is refused with the near miss. A name +A name that is none of the six kinds is refused with the near miss. A name declared as two of them, such as a constraint and a variable, is refused too. ## Symbol tables diff --git a/schema/mathspec.schema.json b/schema/mathspec.schema.json index d257c548..0e291877 100644 --- a/schema/mathspec.schema.json +++ b/schema/mathspec.schema.json @@ -392,7 +392,7 @@ }, "GivenMaskBlock": { "additionalProperties": false, - "description": "A mask this file reads and another file defines.\n\nThe frame is all this file states. A where reads the name as data over\nthat frame, true or false at each coordinate, since a mask reads nothing\na solve decides: the predicate is the definer's.", + "description": "A mask this file reads and another file defines.\n\nThe frame is all this file states, and it names the dims the definer's\npredicate reads. A where reads the name as data over that frame, true or false at each coordinate, since a mask reads nothing\na solve decides: the predicate is the definer's.", "properties": { "description": { "anyOf": [ @@ -548,7 +548,7 @@ "anyOf": [ { "additionalProperties": false, - "description": "A named predicate: one where string, read wherever a ``where:``, a ``when:`` or a ``holds:`` names it.\n\nWritten in YAML as a bare where string, or as a mapping once it carries a\n``dims:`` or a ``description:``, and serialised back to whichever form it\nwas written in::\n\n masks:\n committable: \"Generator_committable AND Generator_active\"\n stands:\n dims: [period, generator]\n where: build_year <= period_year AND period_year < build_year + lifetime\n description: the generator stands in this period\n\nA bare mask name in a where string stands for its predicate, inside\n``count``, ``shift`` and ``at`` too. ``dims:`` declares the frame the mask\nis read over; left out, the predicate decides it. A mask is a predicate,\nso it is never a value in an expression.", + "description": "A named predicate: one where string, read wherever a ``where:``, a ``when:`` or a ``holds:`` names it.\n\nWritten in YAML as a bare where string, or as a mapping once it carries a\n``description:``, and serialised back to whichever form it was written in::\n\n masks:\n committable: \"Generator_committable AND Generator_active\"\n stands:\n where: build_year <= period_year AND period_year < build_year + lifetime\n description: the generator stands in this period\n\nA bare mask name in a where string stands for its predicate, inside\n``count``, ``shift`` and ``at`` too. The mask's frame is the dims its\npredicate reads, so it declares none. A mask is a predicate, so it is\nnever a value in an expression.", "properties": { "description": { "anyOf": [ @@ -562,21 +562,6 @@ "default": null, "title": "Description" }, - "dims": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "type": "null" - } - ], - "default": null, - "title": "Dims" - }, "where": { "title": "Where", "type": "string" diff --git a/src/mathspec/composition.py b/src/mathspec/composition.py index b1d5044c..f67efe06 100644 --- a/src/mathspec/composition.py +++ b/src/mathspec/composition.py @@ -45,9 +45,9 @@ reads its own sum through another fragment is refused, both named. * **A given declaration is folded** into the declaration that introduces the name, once the reader is checked to say the same as the introducer or less. - A given expression's body, and a given mask's frame, may carry no dimension - its reader does not state, and a name read as one kind and introduced as - another is refused. A + A given expression's body may carry no dimension its reader does not + state. A given mask states exactly the dims its definer's predicate reads. + A name read as one kind and introduced as another is refused. A reader's description fills a declaration its owner left undescribed. Two fragments that both read a name have to read it over one frame, as a set. What no fragment introduces stays under ``given:`` until a host model @@ -559,8 +559,9 @@ def _fits( """Refuse a reading that says more than the declaration it folds into. A reading states the frame its introducer declares, and every other field - it writes is the introducer's. A given expression and a given mask are the - kinds whose frame may be wider than the composed body: a body over fewer + it writes is the introducer's. A mask declares no frame, so its reader + states the dims the predicate reads. A given expression is the one kind + whose frame may be wider than the composed body: a body over fewer dimensions broadcasts, and the composed load refuses a row it would repeat. """ stated = set(cast('list[str]', _mapping(reading)['dims'])) @@ -569,7 +570,7 @@ def _fits( fits = frame <= stated elif kind == 'masks': frame = set(next(spec.program.masks[key].dims for spec in loaded.values() if key in spec.program.masks)) - fits = frame <= stated + fits = frame == stated else: frame = set(cast('list[str]', _mapping(introduced)['dims'])) fits = frame == stated diff --git a/src/mathspec/lowering.py b/src/mathspec/lowering.py index f2605ff9..b980261d 100644 --- a/src/mathspec/lowering.py +++ b/src/mathspec/lowering.py @@ -136,7 +136,7 @@ def lower(schema: Spec) -> Program: errors.extend(refusals) if named_mask is not None: body = Mask(named_mask.body) - frame = tuple(mdef.dims) if mdef.dims is not None else tuple(d for d in schema.dimensions if d in body.dims) + frame = tuple(d for d in schema.dimensions if d in body.dims) masks[mname] = MaskDeclaration(body, frame, mdef.description) variables = {} diff --git a/src/mathspec/program.py b/src/mathspec/program.py index 241f6ddd..382d033a 100644 --- a/src/mathspec/program.py +++ b/src/mathspec/program.py @@ -735,8 +735,8 @@ class MaskDeclaration: """ where: Mask - #: The frame the mask is read over: the ``dims:`` the entry declares, or - #: the dims its predicate carries. + #: The frame the mask is read over: the dims its predicate carries, in the + #: order ``dimensions:`` declares them. dims: tuple[str, ...] description: str | None = None diff --git a/src/mathspec/resolution.py b/src/mathspec/resolution.py index 738e5124..d544b2ef 100644 --- a/src/mathspec/resolution.py +++ b/src/mathspec/resolution.py @@ -514,9 +514,7 @@ def _mask(name: str, block: MaskBlock, ns: Namespace, errors: list[str]) -> Name """One ``masks:`` entry as the node every use of it holds, or ``None`` once anything in it failed. A predicate the connectives decide is refused, since a name for every row - or for none narrows nothing a where could not say plainly; and a predicate - over a dimension the declared frame leaves out is refused, as an - expression's body is. + or for none narrows nothing a where could not say plainly. """ context = f"Mask '{name}'" body = resolve_where_text(block.where, ns, context, errors) @@ -529,13 +527,6 @@ def _mask(name: str, block: MaskBlock, ns: Namespace, errors: list[str]) -> Name f'Delete it, or write the predicate over the data.' ) return None - carried = Mask(body).dims - if block.dims is not None and (outside := sorted(carried - set(block.dims))): - errors.append( - f'{context}: the predicate reads {outside}, which the dims {block.dims} do not name. A mask ' - f'is read over the frame it declares: add {outside} to its dims, or leave them out of the predicate.' - ) - return None return NamedMask(name, body) diff --git a/src/mathspec/spec.py b/src/mathspec/spec.py index ac7d43c2..91400cec 100644 --- a/src/mathspec/spec.py +++ b/src/mathspec/spec.py @@ -350,8 +350,8 @@ class GivenExpressionBlock(_StrictBlock): class GivenMaskBlock(_StrictBlock): """A mask this file reads and another file defines. - The frame is all this file states. A where reads the name as data over - that frame, true or false at each coordinate, since a mask reads nothing + The frame is all this file states, and it names the dims the definer's + predicate reads. A where reads the name as data over that frame, true or false at each coordinate, since a mask reads nothing a solve decides: the predicate is the definer's. """ @@ -573,27 +573,24 @@ class MaskBlock(_StrictBlock): """A named predicate: one where string, read wherever a ``where:``, a ``when:`` or a ``holds:`` names it. Written in YAML as a bare where string, or as a mapping once it carries a - ``dims:`` or a ``description:``, and serialised back to whichever form it - was written in:: + ``description:``, and serialised back to whichever form it was written in:: masks: committable: "Generator_committable AND Generator_active" stands: - dims: [period, generator] where: build_year <= period_year AND period_year < build_year + lifetime description: the generator stands in this period A bare mask name in a where string stands for its predicate, inside - ``count``, ``shift`` and ``at`` too. ``dims:`` declares the frame the mask - is read over; left out, the predicate decides it. A mask is a predicate, - so it is never a value in an expression. + ``count``, ``shift`` and ``at`` too. The mask's frame is the dims its + predicate reads, so it declares none. A mask is a predicate, so it is + never a value in an expression. """ _label: ClassVar[str] = 'a mask declaration' #: The predicate, in the where grammar. where: str - dims: list[str] | None = None description: str | None = None @model_validator(mode='before') @@ -609,13 +606,9 @@ def __get_pydantic_json_schema__(cls, core_schema: CoreSchema, handler: GetJsonS @model_serializer def _as_written(self) -> str | dict[str, object]: - if self.dims is None and self.description is None: + if self.description is None: return self.where - written: dict[str, object] = {'dims': list(self.dims)} if self.dims is not None else {} - written['where'] = self.where - if self.description is not None: - written['description'] = self.description - return written + return {'where': self.where, 'description': self.description} class AssumptionBlock(_StrictBlock): diff --git a/src/mathspec/validation.py b/src/mathspec/validation.py index 6008a591..4d1c2e66 100644 --- a/src/mathspec/validation.py +++ b/src/mathspec/validation.py @@ -160,7 +160,6 @@ def _frame_dimensions(schema: Spec) -> Iterator[str]: *(('Given mask', name, g.dims) for name, g in schema.given.masks.items()), *(('Constraint', name, c.dims) for name, c in schema.constraints.items()), *(('Named expression', name, e.dims or []) for name, e in schema.expressions.items()), - *(('Mask', name, m.dims or []) for name, m in schema.masks.items()), ] for kind, name, dims in frames: yield from (undeclared_dimension(kind, name, d) for d in dims if d not in schema.dimensions) diff --git a/tests/test_masks.py b/tests/test_masks.py index 4181718c..14d466ed 100644 --- a/tests/test_masks.py +++ b/tests/test_masks.py @@ -35,7 +35,7 @@ 'period_year': {'dims': ['period']}, 'p_nom': {'dims': ['generator']}, }, - 'masks': {'stands': {'dims': FRAME, 'where': STANDS, 'description': 'the unit stands in this period'}}, + 'masks': {'stands': {'where': STANDS, 'description': 'the unit stands in this period'}}, 'variables': {'p': {'dims': FRAME, 'where': 'stands', 'bounds': {'lower': 0}}}, 'constraints': {'cap': {'dims': FRAME, 'where': 'stands', 'expression': 'p <= p_nom'}}, 'objective': {'sense': 'minimize', 'expression': 'sum(p, over=[period, generator])'}, @@ -99,7 +99,23 @@ def test_a_mask_is_read_wherever_a_where_string_is(patch): def test_the_one_line_form_writes_back_as_written(): spec = to_spec(varied(CORE, **{'masks.stands': STANDS})) - assert spec.to_dict()['masks'] == {'stands': STANDS}, 'a mask with no frame and no words is one where string' + assert spec.to_dict()['masks'] == {'stands': STANDS}, 'a mask with no words is one where string' + + +def test_a_mask_declares_no_frame(): + """A declared frame wider than the predicate printed a use over an index no quantifier binds. + + ``young`` over ``[period, generator]`` read only ``lifetime``. A use over + ``[generator]`` loaded, and every format printed ``young`` with a period + index under a quantifier over generators alone. The predicate gives a mask + its frame, so the closed schema now refuses ``dims:`` on a mask. + """ + young = { + 'masks.young': {'dims': FRAME, 'where': 'lifetime > 10'}, + 'constraints.total': {'dims': ['generator'], 'where': 'young', 'expression': 'sum(p, over=period) <= p_nom'}, + } + with pytest.raises(LanguageError, match=r"masks\.young: unknown key 'dims' in a mask declaration"): + to_spec(varied(CORE, **young)) def test_a_variable_that_asks_through_a_mask_whether_it_exists_is_refused(): @@ -121,11 +137,6 @@ def test_a_mask_may_ask_whether_another_variable_exists(): pytest.param({'masks': {'a': 'a'}}, 'circular mask reference: a -> a', id='a-mask-reading-itself'), pytest.param({'masks': {'a': 'True'}}, 'folds to true, so the mask admits every row', id='always-true'), pytest.param({'masks': {'a': 'NOT True'}}, 'folds to false, so the mask admits no row', id='always-false'), - pytest.param( - {'masks': {'a': {'dims': ['h'], 'where': 'c > 0'}}}, - "the predicate reads ['g'], which the dims ['h'] do not name", - id='a-predicate-wider-than-its-frame', - ), pytest.param( {'masks': {'a': 'c > 0'}, 'expressions.e': 'c * a'}, "'a' is a mask, which is true or false where it is read, and not a number", @@ -233,16 +244,20 @@ def test_a_reading_the_definer_does_not_answer_is_refused(reader, says): assert says in str(raised.value) -def test_a_reader_may_state_a_frame_wider_than_the_mask_s(): - """A mask over fewer dimensions holds alike along the rest, as a given expression's narrower body does. +def test_a_reader_that_states_a_frame_wider_than_the_mask_s_is_refused(): + """A reader stated a superset of the predicate's dims, and the merge took it. - The one-line definer has no ``dims:`` in its block, so the frame is read - off the definer's program rather than off the block it wrote. + A mask's frame is the dims its predicate reads, which no file chooses, so + a wider reading only defers the failure to the composed load. """ definer = varied(CORE, **{'masks.stands': 'lifetime > 0'}) - program = merge([definer, varied(RAMPING, **{'constraints.ramp_up.where': 'stands'})]).program - assert program.masks['stands'].dims == ('generator',), 'the definer decides the frame' - assert not program.given + reader = varied(RAMPING, **{'constraints.ramp_up.where': 'stands'}) + with pytest.raises(LanguageError) as raised: + merge([definer, reader]) + assert "reads the given mask 'stands' as {'dims': ['period', 'generator']}" in str(raised.value), ( + 'the refusal names the frame the reader states' + ) + assert "introduces it over ['generator']" in str(raised.value), "and the frame the definer's predicate reads" def test_two_fragments_that_define_one_mask_collide(): @@ -254,7 +269,7 @@ def test_two_fragments_that_define_one_mask_collide(): def test_a_patch_rewrites_a_mask_s_predicate(): laid = override(CORE, [{'masks': {'stands': {'where': 'lifetime > 0'}}}]) assert laid.masks['stands'].where == 'lifetime > 0' - assert laid.masks['stands'].dims == FRAME, 'a patch says only what it changes' + assert laid.masks['stands'].description == 'the unit stands in this period', 'a patch says only what it changes' # --------------------------------------------------------------------------- diff --git a/tests/typesetting/golden/model.yaml b/tests/typesetting/golden/model.yaml index 74f8306b..35ac5a36 100644 --- a/tests/typesetting/golden/model.yaml +++ b/tests/typesetting/golden/model.yaml @@ -160,8 +160,7 @@ expressions: otherwise: 0 masks: - dispatchable: # a named predicate with its frame and its words: a use prints the symbol, the predicate prints once - dims: [generator] + dispatchable: # a named predicate with its words: a use prints the symbol over the dims its predicate reads, the predicate prints once where: "p_max > 0 AND NOT is_flexible" description: a unit that dispatches and cannot be turned down priced_dispatch: "dispatchable AND spend_cap > 0" # the one-line form, reading another mask and a data-only entry