From 1c468934c4f8b2d71eec4415599f6200c24f498d Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 28 Sep 2026 21:55:02 +0000 Subject: [PATCH 1/9] fix(advice): an objective that reads a given expression or an empty sum gets advice rather than a KeyError MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The unboundedness pass looked every column the objective reads up among the declared variables, and skipped only a given variable. A given expression and an empty sum are columns the program reads too, so `advice` raised KeyError on any spec whose objective names one — every composed cost that adds a term to a scalar sum is such a spec. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AvK6QNNFZHJcRzRf3sBbWe --- src/mathspec/boundedness.py | 6 ++++-- tests/test_boundedness.py | 8 ++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/mathspec/boundedness.py b/src/mathspec/boundedness.py index c95d69eb..7ccf601e 100644 --- a/src/mathspec/boundedness.py +++ b/src/mathspec/boundedness.py @@ -59,7 +59,9 @@ def unbounded_notes(program: Program) -> list[Advice]: Returns: One note per variable that is unbounded on the side its objective term - improves toward and named by no constraint. + improves toward and named by no constraint. A column the program reads + and does not declare, a given one or an empty sum, has its bounds + where it is built, so it gets none. """ if program.objective is None: return [] @@ -76,7 +78,7 @@ def unbounded_notes(program: Program) -> list[Advice]: minimize = program.objective.sense == 'minimize' notes: list[Advice] = [] for vname, sign in signs.items(): - if sign is None or vname in constrained or vname in program.given.variables: + if sign is None or vname in constrained or vname not in program.variables: continue side: BoundSide = 'lower' if minimize == (sign == '+') else 'upper' if _is_open(program.variables[vname], side): diff --git a/tests/test_boundedness.py b/tests/test_boundedness.py index 053d3d7c..1d456cc9 100644 --- a/tests/test_boundedness.py +++ b/tests/test_boundedness.py @@ -119,6 +119,14 @@ def test_a_named_constant_coefficient_carries_its_sign(objective, side): {'objective.expression': '-sum(v, over=g)', 'variables.v.bounds': {'upper': 10}}, id='bounded-on-the-improving-side-running-up', ), + pytest.param( + {'given': {'expressions': {'e': {'dims': ['g']}}}, 'objective.expression': 'sum(e, over=g)'}, + id='a-given-expression-is-bounded-by-whoever-defines-it', + ), + pytest.param( + {'expressions.e': {'dims': ['g'], 'empty': True}, 'objective.expression': 'sum(e, over=g)'}, + id='an-empty-sum-is-bounded-by-its-terms', + ), ], ) def test_nothing_is_claimed_where_the_file_does_not_decide_it(patch): From 8b00ac7616ed430023f6aed6b3ea63163739a265 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 28 Sep 2026 21:55:20 +0000 Subject: [PATCH 2/9] docs(changelog): name #767 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AvK6QNNFZHJcRzRf3sBbWe --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76f0b518..2845aac4 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(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)) From a4f3e0a4558534c32beb12dc7b3dd331d00201ba Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 28 Sep 2026 22:09:53 +0000 Subject: [PATCH 3/9] fix(language): a given declaration that leaves out its domain or dtype folds into an introducer that sets one merge read each fragment's given block back through to_dict, which writes every default. A reading with no domain claimed continuous, and one with no dtype claimed float, so the frame alone was refused against an integer or binary column and an integer parameter. The composition docs promise the reader may say less; it now hands the fold only the fields the file wrote. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AvK6QNNFZHJcRzRf3sBbWe --- src/mathspec/composition.py | 8 ++++++-- tests/test_given.py | 27 +++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/mathspec/composition.py b/src/mathspec/composition.py index 95b477dc..26c341ca 100644 --- a/src/mathspec/composition.py +++ b/src/mathspec/composition.py @@ -157,7 +157,7 @@ def merge(fragments: Sequence[Source], description: str | None = None) -> Spec: for section in SHARED_SECTIONS: if agreed := _agreed(read, section, _singular(section), 'give one of them a name of its own'): merged[section] = agreed - asked = {name: _readings(sections) for name, sections in read.items()} + asked = {name: _readings({'given': spec.given.model_dump(exclude_unset=True)}) for name, spec in loaded.items()} readings = { kind: _agreed(asked, kind, label, 'read it over one frame', claims=_reading_claims) for kind, label in GIVEN_KINDS.items() @@ -246,7 +246,11 @@ def _reading_claims(block: object) -> object: def _readings(sections: Mapping[str, object]) -> dict[str, object]: - """The ``given:`` block of one fragment, each entry without its term: what the fragment reads, apart from what it adds.""" + """The ``given:`` block of one fragment, each entry without its term: what the fragment reads, apart from what it adds. + + ``merge`` hands it the fields the fragment wrote. A field left out says + less, and a default filled in would claim a value against the introducer. + """ return { kind: {key: {f: v for f, v in _mapping(entry).items() if f != 'term'} for key, entry in _mapping(group).items()} for kind, group in _mapping(sections.get('given')).items() diff --git a/tests/test_given.py b/tests/test_given.py index dc0cdf29..a627b292 100644 --- a/tests/test_given.py +++ b/tests/test_given.py @@ -168,6 +168,33 @@ def test_a_given_declaration_may_say_less_than_the_introducer(reads): assert composed.variables['flow'].bounds.upper == 1000 +#: A unit count one file introduces as an integer column over an integer +#: parameter, and another reads by its frame alone. +UNITS = { + 'dimensions': {'generator': {'dtype': 'str'}}, + 'parameters': {'units_max': {'dims': ['generator'], 'dtype': 'int'}}, + 'variables': {'units': {'dims': ['generator'], 'domain': 'integer', 'bounds': {'lower': 0, 'upper': 'units_max'}}}, +} +READS_UNITS = { + 'dimensions': {'generator': {'dtype': 'str'}}, + 'given': {'variables': {'units': {'dims': ['generator']}}, 'parameters': {'units_max': {'dims': ['generator']}}}, + 'constraints': {'at_least_one': {'dims': ['generator'], 'expression': 'units >= 1'}}, +} + + +def test_a_reader_that_leaves_a_field_out_says_less_whatever_the_introducer_sets_it_to(): + """The frame alone folded only where the introducer kept the default. + + `merge` read each fragment back through `to_dict`, which writes every + default, so a reading with no `domain` claimed `continuous` and one with no + `dtype` claimed `float`, and both were refused against an integer + introducer as if they said something else. + """ + composed = merge([UNITS, READS_UNITS]) + assert composed.variables['units'].domain == 'integer', "the introducer's domain is the one that survives" + assert composed.parameters['units_max'].dtype == 'int', "the introducer's dtype is the one that survives" + + #: A fragment that reads `flow` over `port` alone, and loads so: it only #: declares what it reads. PORTS_ONLY = { From 4d66c790051f4a1b83f8ac0ed6899fbe5c7f8850 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 28 Sep 2026 22:10:11 +0000 Subject: [PATCH 4/9] docs(changelog): name #768 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AvK6QNNFZHJcRzRf3sBbWe --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76f0b518..ef8fcc42 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 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(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)) From 9f49fb766e5b650745bec51ff63237da83aa5159 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 28 Sep 2026 22:20:41 +0000 Subject: [PATCH 5/9] chore(tools): every spec renders to a LaTeX document of its own, where two share a file name render_tex named each document after the spec's stem, so the library's and PyPSA's generator.yaml and load.yaml each wrote one file and compile-tex compiled one of each pair. A document is now named after the spec's path. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AvK6QNNFZHJcRzRf3sBbWe --- tests/test_render_tex.py | 21 +++++++++++++++++++++ tools/render_tex.py | 12 +++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 tests/test_render_tex.py diff --git a/tests/test_render_tex.py b/tests/test_render_tex.py new file mode 100644 index 00000000..c405b0a2 --- /dev/null +++ b/tests/test_render_tex.py @@ -0,0 +1,21 @@ +# SPDX-FileCopyrightText: mathspec Contributors +# +# SPDX-License-Identifier: MIT + +"""The render half of the LaTeX gate: every spec in the tree becomes a document of its own.""" + +from __future__ import annotations + +from tools import render_tex + + +def test_every_spec_renders_to_a_document_of_its_own(tmp_path): + """Two specs that share a file name wrote one document, and the second overwrote the first. + + The output was named after the file's stem alone, so + `examples/library/generator.yaml` and `examples/pypsa/generator.yaml` both + wrote `generator.tex`, and `compile-tex` compiled one of the two. + """ + assert render_tex.main([str(tmp_path)]) == 0 + written = sorted(path.name for path in tmp_path.glob('*.tex')) + assert len(written) == len(render_tex.models()), 'one document per spec, none overwritten by a namesake' diff --git a/tools/render_tex.py b/tools/render_tex.py index aae1cfbb..c882cea3 100644 --- a/tools/render_tex.py +++ b/tools/render_tex.py @@ -35,6 +35,16 @@ def models() -> list[Path]: return sorted(path for path in found if not excluded.intersection(path.parents)) +def document_name(model: Path) -> str: + """The file name *model* renders to: its path under the repository, one part to a hyphen. + + Two specs in different folders may share a stem, and one document each is + what the gate compiles. + """ + parts = model.relative_to(ROOT).with_suffix('').parts + return '-'.join(parts[1:] if parts[0] == 'examples' else parts) + '.tex' + + def main(argv: list[str] | None = None) -> int: argv = sys.argv[1:] if argv is None else argv if len(argv) != 1: @@ -50,7 +60,7 @@ def main(argv: list[str] | None = None) -> int: return 1 for model in found: - args = ['latex', str(model), '--standalone', '-o', str(out / f'{model.stem}.tex')] + args = ['latex', str(model), '--standalone', '-o', str(out / document_name(model))] if symbols := sidecar_for(model): args += ['--symbols', str(symbols)] render(args) From 2d7ffce23b68c141e2c2cfc56d75d2a1dc96b4ed Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 28 Sep 2026 22:22:51 +0000 Subject: [PATCH 6/9] docs(calliope): all of calliope's math is a set of fragments that merge, with its modes laid over them as patches Calliope v0.7.0's base math is 14 topic fragments under examples/calliope/, merged into one spec with nothing left under given. Its MILP math, and each of its 13 user-math examples and the urban-scale model's extra math, is an extension fragment; where Calliope restates system_balance, cost_investment, cost_operation_fixed or the objective to add a term, the extension adds the term to a sum and no base file changes. The MILP edits, operate mode, SPORES, inter-cluster storage and the two rewrites of balance_conversion are variants that override lays over the composition. Each fragment and variant has a gallery page, the index lists the sums and the fragments, and the port record lists every Calliope block with its status and the nine gaps where mathspec needs more than one block for one. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AvK6QNNFZHJcRzRf3sBbWe --- .claude/skills/docs-writing/SKILL.md | 4 +- .prettierignore | 5 + docs/examples/calliope/area.md | 177 ++++++ docs/examples/calliope/balance.md | 90 +++ docs/examples/calliope/conversion.md | 64 ++ docs/examples/calliope/cost.md | 195 ++++++ docs/examples/calliope/demand.md | 181 ++++++ docs/examples/calliope/export.md | 156 +++++ .../extensions/annual_energy_balance.md | 168 +++++ docs/examples/calliope/extensions/chp_htp.md | 241 ++++++++ .../demand_share_per_timestep_decision.md | 193 ++++++ .../examples/calliope/extensions/fuel_dist.md | 179 ++++++ .../calliope/extensions/max_time_varying.md | 73 +++ docs/examples/calliope/extensions/milp.md | 581 ++++++++++++++++++ .../extensions/monthly_peak_flow_charge.md | 140 +++++ .../calliope/extensions/net_import_share.md | 192 ++++++ .../extensions/piecewise_linear_costs.md | 127 ++++ .../extensions/piecewise_linear_efficiency.md | 85 +++ .../extensions/share_all_timesteps.md | 144 +++++ .../calliope/extensions/share_per_timestep.md | 148 +++++ .../extensions/sos2_piecewise_linear_costs.md | 166 +++++ .../extensions/uptime_downtime_limits.md | 147 +++++ .../calliope/extensions/urban_scale_chp.md | 146 +++++ docs/examples/calliope/feasibility.md | 128 ++++ docs/examples/calliope/flows.md | 489 +++++++++++++++ docs/examples/calliope/index.md | 143 +++++ docs/examples/calliope/port.md | 194 ++++++ docs/examples/calliope/reporting.md | 120 ++++ docs/examples/calliope/settings.md | 107 ++++ docs/examples/calliope/storage.md | 321 ++++++++++ docs/examples/calliope/supply.md | 338 ++++++++++ docs/examples/calliope/supply_storage.md | 73 +++ docs/examples/calliope/transmission.md | 132 ++++ docs/examples/calliope/variants/chp_htp.md | 33 + docs/examples/calliope/variants/milp.md | 111 ++++ docs/examples/calliope/variants/operate.md | 87 +++ .../calliope/variants/operate_milp.md | 50 ++ docs/examples/calliope/variants/spores.md | 79 +++ .../variants/storage_inter_cluster.md | 237 +++++++ .../calliope/variants/urban_scale_chp.md | 32 + docs/examples/index.md | 2 + examples/calliope/area.yaml | 81 +++ examples/calliope/balance.yaml | 43 ++ examples/calliope/conversion.yaml | 29 + examples/calliope/cost.yaml | 95 +++ examples/calliope/demand.yaml | 89 +++ examples/calliope/export.yaml | 65 ++ .../extensions/annual_energy_balance.yaml | 85 +++ examples/calliope/extensions/chp_htp.yaml | 122 ++++ .../demand_share_per_timestep_decision.yaml | 96 +++ examples/calliope/extensions/fuel_dist.yaml | 79 +++ .../calliope/extensions/max_time_varying.yaml | 33 + examples/calliope/extensions/milp.yaml | 278 +++++++++ .../extensions/monthly_peak_flow_charge.yaml | 66 ++ .../calliope/extensions/net_import_share.yaml | 97 +++ .../extensions/piecewise_linear_costs.yaml | 55 ++ .../piecewise_linear_efficiency.yaml | 43 ++ .../extensions/share_all_timesteps.yaml | 77 +++ .../extensions/share_per_timestep.yaml | 81 +++ .../sos2_piecewise_linear_costs.yaml | 68 ++ .../extensions/uptime_downtime_limits.yaml | 69 +++ .../calliope/extensions/urban_scale_chp.yaml | 70 +++ examples/calliope/feasibility.yaml | 50 ++ examples/calliope/flows.yaml | 259 ++++++++ examples/calliope/reporting.yaml | 48 ++ examples/calliope/settings.yaml | 55 ++ examples/calliope/storage.yaml | 167 +++++ examples/calliope/supply.yaml | 159 +++++ examples/calliope/supply_storage.yaml | 35 ++ examples/calliope/transmission.yaml | 69 +++ examples/calliope/variants/chp_htp.yaml | 11 + examples/calliope/variants/milp.yaml | 47 ++ examples/calliope/variants/operate.yaml | 57 ++ examples/calliope/variants/operate_milp.yaml | 32 + examples/calliope/variants/spores.yaml | 39 ++ .../variants/storage_inter_cluster.yaml | 141 +++++ .../calliope/variants/urban_scale_chp.yaml | 10 + examples/symbols/calliope.yaml | 20 + mkdocs.yml | 43 +- tests/test_calliope_example.py | 145 +++++ tools/gallery.py | 117 +++- tools/render_tex.py | 4 +- 82 files changed, 9488 insertions(+), 19 deletions(-) create mode 100644 docs/examples/calliope/area.md create mode 100644 docs/examples/calliope/balance.md create mode 100644 docs/examples/calliope/conversion.md create mode 100644 docs/examples/calliope/cost.md create mode 100644 docs/examples/calliope/demand.md create mode 100644 docs/examples/calliope/export.md create mode 100644 docs/examples/calliope/extensions/annual_energy_balance.md create mode 100644 docs/examples/calliope/extensions/chp_htp.md create mode 100644 docs/examples/calliope/extensions/demand_share_per_timestep_decision.md create mode 100644 docs/examples/calliope/extensions/fuel_dist.md create mode 100644 docs/examples/calliope/extensions/max_time_varying.md create mode 100644 docs/examples/calliope/extensions/milp.md create mode 100644 docs/examples/calliope/extensions/monthly_peak_flow_charge.md create mode 100644 docs/examples/calliope/extensions/net_import_share.md create mode 100644 docs/examples/calliope/extensions/piecewise_linear_costs.md create mode 100644 docs/examples/calliope/extensions/piecewise_linear_efficiency.md create mode 100644 docs/examples/calliope/extensions/share_all_timesteps.md create mode 100644 docs/examples/calliope/extensions/share_per_timestep.md create mode 100644 docs/examples/calliope/extensions/sos2_piecewise_linear_costs.md create mode 100644 docs/examples/calliope/extensions/uptime_downtime_limits.md create mode 100644 docs/examples/calliope/extensions/urban_scale_chp.md create mode 100644 docs/examples/calliope/feasibility.md create mode 100644 docs/examples/calliope/flows.md create mode 100644 docs/examples/calliope/index.md create mode 100644 docs/examples/calliope/port.md create mode 100644 docs/examples/calliope/reporting.md create mode 100644 docs/examples/calliope/settings.md create mode 100644 docs/examples/calliope/storage.md create mode 100644 docs/examples/calliope/supply.md create mode 100644 docs/examples/calliope/supply_storage.md create mode 100644 docs/examples/calliope/transmission.md create mode 100644 docs/examples/calliope/variants/chp_htp.md create mode 100644 docs/examples/calliope/variants/milp.md create mode 100644 docs/examples/calliope/variants/operate.md create mode 100644 docs/examples/calliope/variants/operate_milp.md create mode 100644 docs/examples/calliope/variants/spores.md create mode 100644 docs/examples/calliope/variants/storage_inter_cluster.md create mode 100644 docs/examples/calliope/variants/urban_scale_chp.md create mode 100644 examples/calliope/area.yaml create mode 100644 examples/calliope/balance.yaml create mode 100644 examples/calliope/conversion.yaml create mode 100644 examples/calliope/cost.yaml create mode 100644 examples/calliope/demand.yaml create mode 100644 examples/calliope/export.yaml create mode 100644 examples/calliope/extensions/annual_energy_balance.yaml create mode 100644 examples/calliope/extensions/chp_htp.yaml create mode 100644 examples/calliope/extensions/demand_share_per_timestep_decision.yaml create mode 100644 examples/calliope/extensions/fuel_dist.yaml create mode 100644 examples/calliope/extensions/max_time_varying.yaml create mode 100644 examples/calliope/extensions/milp.yaml create mode 100644 examples/calliope/extensions/monthly_peak_flow_charge.yaml create mode 100644 examples/calliope/extensions/net_import_share.yaml create mode 100644 examples/calliope/extensions/piecewise_linear_costs.yaml create mode 100644 examples/calliope/extensions/piecewise_linear_efficiency.yaml create mode 100644 examples/calliope/extensions/share_all_timesteps.yaml create mode 100644 examples/calliope/extensions/share_per_timestep.yaml create mode 100644 examples/calliope/extensions/sos2_piecewise_linear_costs.yaml create mode 100644 examples/calliope/extensions/uptime_downtime_limits.yaml create mode 100644 examples/calliope/extensions/urban_scale_chp.yaml create mode 100644 examples/calliope/feasibility.yaml create mode 100644 examples/calliope/flows.yaml create mode 100644 examples/calliope/reporting.yaml create mode 100644 examples/calliope/settings.yaml create mode 100644 examples/calliope/storage.yaml create mode 100644 examples/calliope/supply.yaml create mode 100644 examples/calliope/supply_storage.yaml create mode 100644 examples/calliope/transmission.yaml create mode 100644 examples/calliope/variants/chp_htp.yaml create mode 100644 examples/calliope/variants/milp.yaml create mode 100644 examples/calliope/variants/operate.yaml create mode 100644 examples/calliope/variants/operate_milp.yaml create mode 100644 examples/calliope/variants/spores.yaml create mode 100644 examples/calliope/variants/storage_inter_cluster.yaml create mode 100644 examples/calliope/variants/urban_scale_chp.yaml create mode 100644 examples/symbols/calliope.yaml create mode 100644 tests/test_calliope_example.py diff --git a/.claude/skills/docs-writing/SKILL.md b/.claude/skills/docs-writing/SKILL.md index c30da12e..7d531ebb 100644 --- a/.claude/skills/docs-writing/SKILL.md +++ b/.claude/skills/docs-writing/SKILL.md @@ -66,8 +66,8 @@ not need, in three groups: and `Program`: an engine such as specsolve, a renderer, a checker. - **Contributing** is for someone who changes mathspec itself. - **Proofs of concept** holds the notation page, which renders the typesetting - test spec, and the PyPSA pages. The PyPSA pages stay in `docs/examples/`, - where `tools/gallery.py` writes them. + test spec, and the PyPSA and Calliope pages. Those stay in + `docs/examples/`, where `tools/gallery.py` writes them. A page in Development keeps the folder of its kind. diff --git a/.prettierignore b/.prettierignore index 9585e8fe..93c63914 100644 --- a/.prettierignore +++ b/.prettierignore @@ -25,6 +25,11 @@ docs/examples/library/generator.md docs/examples/library/load.md docs/examples/library/composed.md docs/examples/pypsa/*.md +docs/examples/calliope/*.md +docs/examples/calliope/extensions/*.md +docs/examples/calliope/variants/*.md +# The port record is written by hand, so prettier keeps its tables. +!docs/examples/calliope/port.md # The PyPSA reference scripts write this file; prettier would reformat what # they stamp, and the two would fight over it exactly as above. diff --git a/docs/examples/calliope/area.md b/docs/examples/calliope/area.md new file mode 100644 index 00000000..b3821296 --- /dev/null +++ b/docs/examples/calliope/area.md @@ -0,0 +1,177 @@ + + +# Area + +One of the base fragments of [Calliope in fragments](index.md). Area use, its limits, its tie to flow capacity, and its cost. + + +```yaml +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + costs: + description: Calliope's `costs` — cost classes, such as monetary and CO2 + +parameters: + area_use_min: + description: "`area_use_min` — least area use. Calliope's default is 0, and data prep fills it" + dims: [nodes, techs] + area_use_max: + description: "`area_use_max` — most area use. Calliope's default is `.inf`, and data prep fills it" + dims: [nodes, techs] + area_use_per_flow_cap: + description: "`area_use_per_flow_cap` — area use per unit of flow capacity; given only where set" + dims: [nodes, techs] + available_area: + description: "`available_area` — the area every technology at a node may use; given only where set" + dims: [nodes] + cost_area_use: + description: "`cost_area_use` — the cost of one unit of area use" + dims: [nodes, techs, costs] + +variables: + area_use: + description: >- + `area_use` — the area a technology uses. Calliope builds it where + `area_use_min` is given at all; the least area use is data here, so + it is built where that is above zero + dims: [nodes, techs] + where: area_use_min > 0 OR area_use_max OR area_use_per_flow_cap OR sink_unit == per_area OR source_unit == per_area + bounds: { lower: area_use_min, upper: area_use_max } + absence: zero + +expressions: + cost_investment_area_use: + description: "`cost_investment_area_use` — the investment cost of area use" + expression: cost_area_use * area_use + +given: + parameters: + flow_cap_max: { dims: [nodes, techs] } + sink_unit: { dims: [nodes, techs], dtype: str } + source_unit: { dims: [nodes, techs], dtype: str } + variables: + flow_cap: { dims: [nodes, techs, carriers] } + expressions: + cost_investment: { dims: [nodes, techs, costs], term: cost_investment_area_use } + +constraints: + force_zero_area_use: + description: "`force_zero_area_use` — a technology with no flow capacity uses no area" + dims: [nodes, techs] + where: area_use AND flow_cap_max == 0 + expression: area_use == 0 + area_use_per_flow_capacity: + description: "`area_use_per_flow_capacity` — area use follows flow capacity, where set" + dims: [nodes, techs, carriers] + where: flow_cap AND area_use AND area_use_per_flow_cap + expression: area_use == flow_cap * area_use_per_flow_cap + area_use_capacity_per_loc: + description: >- + `area_use_capacity_per_loc` — the technologies at a node use at most + its available area. Calliope's `where: area_use` over a node reads as + any technology there using area + dims: [nodes] + where: count(area_use, over=techs) >= 1 AND available_area + expression: sum(area_use, over=techs) <= available_area + +assumptions: + unbounded_area_use_cost: + description: Calliope's `unbounded_area_use_cost` — a negative area cost needs a finite maximum + holds: NOT cost_area_use < 0 OR area_use_max +``` + +#### Sets + +| Symbol | Meaning | +|---|---| +| $`\mathcal{N}`$ | index $`n`$ — `nodes` — Calliope's `nodes` — the places technologies stand at | +| $`\mathcal{I}`$ | index $`i`$ — `techs` — Calliope's `techs` — technologies | +| $`\mathcal{C}`$ | index $`c`$ — `carriers` — Calliope's `carriers` — energy and commodity carriers | +| $`\mathcal{K}`$ | index $`k`$ — `costs` — Calliope's `costs` — cost classes, such as monetary and CO2 | + +#### Parameters + +| Symbol | Meaning | +|---|---| +| $`\mathrm{area\_use\_min}`$ | `area_use_min` over $`\mathcal{N} \times \mathcal{I}`$ — `area_use_min` — least area use. Calliope's default is 0, and data prep fills it | +| $`\mathrm{area\_use\_max}`$ | `area_use_max` over $`\mathcal{N} \times \mathcal{I}`$ — `area_use_max` — most area use. Calliope's default is `.inf`, and data prep fills it | +| $`\mathrm{area\_use\_per\_flow\_cap}`$ | `area_use_per_flow_cap` over $`\mathcal{N} \times \mathcal{I}`$ — `area_use_per_flow_cap` — area use per unit of flow capacity; given only where set | +| $`\mathrm{available\_area}`$ | `available_area` over $`\mathcal{N}`$ — `available_area` — the area every technology at a node may use; given only where set | +| $`\mathrm{cost\_area\_use}`$ | `cost_area_use` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K}`$ — `cost_area_use` — the cost of one unit of area use | + +#### Variables + +| Symbol | Meaning | +|---|---| +| $`\mathit{area\_use}`$ | `area_use` over $`\mathcal{N} \times \mathcal{I}`$ — `area_use` — the area a technology uses. Calliope builds it where `area_use_min` is given at all; the least area use is data here, so it is built where that is above zero | + +#### Given + +| Symbol | Meaning | +|---|---| +| $`\mathrm{flow\_cap\_max}`$ | `flow_cap_max` over $`\mathcal{N} \times \mathcal{I}`$, data another file declares | +| $`\mathrm{sink\_unit}`$ | `sink_unit` over $`\mathcal{N} \times \mathcal{I}`$, data another file declares | +| $`\mathrm{source\_unit}`$ | `source_unit` over $`\mathcal{N} \times \mathcal{I}`$, data another file declares | +| $`\mathit{flow\_cap}`$ | `flow_cap` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C}`$ | +| $`\mathit{cost\_investment}`$ | `cost_investment` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K}`$, an expression this file adds `cost_investment_area_use` to | + +#### Definitions + +| Symbol | Meaning | +|---|---| +| $`\mathit{cost\_investment\_area\_use}`$ | `cost_investment_area_use` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K}`$ — `cost_investment_area_use` — the investment cost of area use | + +Upright is what the data supplies — a parameter such as $`\mathrm{area\_use\_min}`$, a coordinate map, a label — and italic is what the solver chooses, such as $`\mathit{area\_use}`$. An index is italic too, being what a quantifier chooses, and a set is script. + +#### Subject to + +**`force_zero_area_use`** + +```math +\mathit{area\_use}_{n,i} = 0 \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I} \,:\, \mathit{area\_use}_{n,i} \text{ exists} \wedge \mathrm{flow\_cap\_max}_{n,i} = 0 +``` + +**`area_use_per_flow_capacity`** + +```math +\mathit{area\_use}_{n,i} = \mathit{flow\_cap}_{n,i,c} \cdot \mathrm{area\_use\_per\_flow\_cap}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C} \,:\, \mathit{flow\_cap}_{n,i,c} \text{ exists} \wedge \mathit{area\_use}_{n,i} \text{ exists} \wedge \mathrm{area\_use\_per\_flow\_cap}_{n,i} \text{ is defined} +``` + +**`area_use_capacity_per_loc`** + +```math +\sum_{i \in \mathcal{I}} \mathit{area\_use}_{n,i} \le \mathrm{available\_area}_{n} \qquad \forall\, n \in \mathcal{N} \,:\, \lvert \{ i \in \mathcal{I} \,:\, \mathit{area\_use}_{n,i} \text{ exists} \} \rvert \ge 1 \wedge \mathrm{available\_area}_{n} \text{ is defined} +``` + +#### Definitions + +**`cost_investment_area_use`** + +```math +\mathit{cost\_investment\_area\_use}_{n,i,k} = \mathrm{cost\_area\_use}_{n,i,k} \cdot \mathit{area\_use}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ k \in \mathcal{K} +``` + +#### Variable domains + +**`area_use`** + +```math +\mathrm{area\_use\_min}_{n,i} \le \mathit{area\_use}_{n,i} \le \mathrm{area\_use\_max}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I} \,:\, \mathrm{area\_use\_min}_{n,i} > 0 \vee \mathrm{area\_use\_max}_{n,i} \text{ is defined} \vee \mathrm{area\_use\_per\_flow\_cap}_{n,i} \text{ is defined} \vee \mathrm{sink\_unit}_{n,i} = \text{'}\mathrm{per\_area}\text{'} \vee \mathrm{source\_unit}_{n,i} = \text{'}\mathrm{per\_area}\text{'} +``` + +#### Assumptions + +**`unbounded_area_use_cost`** + +```math +\neg \left( \mathrm{cost\_area\_use}_{n,i,k} < 0 \right) \vee \mathrm{area\_use\_max}_{n,i} \text{ is defined} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ k \in \mathcal{K} +``` + diff --git a/docs/examples/calliope/balance.md b/docs/examples/calliope/balance.md new file mode 100644 index 00000000..809146d3 --- /dev/null +++ b/docs/examples/calliope/balance.md @@ -0,0 +1,90 @@ + + +# The balance + +One of the base fragments of [Calliope in fragments](index.md). Calliope's `system_balance`: at each node, in each time step, a carrier's production equals its consumption. The file declares the sum `carrier_flow` empty, and every file that moves a carrier adds its term. + + +```yaml +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + +given: + parameters: + carrier_in: + description: whether a technology consumes a carrier at a node + dims: [nodes, techs, carriers] + dtype: bool + carrier_out: + description: whether a technology produces a carrier at a node + dims: [nodes, techs, carriers] + dtype: bool + +expressions: + carrier_flow: + description: >- + what every technology and every other file puts into a node's carrier, + less what it takes out + dims: [nodes, carriers, timesteps] + empty: true + +constraints: + system_balance: + description: >- + `system_balance` — at every node, in every time step, a carrier's + production equals its consumption. Built where a technology at the + node produces or consumes the carrier + dims: [nodes, carriers, timesteps] + where: count(carrier_in, over=techs) >= 1 OR count(carrier_out, over=techs) >= 1 + expression: carrier_flow == 0 +``` + +#### Sets + +| Symbol | Meaning | +|---|---| +| $`\mathcal{N}`$ | index $`n`$ — `nodes` — Calliope's `nodes` — the places technologies stand at | +| $`\mathcal{I}`$ | index $`i`$ — `techs` — Calliope's `techs` — technologies | +| $`\mathcal{C}`$ | index $`c`$ — `carriers` — Calliope's `carriers` — energy and commodity carriers | +| $`\mathcal{T}`$ | index $`t`$ — `timesteps` — Calliope's `timesteps` — time steps, in order | + +#### Given + +| Symbol | Meaning | +|---|---| +| $`\mathrm{carrier\_in}`$ | `carrier_in` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C}`$, data another file declares — whether a technology consumes a carrier at a node | +| $`\mathrm{carrier\_out}`$ | `carrier_out` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C}`$, data another file declares — whether a technology produces a carrier at a node | + +#### Definitions + +| Symbol | Meaning | +|---|---| +| $`\mathit{carrier\_flow}`$ | `carrier_flow` over $`\mathcal{N} \times \mathcal{C} \times \mathcal{T}`$ — what every technology and every other file puts into a node's carrier, less what it takes out | + +#### Subject to + +**`system_balance`** + +```math +\mathit{carrier\_flow}_{n,c,t} = 0 \qquad \forall\, n \in \mathcal{N},\ c \in \mathcal{C},\ t \in \mathcal{T} \,:\, \lvert \{ i \in \mathcal{I} \,:\, \mathrm{carrier\_in}_{n,i,c} \} \rvert \ge 1 \vee \lvert \{ i \in \mathcal{I} \,:\, \mathrm{carrier\_out}_{n,i,c} \} \rvert \ge 1 +``` + +#### Definitions + +**`carrier_flow`** + +```math +\mathit{carrier\_flow}_{n,c,t} = \cdots \qquad \forall\, n \in \mathcal{N},\ c \in \mathcal{C},\ t \in \mathcal{T} +``` + diff --git a/docs/examples/calliope/conversion.md b/docs/examples/calliope/conversion.md new file mode 100644 index 00000000..9cac290d --- /dev/null +++ b/docs/examples/calliope/conversion.md @@ -0,0 +1,64 @@ + + +# Conversion + +One of the base fragments of [Calliope in fragments](index.md). Calliope's `balance_conversion`, alone: a conversion technology puts out what it takes in. It declares nothing, and reads everything it needs. + + +```yaml +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + +given: + parameters: + base_tech: { dims: [techs], dtype: str } + include_storage: { dims: [nodes, techs], dtype: bool } + expressions: + flow_out_inc_eff: { dims: [nodes, techs, carriers, timesteps] } + flow_in_inc_eff: { dims: [nodes, techs, carriers, timesteps] } + +constraints: + balance_conversion: + description: "`balance_conversion` — a conversion technology puts out, before its losses, what it takes in after them" + dims: [nodes, techs, timesteps] + where: base_tech == 'conversion' AND NOT include_storage + expression: sum(flow_out_inc_eff, over=carriers) == sum(flow_in_inc_eff, over=carriers) +``` + +#### Sets + +| Symbol | Meaning | +|---|---| +| $`\mathcal{N}`$ | index $`n`$ — `nodes` — Calliope's `nodes` — the places technologies stand at | +| $`\mathcal{I}`$ | index $`i`$ — `techs` — Calliope's `techs` — technologies | +| $`\mathcal{C}`$ | index $`c`$ — `carriers` — Calliope's `carriers` — energy and commodity carriers | +| $`\mathcal{T}`$ | index $`t`$ — `timesteps` — Calliope's `timesteps` — time steps, in order | + +#### Given + +| Symbol | Meaning | +|---|---| +| $`\mathrm{base\_tech}`$ | `base_tech` over $`\mathcal{I}`$, data another file declares | +| $`\mathrm{include\_storage}`$ | `include_storage` over $`\mathcal{N} \times \mathcal{I}`$, data another file declares | +| $`\mathit{flow\_out\_inc\_eff}`$ | `flow_out_inc_eff` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$, an expression another file defines | +| $`\mathit{flow\_in\_inc\_eff}`$ | `flow_in_inc_eff` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$, an expression another file defines | + +#### Subject to + +**`balance_conversion`** + +```math +\sum_{c \in \mathcal{C}} \mathit{flow\_out\_inc\_eff}_{n,i,c,t} = \sum_{c \in \mathcal{C}} \mathit{flow\_in\_inc\_eff}_{n,i,c,t} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T} \,:\, \mathrm{base\_tech}_{i} = \text{'}\mathrm{conversion}\text{'} \wedge \neg \mathrm{include\_storage}_{n,i} +``` + diff --git a/docs/examples/calliope/cost.md b/docs/examples/calliope/cost.md new file mode 100644 index 00000000..4eb06dc6 --- /dev/null +++ b/docs/examples/calliope/cost.md @@ -0,0 +1,195 @@ + + +# The cost + +One of the base fragments of [Calliope in fragments](index.md). How Calliope prices a technology: investment, annualised; variable operation; fixed operation. It declares `cost_investment` and `cost_operation_variable` empty, and `cost_operation_fixed` with a body of its own, and each capacity or flow adds its cost. + + +```yaml +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + costs: + description: Calliope's `costs` — cost classes, such as monetary and CO2 + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + +parameters: + cost_om_annual_investment_fraction: + description: "`cost_om_annual_investment_fraction` — the annual cost of operation, as a share of the investment cost" + dims: [nodes, techs, costs] + cost_depreciation_rate: + description: >- + `cost_depreciation_rate` — the share of the investment cost a year + carries; given only where set, and derived from the lifetime and the + interest rate elsewhere + dims: [nodes, techs, costs] + cost_interest_rate: + description: "`cost_interest_rate` — the interest rate an investment is annualised at" + dims: [nodes, techs, costs] + lifetime: + description: >- + `lifetime` — the years a technology lasts. Calliope's default is + `.inf`, and data prep fills it + dims: [nodes, techs] + cost_annuity_factor: + description: >- + the annuity factor `r (1 + r) ** lifetime / ((1 + r) ** lifetime - 1)` + of the interest rate `r`, data prep. mathspec refuses a sum as the + base of `**` and as a divisor, over parameters too + dims: [nodes, techs, costs] + +given: + parameters: + timestep_resolution: { dims: [timesteps] } + timestep_weights: { dims: [timesteps] } + objective_cost_weights: { dims: [costs] } + expressions: + system_cost: { dims: [], term: cost_of_techs } + +expressions: + cost_investment: + description: >- + `cost_investment` — the investment cost of a technology: flow, storage + and source capacity, and area use. Each file that builds a capacity + adds its own cost + dims: [nodes, techs, costs] + empty: true + cost_operation_variable: + description: >- + `cost_operation_variable` — the operating cost of a technology in a + time step. Each file that builds a flow adds its own cost + dims: [nodes, techs, costs, timesteps] + empty: true + cost_operation_fixed: + description: >- + `cost_operation_fixed` — the fixed annual operating cost of a + technology: its share of the investment cost here, and what each file + adds per unit of capacity + dims: [nodes, techs, costs] + expression: annualisation_weight * cost_investment * cost_om_annual_investment_fraction + annualisation_weight: + description: "`$annualisation_weight` — the share of a year the modelled time steps stand for" + expression: sum(timestep_resolution * timestep_weights, over=timesteps) / 8760 + depreciation_rate: + description: >- + `$depreciation_rate` of `cost_investment_annualised` — the share of the + investment cost a year carries: as given, one over the lifetime with + no interest, and the annuity factor with some + dims: [nodes, techs, costs] + cases: + given: + when: cost_depreciation_rate + expression: cost_depreciation_rate + no_interest: + when: NOT cost_depreciation_rate AND (NOT cost_interest_rate OR cost_interest_rate == 0) + expression: 1 / lifetime + otherwise: cost_annuity_factor + cost_investment_annualised: + description: "`cost_investment_annualised` — the investment cost, as a year's share scaled to the modelled time" + expression: annualisation_weight * depreciation_rate * cost_investment + cost: + description: "`cost` — the total cost of a technology: investment, variable and fixed operation" + expression: cost_investment_annualised + sum(cost_operation_variable, over=timesteps) + cost_operation_fixed + cost_of_techs: + description: "`sum(sum(cost, over=[nodes, techs]) * objective_cost_weights, over=costs)` of `min_cost_optimisation`" + expression: sum(sum(sum(cost, over=nodes), over=techs) * objective_cost_weights) +``` + +#### Sets + +| Symbol | Meaning | +|---|---| +| $`\mathcal{N}`$ | index $`n`$ — `nodes` — Calliope's `nodes` — the places technologies stand at | +| $`\mathcal{I}`$ | index $`i`$ — `techs` — Calliope's `techs` — technologies | +| $`\mathcal{K}`$ | index $`k`$ — `costs` — Calliope's `costs` — cost classes, such as monetary and CO2 | +| $`\mathcal{T}`$ | index $`t`$ — `timesteps` — Calliope's `timesteps` — time steps, in order | + +#### Parameters + +| Symbol | Meaning | +|---|---| +| $`\mathrm{cost}^{\mathrm{om,annual,investment,fraction}}`$ | `cost_om_annual_investment_fraction` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K}`$ — `cost_om_annual_investment_fraction` — the annual cost of operation, as a share of the investment cost | +| $`\mathrm{cost}^{\mathrm{depreciation,rate}}`$ | `cost_depreciation_rate` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K}`$ — `cost_depreciation_rate` — the share of the investment cost a year carries; given only where set, and derived from the lifetime and the interest rate elsewhere | +| $`\mathrm{cost}^{\mathrm{interest,rate}}`$ | `cost_interest_rate` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K}`$ — `cost_interest_rate` — the interest rate an investment is annualised at | +| $`\mathrm{lifetime}`$ | `lifetime` over $`\mathcal{N} \times \mathcal{I}`$ — `lifetime` — the years a technology lasts. Calliope's default is `.inf`, and data prep fills it | +| $`\mathrm{cost}^{\mathrm{annuity,factor}}`$ | `cost_annuity_factor` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K}`$ — the annuity factor `r (1 + r) ** lifetime / ((1 + r) ** lifetime - 1)` of the interest rate `r`, data prep. mathspec refuses a sum as the base of `**` and as a divisor, over parameters too | + +#### Given + +| Symbol | Meaning | +|---|---| +| $`\mathrm{timestep\_resolution}`$ | `timestep_resolution` over $`\mathcal{T}`$, data another file declares | +| $`\mathrm{timestep\_weights}`$ | `timestep_weights` over $`\mathcal{T}`$, data another file declares | +| $`\mathrm{objective\_cost\_weights}`$ | `objective_cost_weights` over $`\mathcal{K}`$, data another file declares | +| $`\mathit{system\_cost}`$ | `system_cost` (scalar), an expression this file adds `cost_of_techs` to | + +#### Definitions + +| Symbol | Meaning | +|---|---| +| $`\mathit{cost}^{\mathrm{operation,fixed}}`$ | `cost_operation_fixed` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K}`$ — `cost_operation_fixed` — the fixed annual operating cost of a technology: its share of the investment cost here, and what each file adds per unit of capacity | +| $`\mathrm{annualisation\_weight}`$ | `annualisation_weight` (scalar) — `$annualisation_weight` — the share of a year the modelled time steps stand for | +| $`\mathrm{depreciation\_rate}`$ | `depreciation_rate` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K}`$ — `$depreciation_rate` of `cost_investment_annualised` — the share of the investment cost a year carries: as given, one over the lifetime with no interest, and the annuity factor with some | +| $`\mathit{cost}^{\mathrm{investment,annualised}}`$ | `cost_investment_annualised` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K}`$ — `cost_investment_annualised` — the investment cost, as a year's share scaled to the modelled time | +| $`\mathit{cost}`$ | `cost` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K}`$ — `cost` — the total cost of a technology: investment, variable and fixed operation | +| $`\mathit{cost}^{\mathrm{of,techs}}`$ | `cost_of_techs` (scalar) — `sum(sum(cost, over=[nodes, techs]) * objective_cost_weights, over=costs)` of `min_cost_optimisation` | +| $`\mathit{cost}^{\mathrm{investment}}`$ | `cost_investment` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K}`$ — `cost_investment` — the investment cost of a technology: flow, storage and source capacity, and area use. Each file that builds a capacity adds its own cost | +| $`\mathit{cost}^{\mathrm{operation,variable}}`$ | `cost_operation_variable` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K} \times \mathcal{T}`$ — `cost_operation_variable` — the operating cost of a technology in a time step. Each file that builds a flow adds its own cost | + +#### Definitions + +**`cost_operation_fixed`** + +```math +\mathit{cost}^{\mathrm{operation,fixed}}_{n,i,k} = \mathrm{annualisation\_weight} \cdot \mathit{cost}^{\mathrm{investment}}_{n,i,k} \cdot \mathrm{cost}^{\mathrm{om,annual,investment,fraction}}_{n,i,k} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ k \in \mathcal{K} +``` + +**`annualisation_weight`** + +```math +\mathrm{annualisation\_weight} = \frac{\sum_{t \in \mathcal{T}} \mathrm{timestep\_resolution}_{t} \cdot \mathrm{timestep\_weights}_{t}}{8760} +``` + +**`depreciation_rate`** + +```math +\mathrm{depreciation\_rate}_{n,i,k} = \begin{cases} \mathrm{cost}^{\mathrm{depreciation,rate}}_{n,i,k} & \text{if } \mathrm{cost}^{\mathrm{depreciation,rate}}_{n,i,k} \text{ is defined} \\ \frac{1}{\mathrm{lifetime}_{n,i}} & \text{if } \neg \left( \mathrm{cost}^{\mathrm{depreciation,rate}}_{n,i,k} \text{ is defined} \right) \wedge \left( \neg \left( \mathrm{cost}^{\mathrm{interest,rate}}_{n,i,k} \text{ is defined} \right) \vee \mathrm{cost}^{\mathrm{interest,rate}}_{n,i,k} = 0 \right) \\ \mathrm{cost}^{\mathrm{annuity,factor}}_{n,i,k} & \text{otherwise} \end{cases} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ k \in \mathcal{K} +``` + +**`cost_investment_annualised`** + +```math +\mathit{cost}^{\mathrm{investment,annualised}}_{n,i,k} = \mathrm{annualisation\_weight} \cdot \mathrm{depreciation\_rate}_{n,i,k} \cdot \mathit{cost}^{\mathrm{investment}}_{n,i,k} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ k \in \mathcal{K} +``` + +**`cost`** + +```math +\mathit{cost}_{n,i,k} = \mathit{cost}^{\mathrm{investment,annualised}}_{n,i,k} + \sum_{t \in \mathcal{T}} \mathit{cost}^{\mathrm{operation,variable}}_{n,i,k,t} + \mathit{cost}^{\mathrm{operation,fixed}}_{n,i,k} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ k \in \mathcal{K} +``` + +**`cost_of_techs`** + +```math +\mathit{cost}^{\mathrm{of,techs}} = \sum_{k \in \mathcal{K}} \left( \sum_{i \in \mathcal{I}} \sum_{n \in \mathcal{N}} \mathit{cost}_{n,i,k} \right) \cdot \mathrm{objective\_cost\_weights}_{k} +``` + +**`cost_investment`** + +```math +\mathit{cost}^{\mathrm{investment}}_{n,i,k} = \cdots \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ k \in \mathcal{K} +``` + +**`cost_operation_variable`** + +```math +\mathit{cost}^{\mathrm{operation,variable}}_{n,i,k,t} = \cdots \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ k \in \mathcal{K},\ t \in \mathcal{T} +``` + diff --git a/docs/examples/calliope/demand.md b/docs/examples/calliope/demand.md new file mode 100644 index 00000000..f8c3a165 --- /dev/null +++ b/docs/examples/calliope/demand.md @@ -0,0 +1,181 @@ + + +# Demand + +One of the base fragments of [Calliope in fragments](index.md). Demand technologies: the sink a technology puts into, required, capped or floored per time step. The sink scaler reads `area_use`, as the source scaler does. + + +```yaml +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + +parameters: + sink_use_min: + description: "`sink_use_min` — least sink use in a time step, per unit of `sink_unit`" + dims: [nodes, techs, timesteps] + sink_use_max: + description: "`sink_use_max` — most sink use in a time step, per unit of `sink_unit`; given only where set" + dims: [nodes, techs, timesteps] + sink_use_equals: + description: "`sink_use_equals` — the sink use required in a time step, such as a demand profile; given only where set" + dims: [nodes, techs, timesteps] + sink_unit: + description: >- + `sink_unit` — what the sink is per: `absolute`, `per_area` of area + use, or `per_cap` of flow capacity. Calliope's default is + `absolute`, which is what a technology with no row reads as + dims: [nodes, techs] + dtype: str + +expressions: + flow_cap_in: + description: "`where(flow_cap, carrier_in)` — the flow capacity of the carriers a technology consumes" + dims: [nodes, techs, carriers] + cases: + consumed: + when: carrier_in + expression: flow_cap + otherwise: 0 + sink_scaler: + description: "`$sink_scaler` — what the sink parameters are per: area use, flow capacity, or one" + dims: [nodes, techs] + cases: + per_area: + when: sink_unit == per_area + expression: area_use + per_cap: + when: sink_unit == per_cap + expression: sum(flow_cap_in, over=carriers) + otherwise: 1 + +given: + parameters: + base_tech: { dims: [techs], dtype: str } + carrier_in: { dims: [nodes, techs, carriers], dtype: bool } + variables: + flow_cap: { dims: [nodes, techs, carriers] } + area_use: { dims: [nodes, techs] } + expressions: + flow_in_inc_eff: { dims: [nodes, techs, carriers, timesteps] } + +constraints: + balance_demand_equals: + description: "`balance_demand` where `sink_use_equals` is set — a demand technology takes in what its sink requires" + dims: [nodes, techs, carriers, timesteps] + where: carrier_in AND base_tech == 'demand' AND sink_use_equals + expression: flow_in_inc_eff == sink_use_equals * sink_scaler + balance_demand_max: + description: "`balance_demand` where only `sink_use_max` is set — a demand technology takes in at most what its sink allows" + dims: [nodes, techs, carriers, timesteps] + where: carrier_in AND base_tech == 'demand' AND NOT sink_use_equals AND sink_use_max + expression: flow_in_inc_eff <= sink_use_max * sink_scaler + balance_demand_min_use: + description: "`balance_demand_min_use` — a demand technology takes in at least its least sink use" + dims: [nodes, techs, carriers, timesteps] + where: carrier_in AND sink_use_min AND NOT sink_use_equals AND base_tech == 'demand' + expression: flow_in_inc_eff >= sink_use_min * sink_scaler + +assumptions: + finite_sink_use: + description: Calliope's `finite_source_use`, for the sink — a required use is finite + holds: NOT sink_use_equals == inf + sink_unit_one_of: + description: Calliope's `one_of` on `sink_unit` + holds: sink_unit == absolute OR sink_unit == per_area OR sink_unit == per_cap + where: sink_unit +``` + +#### Sets + +| Symbol | Meaning | +|---|---| +| $`\mathcal{N}`$ | index $`n`$ — `nodes` — Calliope's `nodes` — the places technologies stand at | +| $`\mathcal{I}`$ | index $`i`$ — `techs` — Calliope's `techs` — technologies | +| $`\mathcal{C}`$ | index $`c`$ — `carriers` — Calliope's `carriers` — energy and commodity carriers | +| $`\mathcal{T}`$ | index $`t`$ — `timesteps` — Calliope's `timesteps` — time steps, in order | + +#### Parameters + +| Symbol | Meaning | +|---|---| +| $`\mathrm{sink\_use\_min}`$ | `sink_use_min` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{T}`$ — `sink_use_min` — least sink use in a time step, per unit of `sink_unit` | +| $`\mathrm{sink\_use\_max}`$ | `sink_use_max` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{T}`$ — `sink_use_max` — most sink use in a time step, per unit of `sink_unit`; given only where set | +| $`\mathrm{sink\_use\_equals}`$ | `sink_use_equals` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{T}`$ — `sink_use_equals` — the sink use required in a time step, such as a demand profile; given only where set | +| $`\mathrm{sink\_unit}`$ | `sink_unit` over $`\mathcal{N} \times \mathcal{I}`$ — `sink_unit` — what the sink is per: `absolute`, `per_area` of area use, or `per_cap` of flow capacity. Calliope's default is `absolute`, which is what a technology with no row reads as | + +#### Given + +| Symbol | Meaning | +|---|---| +| $`\mathrm{base\_tech}`$ | `base_tech` over $`\mathcal{I}`$, data another file declares | +| $`\mathrm{carrier\_in}`$ | `carrier_in` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C}`$, data another file declares | +| $`\mathit{flow\_cap}`$ | `flow_cap` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C}`$ | +| $`\mathit{area\_use}`$ | `area_use` over $`\mathcal{N} \times \mathcal{I}`$ | +| $`\mathit{flow\_in\_inc\_eff}`$ | `flow_in_inc_eff` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$, an expression another file defines | + +#### Definitions + +| Symbol | Meaning | +|---|---| +| $`\mathit{flow\_cap\_in}`$ | `flow_cap_in` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C}`$ — `where(flow_cap, carrier_in)` — the flow capacity of the carriers a technology consumes | +| $`\mathit{sink\_scaler}`$ | `sink_scaler` over $`\mathcal{N} \times \mathcal{I}`$ — `$sink_scaler` — what the sink parameters are per: area use, flow capacity, or one | + +#### Subject to + +**`balance_demand_equals`** + +```math +\mathit{flow\_in\_inc\_eff}_{n,i,c,t} = \mathrm{sink\_use\_equals}_{n,i,t} \cdot \mathit{sink\_scaler}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} \,:\, \mathrm{carrier\_in}_{n,i,c} \wedge \mathrm{base\_tech}_{i} = \text{'}\mathrm{demand}\text{'} \wedge \mathrm{sink\_use\_equals}_{n,i,t} \text{ is defined} +``` + +**`balance_demand_max`** + +```math +\mathit{flow\_in\_inc\_eff}_{n,i,c,t} \le \mathrm{sink\_use\_max}_{n,i,t} \cdot \mathit{sink\_scaler}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} \,:\, \mathrm{carrier\_in}_{n,i,c} \wedge \mathrm{base\_tech}_{i} = \text{'}\mathrm{demand}\text{'} \wedge \neg \left( \mathrm{sink\_use\_equals}_{n,i,t} \text{ is defined} \right) \wedge \mathrm{sink\_use\_max}_{n,i,t} \text{ is defined} +``` + +**`balance_demand_min_use`** + +```math +\mathit{flow\_in\_inc\_eff}_{n,i,c,t} \ge \mathrm{sink\_use\_min}_{n,i,t} \cdot \mathit{sink\_scaler}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} \,:\, \mathrm{carrier\_in}_{n,i,c} \wedge \mathrm{sink\_use\_min}_{n,i,t} \text{ is defined} \wedge \neg \left( \mathrm{sink\_use\_equals}_{n,i,t} \text{ is defined} \right) \wedge \mathrm{base\_tech}_{i} = \text{'}\mathrm{demand}\text{'} +``` + +#### Definitions + +**`flow_cap_in`** + +```math +\mathit{flow\_cap\_in}_{n,i,c} = \begin{cases} \mathit{flow\_cap}_{n,i,c} & \text{if } \mathrm{carrier\_in}_{n,i,c} \\ 0 & \text{otherwise} \end{cases} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C} +``` + +**`sink_scaler`** + +```math +\mathit{sink\_scaler}_{n,i} = \begin{cases} \mathit{area\_use}_{n,i} & \text{if } \mathrm{sink\_unit}_{n,i} = \text{'}\mathrm{per\_area}\text{'} \\ \sum_{c \in \mathcal{C}} \mathit{flow\_cap\_in}_{n,i,c} & \text{if } \mathrm{sink\_unit}_{n,i} = \text{'}\mathrm{per\_cap}\text{'} \\ 1 & \text{otherwise} \end{cases} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I} +``` + +#### Assumptions + +**`finite_sink_use`** + +```math +\neg \left( \mathrm{sink\_use\_equals}_{n,i,t} = \infty \right) \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T} +``` + +**`sink_unit_one_of`** + +```math +\mathrm{sink\_unit}_{n,i} = \text{'}\mathrm{absolute}\text{'} \vee \mathrm{sink\_unit}_{n,i} = \text{'}\mathrm{per\_area}\text{'} \vee \mathrm{sink\_unit}_{n,i} = \text{'}\mathrm{per\_cap}\text{'} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I} \,:\, \mathrm{sink\_unit}_{n,i} \text{ is defined} +``` + diff --git a/docs/examples/calliope/export.md b/docs/examples/calliope/export.md new file mode 100644 index 00000000..fdeab018 --- /dev/null +++ b/docs/examples/calliope/export.md @@ -0,0 +1,156 @@ + + +# Export + +One of the base fragments of [Calliope in fragments](index.md). Export out of the system: a technology may export a carrier it produces, and the export leaves the balance and has a cost. + + +```yaml +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + costs: + description: Calliope's `costs` — cost classes, such as monetary and CO2 + +parameters: + carrier_export: + description: "`carrier_export` — whether a technology may export a carrier it produces out of the system" + dims: [nodes, techs, carriers] + dtype: bool + export_min: + description: "`export_min` — least export. Calliope's default is 0, and data prep fills it" + dims: [nodes, techs, carriers] + export_max: + description: "`export_max` — most export. Calliope's default is `.inf`, and data prep fills it" + dims: [nodes, techs, carriers] + cost_export: + description: "`cost_export` — the cost of one unit of export, usually negative" + dims: [nodes, techs, costs, timesteps] + +variables: + flow_export: + description: "`flow_export` — what a technology exports out of the system in a time step" + dims: [nodes, techs, carriers, timesteps] + where: carrier_export + bounds: { lower: export_min, upper: export_max } + absence: zero + +expressions: + export_carrier_flow: -sum(flow_export, over=techs) + export_cost_operation_variable: timestep_weights * sum(cost_export * flow_export, over=carriers) + +given: + parameters: + carrier_out: { dims: [nodes, techs, carriers], dtype: bool } + timestep_weights: { dims: [timesteps] } + variables: + flow_out: { dims: [nodes, techs, carriers, timesteps] } + expressions: + carrier_flow: { dims: [nodes, carriers, timesteps], term: export_carrier_flow } + cost_operation_variable: { dims: [nodes, techs, costs, timesteps], term: export_cost_operation_variable } + +constraints: + export_balance: + description: "`export_balance` — a technology exports at most what it puts out" + dims: [nodes, techs, carriers, timesteps] + where: flow_export + expression: flow_out >= flow_export + +assumptions: + export_only_for_outflows: + description: Calliope's `export_only_for_outflows` — an exported carrier is one the technology produces + holds: NOT carrier_export OR count(carrier_out, over=nodes) >= 1 +``` + +#### Sets + +| Symbol | Meaning | +|---|---| +| $`\mathcal{N}`$ | index $`n`$ — `nodes` — Calliope's `nodes` — the places technologies stand at | +| $`\mathcal{I}`$ | index $`i`$ — `techs` — Calliope's `techs` — technologies | +| $`\mathcal{C}`$ | index $`c`$ — `carriers` — Calliope's `carriers` — energy and commodity carriers | +| $`\mathcal{T}`$ | index $`t`$ — `timesteps` — Calliope's `timesteps` — time steps, in order | +| $`\mathcal{K}`$ | index $`k`$ — `costs` — Calliope's `costs` — cost classes, such as monetary and CO2 | + +#### Parameters + +| Symbol | Meaning | +|---|---| +| $`\mathrm{carrier\_export}`$ | `carrier_export` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C}`$ — `carrier_export` — whether a technology may export a carrier it produces out of the system | +| $`\mathrm{export\_min}`$ | `export_min` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C}`$ — `export_min` — least export. Calliope's default is 0, and data prep fills it | +| $`\mathrm{export\_max}`$ | `export_max` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C}`$ — `export_max` — most export. Calliope's default is `.inf`, and data prep fills it | +| $`\mathrm{cost\_export}`$ | `cost_export` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K} \times \mathcal{T}`$ — `cost_export` — the cost of one unit of export, usually negative | + +#### Variables + +| Symbol | Meaning | +|---|---| +| $`\mathit{flow\_export}`$ | `flow_export` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ — `flow_export` — what a technology exports out of the system in a time step | + +#### Given + +| Symbol | Meaning | +|---|---| +| $`\mathrm{carrier\_out}`$ | `carrier_out` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C}`$, data another file declares | +| $`\mathrm{timestep\_weights}`$ | `timestep_weights` over $`\mathcal{T}`$, data another file declares | +| $`\mathit{flow\_out}`$ | `flow_out` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ | +| $`\mathit{carrier\_flow}`$ | `carrier_flow` over $`\mathcal{N} \times \mathcal{C} \times \mathcal{T}`$, an expression this file adds `export_carrier_flow` to | +| $`\mathit{cost\_operation\_variable}`$ | `cost_operation_variable` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K} \times \mathcal{T}`$, an expression this file adds `export_cost_operation_variable` to | + +#### Definitions + +| Symbol | Meaning | +|---|---| +| $`\mathit{export\_carrier\_flow}`$ | `export_carrier_flow` over $`\mathcal{N} \times \mathcal{C} \times \mathcal{T}`$ | +| $`\mathit{export\_cost\_operation\_variable}`$ | `export_cost_operation_variable` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{T} \times \mathcal{K}`$ | + +Upright is what the data supplies — a parameter such as $`\mathrm{carrier\_export}`$, a coordinate map, a label — and italic is what the solver chooses, such as $`\mathit{flow\_export}`$. An index is italic too, being what a quantifier chooses, and a set is script. + +#### Subject to + +**`export_balance`** + +```math +\mathit{flow\_out}_{n,i,c,t} \ge \mathit{flow\_export}_{n,i,c,t} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} \,:\, \mathit{flow\_export}_{n,i,c,t} \text{ exists} +``` + +#### Definitions + +**`export_carrier_flow`** + +```math +\mathit{export\_carrier\_flow}_{n,c,t} = -\left( \sum_{i \in \mathcal{I}} \mathit{flow\_export}_{n,i,c,t} \right) \qquad \forall\, n \in \mathcal{N},\ c \in \mathcal{C},\ t \in \mathcal{T} +``` + +**`export_cost_operation_variable`** + +```math +\mathit{export\_cost\_operation\_variable}_{n,i,t,k} = \mathrm{timestep\_weights}_{t} \cdot \left( \sum_{c \in \mathcal{C}} \mathrm{cost\_export}_{n,i,k,t} \cdot \mathit{flow\_export}_{n,i,c,t} \right) \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T},\ k \in \mathcal{K} +``` + +#### Variable domains + +**`flow_export`** + +```math +\mathrm{export\_min}_{n,i,c} \le \mathit{flow\_export}_{n,i,c,t} \le \mathrm{export\_max}_{n,i,c} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} \,:\, \mathrm{carrier\_export}_{n,i,c} +``` + +#### Assumptions + +**`export_only_for_outflows`** + +```math +\neg \mathrm{carrier\_export}_{n,i,c} \vee \lvert \{ n' \in \mathcal{N} \,:\, \mathrm{carrier\_out}_{n',i,c} \} \rvert \ge 1 \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C} +``` + diff --git a/docs/examples/calliope/extensions/annual_energy_balance.md b/docs/examples/calliope/extensions/annual_energy_balance.md new file mode 100644 index 00000000..554b75ec --- /dev/null +++ b/docs/examples/calliope/extensions/annual_energy_balance.md @@ -0,0 +1,168 @@ + + +# Annual energy balance + +An extension of [Calliope in fragments](../index.md). Calliope's example `annual_energy_balance.yaml`: limits on what a technology puts out, takes from its source or puts into its sink over the whole time. + + +```yaml +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + +parameters: + annual_flow_max: + description: "`annual_flow_max` — the most a technology puts out over the whole time; given only where set" + dims: [techs] + annual_flow_max_group: + description: >- + `annual_flow_max` as the group row reads it — the most the group puts + out over the whole time. Calliope reads one parameter at three shapes + and lets the data choose; a parameter here has one, so the group's + limit is a number of its own + dims: [] + annual_source_max: + description: "`annual_source_max` — the most a technology takes from its source over the whole time; given only where set" + dims: [techs] + annual_sink_max: + description: "`annual_sink_max` — the most a technology puts into its sink over the whole time; given only where set" + dims: [techs] + flow_max_group: + description: "`flow_max_group` — whether a technology is in the group the group limit holds for" + dims: [techs] + dtype: bool + +expressions: + flow_out_of_group: + description: "`flow_out[techs=$techs]` — outflow of the technologies in the group" + dims: [nodes, techs, carriers, timesteps] + cases: + in_group: + when: flow_max_group + expression: flow_out + otherwise: 0 + +given: + parameters: + base_tech: { dims: [techs], dtype: str } + variables: + flow_out: { dims: [nodes, techs, carriers, timesteps] } + flow_in: { dims: [nodes, techs, carriers, timesteps] } + source_use: { dims: [nodes, techs, timesteps] } + +constraints: + annual_energy_balance_per_tech_and_node: + description: "`annual_energy_balance_per_tech_and_node` — a technology at a node puts out at most its annual limit" + dims: [nodes, techs] + where: annual_flow_max + expression: sum(sum(flow_out, over=carriers), over=timesteps) <= annual_flow_max + annual_energy_balance_global_per_tech: + description: "`annual_energy_balance_global_per_tech` — a technology puts out at most its annual limit over every node" + dims: [techs] + where: annual_flow_max + expression: sum(sum(sum(flow_out, over=nodes), over=carriers), over=timesteps) <= annual_flow_max + annual_energy_balance_global_multi_tech: + description: "`annual_energy_balance_global_multi_tech` — the group of technologies puts out at most its annual limit over every node" + dims: [] + where: annual_flow_max_group + expression: sum(flow_out_of_group) <= annual_flow_max_group + annual_energy_balance_total_source_availability: + description: >- + `annual_energy_balance_total_source_availability` — a technology takes + at most its annual limit from its source. Calliope's `where: + source_use` over a technology reads as the technology being a supply + one, which is where `source_use` is built + dims: [techs] + where: base_tech == 'supply' AND annual_source_max + expression: sum(sum(source_use, over=nodes), over=timesteps) <= annual_source_max + annual_energy_balance_total_sink_availability: + description: "`annual_energy_balance_total_sink_availability` — a demand technology takes in at most its annual limit" + dims: [techs] + where: base_tech == 'demand' AND annual_sink_max + expression: sum(sum(sum(flow_in, over=nodes), over=carriers), over=timesteps) <= annual_sink_max +``` + +#### Sets + +| Symbol | Meaning | +|---|---| +| $`\mathcal{N}`$ | index $`n`$ — `nodes` — Calliope's `nodes` — the places technologies stand at | +| $`\mathcal{I}`$ | index $`i`$ — `techs` — Calliope's `techs` — technologies | +| $`\mathcal{C}`$ | index $`c`$ — `carriers` — Calliope's `carriers` — energy and commodity carriers | +| $`\mathcal{T}`$ | index $`t`$ — `timesteps` — Calliope's `timesteps` — time steps, in order | + +#### Parameters + +| Symbol | Meaning | +|---|---| +| $`\mathrm{annual\_flow\_max}`$ | `annual_flow_max` over $`\mathcal{I}`$ — `annual_flow_max` — the most a technology puts out over the whole time; given only where set | +| $`\mathrm{annual\_flow\_max\_group}`$ | `annual_flow_max_group` (scalar) — `annual_flow_max` as the group row reads it — the most the group puts out over the whole time. Calliope reads one parameter at three shapes and lets the data choose; a parameter here has one, so the group's limit is a number of its own | +| $`\mathrm{annual\_source\_max}`$ | `annual_source_max` over $`\mathcal{I}`$ — `annual_source_max` — the most a technology takes from its source over the whole time; given only where set | +| $`\mathrm{annual\_sink\_max}`$ | `annual_sink_max` over $`\mathcal{I}`$ — `annual_sink_max` — the most a technology puts into its sink over the whole time; given only where set | +| $`\mathrm{flow\_max\_group}`$ | `flow_max_group` over $`\mathcal{I}`$ — `flow_max_group` — whether a technology is in the group the group limit holds for | + +#### Given + +| Symbol | Meaning | +|---|---| +| $`\mathrm{base\_tech}`$ | `base_tech` over $`\mathcal{I}`$, data another file declares | +| $`\mathit{flow\_out}`$ | `flow_out` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ | +| $`\mathit{flow\_in}`$ | `flow_in` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ | +| $`\mathit{source\_use}`$ | `source_use` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{T}`$ | + +#### Definitions + +| Symbol | Meaning | +|---|---| +| $`\mathit{flow\_out\_of\_group}`$ | `flow_out_of_group` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ — `flow_out[techs=$techs]` — outflow of the technologies in the group | + +#### Subject to + +**`annual_energy_balance_per_tech_and_node`** + +```math +\sum_{t \in \mathcal{T}} \sum_{c \in \mathcal{C}} \mathit{flow\_out}_{n,i,c,t} \le \mathrm{annual\_flow\_max}_{i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I} \,:\, \mathrm{annual\_flow\_max}_{i} \text{ is defined} +``` + +**`annual_energy_balance_global_per_tech`** + +```math +\sum_{t \in \mathcal{T}} \sum_{c \in \mathcal{C}} \sum_{n \in \mathcal{N}} \mathit{flow\_out}_{n,i,c,t} \le \mathrm{annual\_flow\_max}_{i} \qquad \forall\, i \in \mathcal{I} \,:\, \mathrm{annual\_flow\_max}_{i} \text{ is defined} +``` + +**`annual_energy_balance_global_multi_tech`** + +```math +\sum_{n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T}} \mathit{flow\_out\_of\_group}_{n,i,c,t} \le \mathrm{annual\_flow\_max\_group} \qquad \text{where } \mathrm{annual\_flow\_max\_group} \text{ is defined} +``` + +**`annual_energy_balance_total_source_availability`** + +```math +\sum_{t \in \mathcal{T}} \sum_{n \in \mathcal{N}} \mathit{source\_use}_{n,i,t} \le \mathrm{annual\_source\_max}_{i} \qquad \forall\, i \in \mathcal{I} \,:\, \mathrm{base\_tech}_{i} = \text{'}\mathrm{supply}\text{'} \wedge \mathrm{annual\_source\_max}_{i} \text{ is defined} +``` + +**`annual_energy_balance_total_sink_availability`** + +```math +\sum_{t \in \mathcal{T}} \sum_{c \in \mathcal{C}} \sum_{n \in \mathcal{N}} \mathit{flow\_in}_{n,i,c,t} \le \mathrm{annual\_sink\_max}_{i} \qquad \forall\, i \in \mathcal{I} \,:\, \mathrm{base\_tech}_{i} = \text{'}\mathrm{demand}\text{'} \wedge \mathrm{annual\_sink\_max}_{i} \text{ is defined} +``` + +#### Definitions + +**`flow_out_of_group`** + +```math +\mathit{flow\_out\_of\_group}_{n,i,c,t} = \begin{cases} \mathit{flow\_out}_{n,i,c,t} & \text{if } \mathrm{flow\_max\_group}_{i} \\ 0 & \text{otherwise} \end{cases} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} +``` + diff --git a/docs/examples/calliope/extensions/chp_htp.md b/docs/examples/calliope/extensions/chp_htp.md new file mode 100644 index 00000000..eafc7b8e --- /dev/null +++ b/docs/examples/calliope/extensions/chp_htp.md @@ -0,0 +1,241 @@ + + +# CHP plants + +An extension of [Calliope in fragments](../index.md). Calliope's example `chp_htp.yaml`: the operating region of combined heat and power plants with extraction or backpressure turbines. Calliope rewrites `balance_conversion` for these plants; the new row is here, and [the CHP patch](../variants/chp_htp.md) keeps the base row off them. + + +```yaml +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + +parameters: + turbine_type: + description: "`turbine_type` — `extraction` or `backpressure`: the kind of turbine a combined heat and power plant has" + dims: [nodes, techs] + dtype: str + power_loss_factor: + description: "`power_loss_factor` — `cv`, the power an extraction turbine loses per unit of heat. Calliope's default is 1, and data prep fills it" + dims: [nodes, techs] + power_to_heat_ratio: + description: "`power_to_heat_ratio` — `cb`, the backpressure ratio. Calliope's default is 1, and data prep fills it" + dims: [nodes, techs] + boiler_eff: + description: "`boiler_eff` — the efficiency of the boiler fuel may be diverted to; given only where set" + dims: [nodes, techs] + +expressions: + chp_electricity_out: + description: "`flow_out[carriers=electricity]`" + dims: [nodes, techs, carriers, timesteps] + cases: + electricity: + when: carriers == electricity + expression: flow_out + otherwise: 0 + chp_heat_out: + description: "`flow_out[carriers=heat]`" + dims: [nodes, techs, carriers, timesteps] + cases: + heat: + when: carriers == heat + expression: flow_out + otherwise: 0 + chp_electricity_out_eff: + description: "`flow_out_eff[carriers=electricity]`" + dims: [nodes, techs, carriers, timesteps] + cases: + electricity: + when: carriers == electricity + expression: flow_out_eff + otherwise: 0 + chp_electricity_out_inc_eff: + description: "`flow_out_inc_eff[carriers=electricity]`" + dims: [nodes, techs, carriers, timesteps] + cases: + electricity: + when: carriers == electricity + expression: flow_out_inc_eff + otherwise: 0 + +given: + parameters: + base_tech: { dims: [techs], dtype: str } + include_storage: { dims: [nodes, techs], dtype: bool } + flow_out_eff: { dims: [nodes, techs, carriers, timesteps] } + variables: + flow_out: { dims: [nodes, techs, carriers, timesteps] } + expressions: + flow_out_inc_eff: { dims: [nodes, techs, carriers, timesteps] } + flow_in_inc_eff: { dims: [nodes, techs, carriers, timesteps] } + +constraints: + balance_conversion_backpressure: + description: >- + `balance_conversion` for a backpressure plant with no boiler — the + plant puts out, before losses, as much electricity as it takes in fuel + after them. The patch keeps the base row off these plants + dims: [nodes, techs, timesteps] + where: base_tech == 'conversion' AND NOT include_storage AND turbine_type == backpressure AND NOT boiler_eff + expression: sum(chp_electricity_out_inc_eff, over=carriers) == sum(flow_in_inc_eff, over=carriers) + chp_extraction_line: + description: "`chp_extraction_line` — an extraction plant puts out at most the electricity its fuel gives, less what the heat costs" + dims: [nodes, techs, timesteps] + where: turbine_type == extraction + expression: >- + sum(chp_electricity_out, over=carriers) + <= sum(flow_in_inc_eff, over=carriers) * sum(chp_electricity_out_eff, over=carriers) + - sum(chp_heat_out, over=carriers) * power_loss_factor + chp_backpressure_line_min: + description: "`chp_backpressure_line_min` — an extraction plant puts out at least the backpressure ratio of electricity per unit of heat" + dims: [nodes, techs, timesteps] + where: turbine_type == extraction + expression: sum(chp_electricity_out, over=carriers) >= sum(chp_heat_out, over=carriers) * power_to_heat_ratio + chp_backpressure_line_max: + description: "`chp_backpressure_line_max` — a backpressure plant with a boiler puts out at most the backpressure ratio of electricity per unit of heat" + dims: [nodes, techs, timesteps] + where: turbine_type == backpressure AND boiler_eff + expression: sum(chp_electricity_out, over=carriers) <= sum(chp_heat_out, over=carriers) * power_to_heat_ratio + chp_divert_fuel_to_boiler: + description: "`chp_divert_fuel_to_boiler` — a backpressure plant with a boiler puts out at most the heat its fuel gives through turbine and boiler" + dims: [nodes, techs, timesteps] + where: turbine_type == backpressure AND boiler_eff + expression: >- + sum(chp_heat_out, over=carriers) + <= sum(flow_in_inc_eff, over=carriers) * boiler_eff + - sum(chp_electricity_out, over=carriers) + * (boiler_eff / sum(chp_electricity_out_eff, over=carriers) - 1 / power_to_heat_ratio) + chp_backpressure_line_equals: + description: "`chp_backpressure_line_equals` — a backpressure plant with no boiler puts out the backpressure ratio of electricity per unit of heat" + dims: [nodes, techs, timesteps] + where: turbine_type == backpressure AND NOT boiler_eff + expression: sum(chp_electricity_out, over=carriers) == sum(chp_heat_out, over=carriers) * power_to_heat_ratio + +assumptions: + turbine_type_one_of: + description: Calliope's `one_of` on `turbine_type` + holds: turbine_type == extraction OR turbine_type == backpressure + where: turbine_type +``` + +#### Sets + +| Symbol | Meaning | +|---|---| +| $`\mathcal{N}`$ | index $`n`$ — `nodes` — Calliope's `nodes` — the places technologies stand at | +| $`\mathcal{I}`$ | index $`i`$ — `techs` — Calliope's `techs` — technologies | +| $`\mathcal{C}`$ | index $`c`$ — `carriers` — Calliope's `carriers` — energy and commodity carriers | +| $`\mathcal{T}`$ | index $`t`$ — `timesteps` — Calliope's `timesteps` — time steps, in order | + +#### Parameters + +| Symbol | Meaning | +|---|---| +| $`\mathrm{turbine\_type}`$ | `turbine_type` over $`\mathcal{N} \times \mathcal{I}`$ — `turbine_type` — `extraction` or `backpressure`: the kind of turbine a combined heat and power plant has | +| $`\mathrm{power\_loss\_factor}`$ | `power_loss_factor` over $`\mathcal{N} \times \mathcal{I}`$ — `power_loss_factor` — `cv`, the power an extraction turbine loses per unit of heat. Calliope's default is 1, and data prep fills it | +| $`\mathrm{power\_to\_heat\_ratio}`$ | `power_to_heat_ratio` over $`\mathcal{N} \times \mathcal{I}`$ — `power_to_heat_ratio` — `cb`, the backpressure ratio. Calliope's default is 1, and data prep fills it | +| $`\mathrm{boiler\_eff}`$ | `boiler_eff` over $`\mathcal{N} \times \mathcal{I}`$ — `boiler_eff` — the efficiency of the boiler fuel may be diverted to; given only where set | + +#### Given + +| Symbol | Meaning | +|---|---| +| $`\mathrm{base\_tech}`$ | `base_tech` over $`\mathcal{I}`$, data another file declares | +| $`\mathrm{include\_storage}`$ | `include_storage` over $`\mathcal{N} \times \mathcal{I}`$, data another file declares | +| $`\mathrm{flow\_out\_eff}`$ | `flow_out_eff` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$, data another file declares | +| $`\mathit{flow\_out}`$ | `flow_out` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ | +| $`\mathit{flow\_out\_inc\_eff}`$ | `flow_out_inc_eff` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$, an expression another file defines | +| $`\mathit{flow\_in\_inc\_eff}`$ | `flow_in_inc_eff` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$, an expression another file defines | + +#### Definitions + +| Symbol | Meaning | +|---|---| +| $`\mathit{chp\_electricity\_out}`$ | `chp_electricity_out` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ — `flow_out[carriers=electricity]` | +| $`\mathit{chp\_heat\_out}`$ | `chp_heat_out` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ — `flow_out[carriers=heat]` | +| $`\mathrm{chp\_electricity\_out\_eff}`$ | `chp_electricity_out_eff` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ — `flow_out_eff[carriers=electricity]` | +| $`\mathit{chp\_electricity\_out\_inc\_eff}`$ | `chp_electricity_out_inc_eff` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ — `flow_out_inc_eff[carriers=electricity]` | + +#### Subject to + +**`balance_conversion_backpressure`** + +```math +\sum_{c \in \mathcal{C}} \mathit{chp\_electricity\_out\_inc\_eff}_{n,i,c,t} = \sum_{c \in \mathcal{C}} \mathit{flow\_in\_inc\_eff}_{n,i,c,t} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T} \,:\, \mathrm{base\_tech}_{i} = \text{'}\mathrm{conversion}\text{'} \wedge \neg \mathrm{include\_storage}_{n,i} \wedge \mathrm{turbine\_type}_{n,i} = \text{'}\mathrm{backpressure}\text{'} \wedge \neg \left( \mathrm{boiler\_eff}_{n,i} \text{ is defined} \right) +``` + +**`chp_extraction_line`** + +```math +\sum_{c \in \mathcal{C}} \mathit{chp\_electricity\_out}_{n,i,c,t} \le \left( \sum_{c \in \mathcal{C}} \mathit{flow\_in\_inc\_eff}_{n,i,c,t} \right) \cdot \left( \sum_{c \in \mathcal{C}} \mathrm{chp\_electricity\_out\_eff}_{n,i,c,t} \right) - \left( \sum_{c \in \mathcal{C}} \mathit{chp\_heat\_out}_{n,i,c,t} \right) \cdot \mathrm{power\_loss\_factor}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T} \,:\, \mathrm{turbine\_type}_{n,i} = \text{'}\mathrm{extraction}\text{'} +``` + +**`chp_backpressure_line_min`** + +```math +\sum_{c \in \mathcal{C}} \mathit{chp\_electricity\_out}_{n,i,c,t} \ge \left( \sum_{c \in \mathcal{C}} \mathit{chp\_heat\_out}_{n,i,c,t} \right) \cdot \mathrm{power\_to\_heat\_ratio}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T} \,:\, \mathrm{turbine\_type}_{n,i} = \text{'}\mathrm{extraction}\text{'} +``` + +**`chp_backpressure_line_max`** + +```math +\sum_{c \in \mathcal{C}} \mathit{chp\_electricity\_out}_{n,i,c,t} \le \left( \sum_{c \in \mathcal{C}} \mathit{chp\_heat\_out}_{n,i,c,t} \right) \cdot \mathrm{power\_to\_heat\_ratio}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T} \,:\, \mathrm{turbine\_type}_{n,i} = \text{'}\mathrm{backpressure}\text{'} \wedge \mathrm{boiler\_eff}_{n,i} \text{ is defined} +``` + +**`chp_divert_fuel_to_boiler`** + +```math +\sum_{c \in \mathcal{C}} \mathit{chp\_heat\_out}_{n,i,c,t} \le \left( \sum_{c \in \mathcal{C}} \mathit{flow\_in\_inc\_eff}_{n,i,c,t} \right) \cdot \mathrm{boiler\_eff}_{n,i} - \left( \sum_{c \in \mathcal{C}} \mathit{chp\_electricity\_out}_{n,i,c,t} \right) \cdot \left( \frac{\mathrm{boiler\_eff}_{n,i}}{\sum_{c \in \mathcal{C}} \mathrm{chp\_electricity\_out\_eff}_{n,i,c,t}} - \frac{1}{\mathrm{power\_to\_heat\_ratio}_{n,i}} \right) \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T} \,:\, \mathrm{turbine\_type}_{n,i} = \text{'}\mathrm{backpressure}\text{'} \wedge \mathrm{boiler\_eff}_{n,i} \text{ is defined} +``` + +**`chp_backpressure_line_equals`** + +```math +\sum_{c \in \mathcal{C}} \mathit{chp\_electricity\_out}_{n,i,c,t} = \left( \sum_{c \in \mathcal{C}} \mathit{chp\_heat\_out}_{n,i,c,t} \right) \cdot \mathrm{power\_to\_heat\_ratio}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T} \,:\, \mathrm{turbine\_type}_{n,i} = \text{'}\mathrm{backpressure}\text{'} \wedge \neg \left( \mathrm{boiler\_eff}_{n,i} \text{ is defined} \right) +``` + +#### Definitions + +**`chp_electricity_out`** + +```math +\mathit{chp\_electricity\_out}_{n,i,c,t} = \begin{cases} \mathit{flow\_out}_{n,i,c,t} & \text{if } c = \text{'}\mathrm{electricity}\text{'} \\ 0 & \text{otherwise} \end{cases} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} +``` + +**`chp_heat_out`** + +```math +\mathit{chp\_heat\_out}_{n,i,c,t} = \begin{cases} \mathit{flow\_out}_{n,i,c,t} & \text{if } c = \text{'}\mathrm{heat}\text{'} \\ 0 & \text{otherwise} \end{cases} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} +``` + +**`chp_electricity_out_eff`** + +```math +\mathrm{chp\_electricity\_out\_eff}_{n,i,c,t} = \begin{cases} \mathrm{flow\_out\_eff}_{n,i,c,t} & \text{if } c = \text{'}\mathrm{electricity}\text{'} \\ 0 & \text{otherwise} \end{cases} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} +``` + +**`chp_electricity_out_inc_eff`** + +```math +\mathit{chp\_electricity\_out\_inc\_eff}_{n,i,c,t} = \begin{cases} \mathit{flow\_out\_inc\_eff}_{n,i,c,t} & \text{if } c = \text{'}\mathrm{electricity}\text{'} \\ 0 & \text{otherwise} \end{cases} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} +``` + +#### Assumptions + +**`turbine_type_one_of`** + +```math +\mathrm{turbine\_type}_{n,i} = \text{'}\mathrm{extraction}\text{'} \vee \mathrm{turbine\_type}_{n,i} = \text{'}\mathrm{backpressure}\text{'} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I} \,:\, \mathrm{turbine\_type}_{n,i} \text{ is defined} +``` + diff --git a/docs/examples/calliope/extensions/demand_share_per_timestep_decision.md b/docs/examples/calliope/extensions/demand_share_per_timestep_decision.md new file mode 100644 index 00000000..a5f7f4f4 --- /dev/null +++ b/docs/examples/calliope/extensions/demand_share_per_timestep_decision.md @@ -0,0 +1,193 @@ + + +# Demand share as a decision + +An extension of [Calliope in fragments](../index.md). Calliope's example `demand_share_per_timestep_decision.yaml`: a technology meets a share of a demand that the model decides, the same in every time step. + + +```yaml +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + +relations: + decide_demand_share: + description: >- + `decide_demand_share` — the demand technology whose inflow a technology + meets a share of. Calliope reads it with `select_from_lookup_arrays`, + which is a read through the relation + key: techs + values: { demand: techs } + demand_share_carrier: + description: >- + `demand_share_carrier` — the carrier a share of demand is counted in. + Calliope slices `flow_out` by it, which is a test of the pair + key: [techs, carriers] + +parameters: + demand_share_relaxation: + description: "`demand_share_relaxation` — how far the share may stray from the one decided, as a fraction" + dims: [nodes, techs] + demand_share_limit: + description: "`demand_share_limit` — the share of demand the technologies meet together; given only where set" + dims: [nodes] + +variables: + demand_share_per_timestep_decision: + description: "`demand_share_per_timestep_decision` — the share of demand a technology meets, the same in every time step" + dims: [nodes, techs] + where: decide_demand_share + bounds: { lower: 0 } + absence: zero + +expressions: + demand_share_flow_out: + description: "`flow_out[carriers=$carrier]` — a technology's outflow of the carrier its share is counted in" + dims: [nodes, techs, carriers, timesteps] + cases: + share_carrier: + when: demand_share_carrier + expression: flow_out + otherwise: 0 + demand_share_sink: + description: "`select_from_lookup_arrays(sink_use_equals, techs=decide_demand_share)` — the demand a technology meets a share of" + expression: at(sink_use_equals, by=decide_demand_share, over=demand, into=techs) + +given: + parameters: + sink_use_equals: { dims: [nodes, techs, timesteps] } + variables: + flow_out: { dims: [nodes, techs, carriers, timesteps] } + +constraints: + demand_share_per_timestep_decision_main_min: + description: "`demand_share_per_timestep_decision_main_min` — a technology puts out at least its decided share of demand, less the relaxation" + dims: [nodes, techs, timesteps] + where: demand_share_per_timestep_decision + expression: >- + sum(demand_share_flow_out, over=carriers) + >= (1 - demand_share_relaxation) * demand_share_sink * demand_share_per_timestep_decision + demand_share_per_timestep_decision_main_max: + description: "`demand_share_per_timestep_decision_main_max` — a technology puts out at most its decided share of demand, plus the relaxation" + dims: [nodes, techs, timesteps] + where: demand_share_per_timestep_decision + expression: >- + sum(demand_share_flow_out, over=carriers) + <= (1 + demand_share_relaxation) * demand_share_sink * demand_share_per_timestep_decision + demand_share_per_timestep_decision_sum: + description: >- + `demand_share_per_timestep_decision_sum` — the decided shares at a node + add up to the limit. Calliope's `where: demand_share_per_timestep_decision` + over a node reads as any technology there deciding a share. Calliope + builds the row in every time step, and it is the same in each; a row + repeated along a dimension it does not read is refused, so it is one + row per node + dims: [nodes] + where: count(demand_share_per_timestep_decision, over=techs) >= 1 AND demand_share_limit + expression: sum(demand_share_per_timestep_decision, over=techs) == demand_share_limit + +assumptions: + demand_share_is_fraction: + description: Calliope's `demand_share_is_fraction` — the demand share limit is a fraction + holds: demand_share_limit >= 0 AND demand_share_limit <= 1 + where: demand_share_limit +``` + +#### Sets + +| Symbol | Meaning | +|---|---| +| $`\mathcal{N}`$ | index $`n`$ — `nodes` — Calliope's `nodes` — the places technologies stand at | +| $`\mathcal{I}`$ | index $`i`$ — `techs` with $`\mathrm{decide\_demand\_share}: \mathcal{I} \to \mathcal{I},\ \mathrm{demand\_share\_carrier} \subseteq \mathcal{I} \times \mathcal{C}`$ — Calliope's `techs` — technologies | +| $`\mathcal{C}`$ | index $`c`$ — `carriers` with $`\mathrm{demand\_share\_carrier} \subseteq \mathcal{I} \times \mathcal{C}`$ — Calliope's `carriers` — energy and commodity carriers | +| $`\mathcal{T}`$ | index $`t`$ — `timesteps` — Calliope's `timesteps` — time steps, in order | + +#### Parameters + +| Symbol | Meaning | +|---|---| +| $`\mathrm{demand\_share\_relaxation}`$ | `demand_share_relaxation` over $`\mathcal{N} \times \mathcal{I}`$ — `demand_share_relaxation` — how far the share may stray from the one decided, as a fraction | +| $`\mathrm{demand\_share\_limit}`$ | `demand_share_limit` over $`\mathcal{N}`$ — `demand_share_limit` — the share of demand the technologies meet together; given only where set | + +#### Variables + +| Symbol | Meaning | +|---|---| +| $`\mathit{demand\_share\_per\_timestep\_decision}`$ | `demand_share_per_timestep_decision` over $`\mathcal{N} \times \mathcal{I}`$ — `demand_share_per_timestep_decision` — the share of demand a technology meets, the same in every time step | + +#### Given + +| Symbol | Meaning | +|---|---| +| $`\mathrm{sink\_use\_equals}`$ | `sink_use_equals` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{T}`$, data another file declares | +| $`\mathit{flow\_out}`$ | `flow_out` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ | + +#### Definitions + +| Symbol | Meaning | +|---|---| +| $`\mathit{demand\_share\_flow\_out}`$ | `demand_share_flow_out` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ — `flow_out[carriers=$carrier]` — a technology's outflow of the carrier its share is counted in | +| $`\mathrm{demand\_share\_sink}`$ | `demand_share_sink` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{T}`$ — `select_from_lookup_arrays(sink_use_equals, techs=decide_demand_share)` — the demand a technology meets a share of | + +Upright is what the data supplies — a parameter such as $`\mathrm{demand\_share\_relaxation}`$, a coordinate map, a label — and italic is what the solver chooses, such as $`\mathit{demand\_share\_per\_timestep\_decision}`$. An index is italic too, being what a quantifier chooses, and a set is script. + +#### Subject to + +**`demand_share_per_timestep_decision_main_min`** + +```math +\sum_{c \in \mathcal{C}} \mathit{demand\_share\_flow\_out}_{n,i,c,t} \ge \left( 1 - \mathrm{demand\_share\_relaxation}_{n,i} \right) \cdot \mathrm{demand\_share\_sink}_{n,i,t} \cdot \mathit{demand\_share\_per\_timestep\_decision}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T} \,:\, \mathit{demand\_share\_per\_timestep\_decision}_{n,i} \text{ exists} +``` + +**`demand_share_per_timestep_decision_main_max`** + +```math +\sum_{c \in \mathcal{C}} \mathit{demand\_share\_flow\_out}_{n,i,c,t} \le \left( 1 + \mathrm{demand\_share\_relaxation}_{n,i} \right) \cdot \mathrm{demand\_share\_sink}_{n,i,t} \cdot \mathit{demand\_share\_per\_timestep\_decision}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T} \,:\, \mathit{demand\_share\_per\_timestep\_decision}_{n,i} \text{ exists} +``` + +**`demand_share_per_timestep_decision_sum`** + +```math +\sum_{i \in \mathcal{I}} \mathit{demand\_share\_per\_timestep\_decision}_{n,i} = \mathrm{demand\_share\_limit}_{n} \qquad \forall\, n \in \mathcal{N} \,:\, \lvert \{ i \in \mathcal{I} \,:\, \mathit{demand\_share\_per\_timestep\_decision}_{n,i} \text{ exists} \} \rvert \ge 1 \wedge \mathrm{demand\_share\_limit}_{n} \text{ is defined} +``` + +#### Definitions + +**`demand_share_flow_out`** + +```math +\mathit{demand\_share\_flow\_out}_{n,i,c,t} = \begin{cases} \mathit{flow\_out}_{n,i,c,t} & \text{if } \left( i,\ c \right) \in \mathrm{demand\_share\_carrier} \\ 0 & \text{otherwise} \end{cases} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} +``` + +**`demand_share_sink`** + +```math +\mathrm{demand\_share\_sink}_{n,i,t} = \mathrm{sink\_use\_equals}_{n,\mathrm{decide\_demand\_share}(i),t} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T} +``` + +#### Variable domains + +**`demand_share_per_timestep_decision`** + +```math +\mathit{demand\_share\_per\_timestep\_decision}_{n,i} \ge 0 \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I} \,:\, \mathrm{decide\_demand\_share}(i) \text{ is defined} +``` + +#### Assumptions + +**`demand_share_is_fraction`** + +```math +\mathrm{demand\_share\_limit}_{n} \ge 0 \wedge \mathrm{demand\_share\_limit}_{n} \le 1 \qquad \forall\, n \in \mathcal{N} \,:\, \mathrm{demand\_share\_limit}_{n} \text{ is defined} +``` + diff --git a/docs/examples/calliope/extensions/fuel_dist.md b/docs/examples/calliope/extensions/fuel_dist.md new file mode 100644 index 00000000..a1b1a1be --- /dev/null +++ b/docs/examples/calliope/extensions/fuel_dist.md @@ -0,0 +1,179 @@ + + +# Fuel distribution + +An extension of [Calliope in fragments](../index.md). Calliope's example `fuel_dist.yaml`: carriers that move between nodes with no network. Calliope restates `system_balance` and the objective whole to add the distributor; here it is a term of each sum, and no base file changes. + + +```yaml +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + carriers: + description: Calliope's `carriers` — energy and commodity carriers + costs: + description: Calliope's `costs` — cost classes, such as monetary and CO2 + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + +parameters: + allow_fuel_distribution: + description: "`allow_fuel_distribution` — whether a node takes part in distributing a carrier" + dims: [nodes, carriers] + dtype: bool + fuel_import_max: + description: "`fuel_import_max` — the most of a carrier a node imports in a time step; given only where set" + dims: [nodes, carriers] + fuel_export_max: + description: "`fuel_export_max` — the most of a carrier a node exports in a time step; given only where set" + dims: [nodes, carriers] + cost_fuel_distribution: + description: "`cost_fuel_distribution` — the cost of importing one unit of a carrier, and the revenue of exporting it" + dims: [nodes, carriers, costs] + +variables: + fuel_distributor: + description: >- + `fuel_distributor` — what a node imports of a carrier, with no network + behind it; an export is negative + dims: [nodes, carriers, timesteps] + where: allow_fuel_distribution + absence: zero + +expressions: + fuel_dist_carrier_flow: + description: "`+ fuel_distributor` — the term Calliope writes into `system_balance`, by restating it whole" + expression: fuel_distributor + cost_var_fuel_distribution: + description: "`cost_var_fuel_distribution` — the cost of importing, and the revenue of exporting, a carrier" + expression: timestep_weights * fuel_distributor * cost_fuel_distribution + fuel_dist_system_cost: + description: >- + `sum(cost_var_fuel_distribution, …) * objective_cost_weights` — the term + Calliope writes into the objective, by restating it whole + expression: sum(cost_var_fuel_distribution * objective_cost_weights) + +given: + parameters: + timestep_weights: { dims: [timesteps] } + objective_cost_weights: { dims: [costs] } + expressions: + carrier_flow: { dims: [nodes, carriers, timesteps], term: fuel_dist_carrier_flow } + system_cost: { dims: [], term: fuel_dist_system_cost } + +constraints: + restrict_total_imports_and_exports: + description: >- + `restrict_total_imports_and_exports` — what the nodes import of a + carrier is what they export. Calliope's `where: fuel_distributor` over + a carrier reads as any node distributing it + dims: [carriers, timesteps] + where: count(fuel_distributor, over=nodes) >= 1 + expression: sum(fuel_distributor, over=nodes) == 0 + restrict_nodal_imports: + description: "`restrict_nodal_imports` — a node imports at most its limit" + dims: [nodes, carriers, timesteps] + where: fuel_distributor AND fuel_import_max + expression: fuel_distributor <= fuel_import_max + restrict_nodal_exports: + description: "`restrict_nodal_exports` — a node exports at most its limit" + dims: [nodes, carriers, timesteps] + where: fuel_distributor AND fuel_export_max + expression: -1 * fuel_distributor <= fuel_export_max +``` + +#### Sets + +| Symbol | Meaning | +|---|---| +| $`\mathcal{N}`$ | index $`n`$ — `nodes` — Calliope's `nodes` — the places technologies stand at | +| $`\mathcal{C}`$ | index $`c`$ — `carriers` — Calliope's `carriers` — energy and commodity carriers | +| $`\mathcal{K}`$ | index $`k`$ — `costs` — Calliope's `costs` — cost classes, such as monetary and CO2 | +| $`\mathcal{T}`$ | index $`t`$ — `timesteps` — Calliope's `timesteps` — time steps, in order | + +#### Parameters + +| Symbol | Meaning | +|---|---| +| $`\mathrm{allow\_fuel\_distribution}`$ | `allow_fuel_distribution` over $`\mathcal{N} \times \mathcal{C}`$ — `allow_fuel_distribution` — whether a node takes part in distributing a carrier | +| $`\mathrm{fuel\_import\_max}`$ | `fuel_import_max` over $`\mathcal{N} \times \mathcal{C}`$ — `fuel_import_max` — the most of a carrier a node imports in a time step; given only where set | +| $`\mathrm{fuel\_export\_max}`$ | `fuel_export_max` over $`\mathcal{N} \times \mathcal{C}`$ — `fuel_export_max` — the most of a carrier a node exports in a time step; given only where set | +| $`\mathrm{cost\_fuel\_distribution}`$ | `cost_fuel_distribution` over $`\mathcal{N} \times \mathcal{C} \times \mathcal{K}`$ — `cost_fuel_distribution` — the cost of importing one unit of a carrier, and the revenue of exporting it | + +#### Variables + +| Symbol | Meaning | +|---|---| +| $`\mathit{fuel\_distributor}`$ | `fuel_distributor` over $`\mathcal{N} \times \mathcal{C} \times \mathcal{T}`$ — `fuel_distributor` — what a node imports of a carrier, with no network behind it; an export is negative | + +#### Given + +| Symbol | Meaning | +|---|---| +| $`\mathrm{timestep\_weights}`$ | `timestep_weights` over $`\mathcal{T}`$, data another file declares | +| $`\mathrm{objective\_cost\_weights}`$ | `objective_cost_weights` over $`\mathcal{K}`$, data another file declares | +| $`\mathit{carrier\_flow}`$ | `carrier_flow` over $`\mathcal{N} \times \mathcal{C} \times \mathcal{T}`$, an expression this file adds `fuel_dist_carrier_flow` to | +| $`\mathit{system\_cost}`$ | `system_cost` (scalar), an expression this file adds `fuel_dist_system_cost` to | + +#### Definitions + +| Symbol | Meaning | +|---|---| +| $`\mathit{fuel\_dist\_carrier\_flow}`$ | `fuel_dist_carrier_flow` over $`\mathcal{N} \times \mathcal{C} \times \mathcal{T}`$ — `+ fuel_distributor` — the term Calliope writes into `system_balance`, by restating it whole | +| $`\mathit{cost\_var\_fuel\_distribution}`$ | `cost_var_fuel_distribution` over $`\mathcal{N} \times \mathcal{C} \times \mathcal{K} \times \mathcal{T}`$ — `cost_var_fuel_distribution` — the cost of importing, and the revenue of exporting, a carrier | +| $`\mathit{fuel\_dist\_system\_cost}`$ | `fuel_dist_system_cost` (scalar) — `sum(cost_var_fuel_distribution, …) * objective_cost_weights` — the term Calliope writes into the objective, by restating it whole | + +Upright is what the data supplies — a parameter such as $`\mathrm{allow\_fuel\_distribution}`$, a coordinate map, a label — and italic is what the solver chooses, such as $`\mathit{fuel\_distributor}`$. An index is italic too, being what a quantifier chooses, and a set is script. + +#### Subject to + +**`restrict_total_imports_and_exports`** + +```math +\sum_{n \in \mathcal{N}} \mathit{fuel\_distributor}_{n,c,t} = 0 \qquad \forall\, c \in \mathcal{C},\ t \in \mathcal{T} \,:\, \lvert \{ n \in \mathcal{N} \,:\, \mathit{fuel\_distributor}_{n,c,t} \text{ exists} \} \rvert \ge 1 +``` + +**`restrict_nodal_imports`** + +```math +\mathit{fuel\_distributor}_{n,c,t} \le \mathrm{fuel\_import\_max}_{n,c} \qquad \forall\, n \in \mathcal{N},\ c \in \mathcal{C},\ t \in \mathcal{T} \,:\, \mathit{fuel\_distributor}_{n,c,t} \text{ exists} \wedge \mathrm{fuel\_import\_max}_{n,c} \text{ is defined} +``` + +**`restrict_nodal_exports`** + +```math +-1 \cdot \mathit{fuel\_distributor}_{n,c,t} \le \mathrm{fuel\_export\_max}_{n,c} \qquad \forall\, n \in \mathcal{N},\ c \in \mathcal{C},\ t \in \mathcal{T} \,:\, \mathit{fuel\_distributor}_{n,c,t} \text{ exists} \wedge \mathrm{fuel\_export\_max}_{n,c} \text{ is defined} +``` + +#### Definitions + +**`fuel_dist_carrier_flow`** + +```math +\mathit{fuel\_dist\_carrier\_flow}_{n,c,t} = \mathit{fuel\_distributor}_{n,c,t} \qquad \forall\, n \in \mathcal{N},\ c \in \mathcal{C},\ t \in \mathcal{T} +``` + +**`cost_var_fuel_distribution`** + +```math +\mathit{cost\_var\_fuel\_distribution}_{n,c,k,t} = \mathrm{timestep\_weights}_{t} \cdot \mathit{fuel\_distributor}_{n,c,t} \cdot \mathrm{cost\_fuel\_distribution}_{n,c,k} \qquad \forall\, n \in \mathcal{N},\ c \in \mathcal{C},\ k \in \mathcal{K},\ t \in \mathcal{T} +``` + +**`fuel_dist_system_cost`** + +```math +\mathit{fuel\_dist\_system\_cost} = \sum_{n \in \mathcal{N},\ c \in \mathcal{C},\ k \in \mathcal{K},\ t \in \mathcal{T}} \mathit{cost\_var\_fuel\_distribution}_{n,c,k,t} \cdot \mathrm{objective\_cost\_weights}_{k} +``` + +#### Variable domains + +**`fuel_distributor`** + +```math +\mathit{fuel\_distributor}_{n,c,t} \in \mathbb{R} \qquad \forall\, n \in \mathcal{N},\ c \in \mathcal{C},\ t \in \mathcal{T} \,:\, \mathrm{allow\_fuel\_distribution}_{n,c} +``` + diff --git a/docs/examples/calliope/extensions/max_time_varying.md b/docs/examples/calliope/extensions/max_time_varying.md new file mode 100644 index 00000000..8a831db4 --- /dev/null +++ b/docs/examples/calliope/extensions/max_time_varying.md @@ -0,0 +1,73 @@ + + +# Time-varying flow limit + +An extension of [Calliope in fragments](../index.md). Calliope's example `max_time_varying.yaml`: outflow at most a share of the flow capacity that varies in time. + + +```yaml +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + +parameters: + flow_cap_max_relative_per_ts: + description: "`flow_cap_max_relative_per_ts` — the share of its flow capacity a technology may put out in a time step; given only where set" + dims: [nodes, techs, timesteps] + +given: + parameters: + flow_out_parasitic_eff: { dims: [nodes, techs, carriers, timesteps] } + variables: + flow_out: { dims: [nodes, techs, carriers, timesteps] } + flow_cap: { dims: [nodes, techs, carriers] } + +constraints: + max_time_varying_flow_cap: + description: "`max_time_varying_flow_cap` — outflow is at most a share of the flow capacity that varies in time" + dims: [nodes, techs, carriers, timesteps] + where: flow_out AND flow_cap_max_relative_per_ts + expression: flow_out <= flow_cap_max_relative_per_ts * flow_cap * flow_out_parasitic_eff +``` + +#### Sets + +| Symbol | Meaning | +|---|---| +| $`\mathcal{N}`$ | index $`n`$ — `nodes` — Calliope's `nodes` — the places technologies stand at | +| $`\mathcal{I}`$ | index $`i`$ — `techs` — Calliope's `techs` — technologies | +| $`\mathcal{C}`$ | index $`c`$ — `carriers` — Calliope's `carriers` — energy and commodity carriers | +| $`\mathcal{T}`$ | index $`t`$ — `timesteps` — Calliope's `timesteps` — time steps, in order | + +#### Parameters + +| Symbol | Meaning | +|---|---| +| $`\mathrm{flow\_cap\_max\_relative\_per\_ts}`$ | `flow_cap_max_relative_per_ts` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{T}`$ — `flow_cap_max_relative_per_ts` — the share of its flow capacity a technology may put out in a time step; given only where set | + +#### Given + +| Symbol | Meaning | +|---|---| +| $`\mathrm{flow\_out\_parasitic\_eff}`$ | `flow_out_parasitic_eff` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$, data another file declares | +| $`\mathit{flow\_out}`$ | `flow_out` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ | +| $`\mathit{flow\_cap}`$ | `flow_cap` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C}`$ | + +#### Subject to + +**`max_time_varying_flow_cap`** + +```math +\mathit{flow\_out}_{n,i,c,t} \le \mathrm{flow\_cap\_max\_relative\_per\_ts}_{n,i,t} \cdot \mathit{flow\_cap}_{n,i,c} \cdot \mathrm{flow\_out\_parasitic\_eff}_{n,i,c,t} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} \,:\, \mathit{flow\_out}_{n,i,c,t} \text{ exists} \wedge \mathrm{flow\_cap\_max\_relative\_per\_ts}_{n,i,t} \text{ is defined} +``` + diff --git a/docs/examples/calliope/extensions/milp.md b/docs/examples/calliope/extensions/milp.md new file mode 100644 index 00000000..1c703828 --- /dev/null +++ b/docs/examples/calliope/extensions/milp.md @@ -0,0 +1,581 @@ + + +# MILP + +An extension of [Calliope in fragments](../index.md). What Calliope's `milp.yaml` adds: whole units bought and run, asynchronous flow, and the capacity minimums the units scale. What it changes in the base is [the MILP patch](../variants/milp.md). The purchase cost is a term of `cost_investment`. + + +```yaml +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + costs: + description: Calliope's `costs` — cost classes, such as monetary and CO2 + +parameters: + cap_method: + description: >- + `cap_method` — `continuous` or `integer`: whether a technology's + capacity is bought in whole units. Calliope's default is + `continuous`, which is what a technology with no row reads as + dims: [nodes, techs] + dtype: str + integer_dispatch: + description: "`integer_dispatch` — whether a unit-bought technology runs in whole units" + dims: [nodes, techs] + dtype: bool + force_async_flow: + description: "`force_async_flow` — whether a technology may not take in and put out in one time step" + dims: [nodes, techs] + dtype: bool + flow_cap_per_unit: + description: "`flow_cap_per_unit` — the flow capacity of one unit; given only where set" + dims: [nodes, techs] + storage_cap_per_unit: + description: "`storage_cap_per_unit` — the storage capacity of one unit; given only where set" + dims: [nodes, techs] + purchased_units_min: + description: "`purchased_units_min` — least units bought. Calliope's default is 0, and data prep fills it" + dims: [nodes, techs] + purchased_units_max: + description: "`purchased_units_max` — most units bought. Calliope's default is `.inf`, and data prep fills it" + dims: [nodes, techs] + purchased_units_min_systemwide: + description: "`purchased_units_min_systemwide` — least units of a technology bought over every node" + dims: [techs] + purchased_units_max_systemwide: + description: "`purchased_units_max_systemwide` — most units of a technology bought over every node; given only where set" + dims: [techs] + cost_purchase: + description: "`cost_purchase` — the cost of one unit bought" + dims: [nodes, techs, costs] + cost_purchase_per_distance: + description: "`cost_purchase_per_distance` — the cost of one unit of a link bought, per unit of distance" + dims: [nodes, techs, costs] + +variables: + purchased_units: + description: "`purchased_units` — how many units of a technology are bought" + dims: [nodes, techs] + where: cap_method == integer + domain: integer + bounds: { lower: purchased_units_min, upper: purchased_units_max } + absence: zero + operating_units: + description: "`operating_units` — how many bought units run in a time step" + dims: [nodes, techs, timesteps] + where: integer_dispatch AND cap_method == integer + domain: integer + bounds: { lower: 0 } + absence: zero + async_flow_switch: + description: "`async_flow_switch` — whether a technology puts out, rather than takes in, in a time step" + dims: [nodes, techs, timesteps] + where: force_async_flow + domain: binary + absence: zero + available_flow_cap: + description: "`available_flow_cap` — the flow capacity in a time step: the whole of it where the technology runs, none where it does not" + dims: [nodes, techs, carriers, timesteps] + where: flow_cap AND integer_dispatch AND flow_cap_max AND NOT flow_cap_per_unit + bounds: { lower: 0 } + absence: zero + +expressions: + cost_investment_purchase: + description: "`cost_investment_purchase` — the investment cost of the units bought; a link's cost is split between its two ends" + dims: [nodes, techs, costs] + cases: + transmission: + when: base_tech == 'transmission' + expression: (cost_purchase + cost_purchase_per_distance * distance) * purchased_units * 0.5 + otherwise: cost_purchase * purchased_units + +given: + parameters: + base_tech: { dims: [techs], dtype: str } + distance: { dims: [techs] } + bigM: { dims: [] } + timestep_resolution: { dims: [timesteps] } + timestep_weights: { dims: [timesteps] } + flow_cap_min: { dims: [nodes, techs] } + flow_cap_max: { dims: [nodes, techs] } + flow_cap_min_systemwide: { dims: [techs, carriers] } + flow_out_min_relative: { dims: [nodes, techs, timesteps] } + flow_out_parasitic_eff: { dims: [nodes, techs, carriers, timesteps] } + storage_cap_min: { dims: [nodes, techs] } + storage_cap_max: { dims: [nodes, techs] } + area_use_min: { dims: [nodes, techs] } + source_cap_min: { dims: [nodes, techs] } + variables: + flow_cap: { dims: [nodes, techs, carriers] } + flow_out: { dims: [nodes, techs, carriers, timesteps] } + flow_in: { dims: [nodes, techs, carriers, timesteps] } + storage: { dims: [nodes, techs, timesteps] } + storage_cap: { dims: [nodes, techs] } + area_use: { dims: [nodes, techs] } + source_cap: { dims: [nodes, techs] } + expressions: + cost_investment: { dims: [nodes, techs, costs], term: cost_investment_purchase } + +constraints: + unit_commitment_milp: + description: "`unit_commitment_milp` — at most the units bought run" + dims: [nodes, techs, timesteps] + where: operating_units AND purchased_units + expression: operating_units <= purchased_units + flow_out_max_milp: + description: "`flow_out_max_milp` — outflow is at most what the running units can put out" + dims: [nodes, techs, carriers, timesteps] + where: flow_out AND operating_units AND flow_cap_per_unit + expression: flow_out <= operating_units * timestep_resolution * flow_cap_per_unit * flow_out_parasitic_eff + flow_in_max_milp: + description: "`flow_in_max_milp` — inflow is at most what the running units can take in" + dims: [nodes, techs, carriers, timesteps] + where: flow_in AND operating_units AND flow_cap_per_unit + expression: flow_in <= operating_units * timestep_resolution * flow_cap_per_unit + flow_out_min_milp_per_unit: + description: "`flow_out_min_milp` where `flow_cap_per_unit` is set — outflow is at least the running units' least share" + dims: [nodes, techs, carriers, timesteps] + where: flow_out AND operating_units AND flow_out_min_relative AND flow_cap_per_unit + expression: flow_out >= operating_units * timestep_resolution * flow_cap_per_unit * flow_out_min_relative + flow_out_min_milp_available: + description: "`flow_out_min_milp` where the available flow capacity is built — outflow is at least its least share of it" + dims: [nodes, techs, carriers, timesteps] + where: flow_out AND operating_units AND flow_out_min_relative AND available_flow_cap + expression: flow_out >= available_flow_cap * timestep_resolution * flow_out_min_relative + storage_capacity_units_milp: + description: "`storage_capacity_units_milp` — storage capacity is the units bought times the capacity of one" + dims: [nodes, techs] + where: storage_cap AND purchased_units AND storage_cap_per_unit + expression: storage_cap == purchased_units * storage_cap_per_unit + flow_capacity_units_milp: + description: "`flow_capacity_units_milp` — flow capacity is the units bought times the capacity of one" + dims: [nodes, techs, carriers] + where: flow_cap AND purchased_units AND flow_cap_per_unit + expression: flow_cap == purchased_units * flow_cap_per_unit + flow_capacity_max_purchase_milp: + description: "`flow_capacity_max_purchase_milp` where `flow_cap_max` is set — no flow capacity unless a unit is bought" + dims: [nodes, techs, carriers] + where: flow_cap AND purchased_units AND flow_cap_max + expression: flow_cap <= flow_cap_max * purchased_units + flow_capacity_max_purchase_milp_big_m: + description: "`flow_capacity_max_purchase_milp` where `flow_cap_max` is not set — the same, with `bigM` for the maximum" + dims: [nodes, techs, carriers] + where: flow_cap AND purchased_units AND NOT flow_cap_max + expression: flow_cap <= bigM * purchased_units + storage_capacity_max_purchase_milp: + description: "`storage_capacity_max_purchase_milp` — no storage capacity unless a unit is bought" + dims: [nodes, techs] + where: purchased_units AND storage_cap_max + expression: storage_cap <= storage_cap_max * purchased_units + unit_capacity_max_systemwide_milp: + description: "`unit_capacity_max_systemwide_milp` — the units of a technology bought over every node are at most its system-wide maximum" + dims: [techs] + where: count(purchased_units, over=nodes) >= 1 AND purchased_units_max_systemwide + expression: sum(purchased_units, over=nodes) <= purchased_units_max_systemwide + unit_capacity_min_systemwide_milp: + description: >- + `unit_capacity_min_systemwide_milp` — the units of a technology bought + over every node are at least its system-wide minimum. Calliope builds + it where the system-wide maximum is set, as here + dims: [techs] + where: count(purchased_units, over=nodes) >= 1 AND purchased_units_max_systemwide + expression: sum(purchased_units, over=nodes) >= purchased_units_min_systemwide + async_flow_in_milp: + description: "`async_flow_in_milp` — no inflow in a time step the switch gives to outflow" + dims: [nodes, techs, timesteps] + where: async_flow_switch + expression: sum(flow_in, over=carriers) <= (1 - async_flow_switch) * bigM + async_flow_out_milp: + description: "`async_flow_out_milp` — no outflow in a time step the switch gives to inflow" + dims: [nodes, techs, timesteps] + where: async_flow_switch + expression: sum(flow_out, over=carriers) <= async_flow_switch * bigM + available_flow_cap_continuous: + description: "`available_flow_cap_continuous` — the available flow capacity is at most the flow capacity" + dims: [nodes, techs, carriers, timesteps] + where: available_flow_cap + expression: available_flow_cap <= flow_cap + available_flow_cap_binary: + description: "`available_flow_cap_binary` — the available flow capacity is zero where no unit runs" + dims: [nodes, techs, carriers, timesteps] + where: available_flow_cap + expression: available_flow_cap <= flow_cap_max * operating_units + available_flow_cap_max_binary_continuous_switch: + description: "`available_flow_cap_max_binary_continuous_switch` — the available flow capacity is the whole flow capacity where the units run" + dims: [nodes, techs, carriers, timesteps] + where: available_flow_cap + expression: available_flow_cap >= flow_cap + (operating_units - purchased_units) * flow_cap_max + flow_capacity_minimum: + description: "`flow_capacity_minimum` where no unit is bought — flow capacity is at least its least" + dims: [nodes, techs, carriers] + where: flow_cap AND flow_cap_min AND NOT purchased_units + expression: flow_cap >= flow_cap_min + flow_capacity_minimum_purchased: + description: "`flow_capacity_minimum` where units are bought — flow capacity is at least its least, if a unit is bought" + dims: [nodes, techs, carriers] + where: flow_cap AND flow_cap_min AND purchased_units + expression: flow_cap >= flow_cap_min * purchased_units + storage_capacity_minimum: + description: "`storage_capacity_minimum` where no unit is bought — storage capacity is at least its least" + dims: [nodes, techs] + where: storage_cap_min AND NOT purchased_units + expression: storage_cap >= storage_cap_min + storage_capacity_minimum_purchased: + description: "`storage_capacity_minimum` where units are bought — storage capacity is at least its least, if a unit is bought" + dims: [nodes, techs] + where: storage_cap_min AND purchased_units + expression: storage_cap >= storage_cap_min * purchased_units + area_use_minimum: + description: "`area_use_minimum` where no unit is bought — area use is at least its least" + dims: [nodes, techs] + where: area_use_min AND NOT purchased_units + expression: area_use >= area_use_min + area_use_minimum_purchased: + description: "`area_use_minimum` where units are bought — area use is at least its least, if a unit is bought" + dims: [nodes, techs] + where: area_use_min AND purchased_units + expression: area_use >= area_use_min * purchased_units + source_capacity_minimum: + description: "`source_capacity_minimum` where no unit is bought — source capacity is at least its least" + dims: [nodes, techs] + where: base_tech == 'supply' AND source_cap_min AND NOT purchased_units + expression: source_cap >= source_cap_min + source_capacity_minimum_purchased: + description: "`source_capacity_minimum` where units are bought — source capacity is at least its least, if a unit is bought" + dims: [nodes, techs] + where: base_tech == 'supply' AND source_cap_min AND purchased_units + expression: source_cap >= source_cap_min * purchased_units + flow_capacity_systemwide_min_purchased: + description: >- + `flow_capacity_systemwide_min` where units are bought — the flow + capacity over every node is at least the system-wide minimum times the + units bought. The patch narrows the base row to where none are + dims: [techs, carriers] + where: count(flow_cap, over=nodes) >= 1 AND flow_cap_min_systemwide AND count(purchased_units, over=nodes) >= 1 + expression: sum(flow_cap, over=nodes) >= flow_cap_min_systemwide * sum(purchased_units, over=nodes) + +assumptions: + distance_only_for_transmission_milp: + description: Calliope's `distance_only_for_transmission_milp` — only a link sets a per-distance purchase cost + holds: base_tech == 'transmission' OR NOT cost_purchase_per_distance + conflicting_flow_caps: + description: Calliope's `conflicting_flow_caps` — a technology sets a capacity per unit or a capacity range, not both + holds: NOT ((flow_cap_max OR flow_cap_min) AND flow_cap_per_unit) + unit_commitment_only_for_units: + description: Calliope's `unit_commitment_only_for_units` — integer dispatch needs integer units + holds: NOT integer_dispatch OR cap_method == integer + conflicting_storage_caps: + description: Calliope's `conflicting_storage_caps` — a technology sets a storage capacity per unit or a range, not both + holds: NOT ((storage_cap_max OR storage_cap_min) AND storage_cap_per_unit) + cap_method_one_of: + description: Calliope's `one_of` on `cap_method` + holds: cap_method == continuous OR cap_method == integer + where: cap_method +``` + +#### Sets + +| Symbol | Meaning | +|---|---| +| $`\mathcal{N}`$ | index $`n`$ — `nodes` — Calliope's `nodes` — the places technologies stand at | +| $`\mathcal{I}`$ | index $`i`$ — `techs` — Calliope's `techs` — technologies | +| $`\mathcal{C}`$ | index $`c`$ — `carriers` — Calliope's `carriers` — energy and commodity carriers | +| $`\mathcal{T}`$ | index $`t`$ — `timesteps` — Calliope's `timesteps` — time steps, in order | +| $`\mathcal{K}`$ | index $`k`$ — `costs` — Calliope's `costs` — cost classes, such as monetary and CO2 | + +#### Parameters + +| Symbol | Meaning | +|---|---| +| $`\mathrm{cap\_method}`$ | `cap_method` over $`\mathcal{N} \times \mathcal{I}`$ — `cap_method` — `continuous` or `integer`: whether a technology's capacity is bought in whole units. Calliope's default is `continuous`, which is what a technology with no row reads as | +| $`\mathrm{integer\_dispatch}`$ | `integer_dispatch` over $`\mathcal{N} \times \mathcal{I}`$ — `integer_dispatch` — whether a unit-bought technology runs in whole units | +| $`\mathrm{force\_async\_flow}`$ | `force_async_flow` over $`\mathcal{N} \times \mathcal{I}`$ — `force_async_flow` — whether a technology may not take in and put out in one time step | +| $`\mathrm{flow\_cap\_per\_unit}`$ | `flow_cap_per_unit` over $`\mathcal{N} \times \mathcal{I}`$ — `flow_cap_per_unit` — the flow capacity of one unit; given only where set | +| $`\mathrm{storage}^{\mathrm{cap,per,unit}}`$ | `storage_cap_per_unit` over $`\mathcal{N} \times \mathcal{I}`$ — `storage_cap_per_unit` — the storage capacity of one unit; given only where set | +| $`\mathrm{purchased\_units\_min}`$ | `purchased_units_min` over $`\mathcal{N} \times \mathcal{I}`$ — `purchased_units_min` — least units bought. Calliope's default is 0, and data prep fills it | +| $`\mathrm{purchased\_units\_max}`$ | `purchased_units_max` over $`\mathcal{N} \times \mathcal{I}`$ — `purchased_units_max` — most units bought. Calliope's default is `.inf`, and data prep fills it | +| $`\mathrm{purchased\_units\_min\_systemwide}`$ | `purchased_units_min_systemwide` over $`\mathcal{I}`$ — `purchased_units_min_systemwide` — least units of a technology bought over every node | +| $`\mathrm{purchased\_units\_max\_systemwide}`$ | `purchased_units_max_systemwide` over $`\mathcal{I}`$ — `purchased_units_max_systemwide` — most units of a technology bought over every node; given only where set | +| $`\mathrm{cost\_purchase}`$ | `cost_purchase` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K}`$ — `cost_purchase` — the cost of one unit bought | +| $`\mathrm{cost\_purchase\_per\_distance}`$ | `cost_purchase_per_distance` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K}`$ — `cost_purchase_per_distance` — the cost of one unit of a link bought, per unit of distance | + +#### Variables + +| Symbol | Meaning | +|---|---| +| $`\mathit{purchased\_units}`$ | `purchased_units` over $`\mathcal{N} \times \mathcal{I}`$ — `purchased_units` — how many units of a technology are bought | +| $`\mathit{operating\_units}`$ | `operating_units` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{T}`$ — `operating_units` — how many bought units run in a time step | +| $`\mathit{async\_flow\_switch}`$ | `async_flow_switch` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{T}`$ — `async_flow_switch` — whether a technology puts out, rather than takes in, in a time step | +| $`\mathit{available\_flow\_cap}`$ | `available_flow_cap` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ — `available_flow_cap` — the flow capacity in a time step: the whole of it where the technology runs, none where it does not | + +#### Given + +| Symbol | Meaning | +|---|---| +| $`\mathrm{base\_tech}`$ | `base_tech` over $`\mathcal{I}`$, data another file declares | +| $`\mathrm{distance}`$ | `distance` over $`\mathcal{I}`$, data another file declares | +| $`\mathrm{bigM}`$ | `bigM` (scalar), data another file declares | +| $`\mathrm{timestep\_resolution}`$ | `timestep_resolution` over $`\mathcal{T}`$, data another file declares | +| $`\mathrm{timestep\_weights}`$ | `timestep_weights` over $`\mathcal{T}`$, data another file declares | +| $`\mathrm{flow\_cap\_min}`$ | `flow_cap_min` over $`\mathcal{N} \times \mathcal{I}`$, data another file declares | +| $`\mathrm{flow\_cap\_max}`$ | `flow_cap_max` over $`\mathcal{N} \times \mathcal{I}`$, data another file declares | +| $`\mathrm{flow\_cap\_min\_systemwide}`$ | `flow_cap_min_systemwide` over $`\mathcal{I} \times \mathcal{C}`$, data another file declares | +| $`\mathrm{flow\_out\_min\_relative}`$ | `flow_out_min_relative` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{T}`$, data another file declares | +| $`\mathrm{flow\_out\_parasitic\_eff}`$ | `flow_out_parasitic_eff` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$, data another file declares | +| $`\mathrm{storage}^{\mathrm{cap,min}}`$ | `storage_cap_min` over $`\mathcal{N} \times \mathcal{I}`$, data another file declares | +| $`\mathrm{storage}^{\mathrm{cap,max}}`$ | `storage_cap_max` over $`\mathcal{N} \times \mathcal{I}`$, data another file declares | +| $`\mathrm{area\_use\_min}`$ | `area_use_min` over $`\mathcal{N} \times \mathcal{I}`$, data another file declares | +| $`\mathrm{source\_cap\_min}`$ | `source_cap_min` over $`\mathcal{N} \times \mathcal{I}`$, data another file declares | +| $`\mathit{flow\_cap}`$ | `flow_cap` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C}`$ | +| $`\mathit{flow\_out}`$ | `flow_out` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ | +| $`\mathit{flow\_in}`$ | `flow_in` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ | +| $`\mathit{storage}`$ | `storage` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{T}`$ | +| $`\mathit{storage}^{\mathrm{cap}}`$ | `storage_cap` over $`\mathcal{N} \times \mathcal{I}`$ | +| $`\mathit{area\_use}`$ | `area_use` over $`\mathcal{N} \times \mathcal{I}`$ | +| $`\mathit{source\_cap}`$ | `source_cap` over $`\mathcal{N} \times \mathcal{I}`$ | +| $`\mathit{cost\_investment}`$ | `cost_investment` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K}`$, an expression this file adds `cost_investment_purchase` to | + +#### Definitions + +| Symbol | Meaning | +|---|---| +| $`\mathit{cost\_investment\_purchase}`$ | `cost_investment_purchase` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K}`$ — `cost_investment_purchase` — the investment cost of the units bought; a link's cost is split between its two ends | + +Upright is what the data supplies — a parameter such as $`\mathrm{cap\_method}`$, a coordinate map, a label — and italic is what the solver chooses, such as $`\mathit{purchased\_units}`$. An index is italic too, being what a quantifier chooses, and a set is script. + +#### Subject to + +**`unit_commitment_milp`** + +```math +\mathit{operating\_units}_{n,i,t} \le \mathit{purchased\_units}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T} \,:\, \mathit{operating\_units}_{n,i,t} \text{ exists} \wedge \mathit{purchased\_units}_{n,i} \text{ exists} +``` + +**`flow_out_max_milp`** + +```math +\mathit{flow\_out}_{n,i,c,t} \le \mathit{operating\_units}_{n,i,t} \cdot \mathrm{timestep\_resolution}_{t} \cdot \mathrm{flow\_cap\_per\_unit}_{n,i} \cdot \mathrm{flow\_out\_parasitic\_eff}_{n,i,c,t} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} \,:\, \mathit{flow\_out}_{n,i,c,t} \text{ exists} \wedge \mathit{operating\_units}_{n,i,t} \text{ exists} \wedge \mathrm{flow\_cap\_per\_unit}_{n,i} \text{ is defined} +``` + +**`flow_in_max_milp`** + +```math +\mathit{flow\_in}_{n,i,c,t} \le \mathit{operating\_units}_{n,i,t} \cdot \mathrm{timestep\_resolution}_{t} \cdot \mathrm{flow\_cap\_per\_unit}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} \,:\, \mathit{flow\_in}_{n,i,c,t} \text{ exists} \wedge \mathit{operating\_units}_{n,i,t} \text{ exists} \wedge \mathrm{flow\_cap\_per\_unit}_{n,i} \text{ is defined} +``` + +**`flow_out_min_milp_per_unit`** + +```math +\mathit{flow\_out}_{n,i,c,t} \ge \mathit{operating\_units}_{n,i,t} \cdot \mathrm{timestep\_resolution}_{t} \cdot \mathrm{flow\_cap\_per\_unit}_{n,i} \cdot \mathrm{flow\_out\_min\_relative}_{n,i,t} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} \,:\, \mathit{flow\_out}_{n,i,c,t} \text{ exists} \wedge \mathit{operating\_units}_{n,i,t} \text{ exists} \wedge \mathrm{flow\_out\_min\_relative}_{n,i,t} \text{ is defined} \wedge \mathrm{flow\_cap\_per\_unit}_{n,i} \text{ is defined} +``` + +**`flow_out_min_milp_available`** + +```math +\mathit{flow\_out}_{n,i,c,t} \ge \mathit{available\_flow\_cap}_{n,i,c,t} \cdot \mathrm{timestep\_resolution}_{t} \cdot \mathrm{flow\_out\_min\_relative}_{n,i,t} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} \,:\, \mathit{flow\_out}_{n,i,c,t} \text{ exists} \wedge \mathit{operating\_units}_{n,i,t} \text{ exists} \wedge \mathrm{flow\_out\_min\_relative}_{n,i,t} \text{ is defined} \wedge \mathit{available\_flow\_cap}_{n,i,c,t} \text{ exists} +``` + +**`storage_capacity_units_milp`** + +```math +\mathit{storage}^{\mathrm{cap}}_{n,i} = \mathit{purchased\_units}_{n,i} \cdot \mathrm{storage}^{\mathrm{cap,per,unit}}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I} \,:\, \mathit{storage}^{\mathrm{cap}}_{n,i} \text{ exists} \wedge \mathit{purchased\_units}_{n,i} \text{ exists} \wedge \mathrm{storage}^{\mathrm{cap,per,unit}}_{n,i} \text{ is defined} +``` + +**`flow_capacity_units_milp`** + +```math +\mathit{flow\_cap}_{n,i,c} = \mathit{purchased\_units}_{n,i} \cdot \mathrm{flow\_cap\_per\_unit}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C} \,:\, \mathit{flow\_cap}_{n,i,c} \text{ exists} \wedge \mathit{purchased\_units}_{n,i} \text{ exists} \wedge \mathrm{flow\_cap\_per\_unit}_{n,i} \text{ is defined} +``` + +**`flow_capacity_max_purchase_milp`** + +```math +\mathit{flow\_cap}_{n,i,c} \le \mathrm{flow\_cap\_max}_{n,i} \cdot \mathit{purchased\_units}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C} \,:\, \mathit{flow\_cap}_{n,i,c} \text{ exists} \wedge \mathit{purchased\_units}_{n,i} \text{ exists} \wedge \mathrm{flow\_cap\_max}_{n,i} \text{ is defined} +``` + +**`flow_capacity_max_purchase_milp_big_m`** + +```math +\mathit{flow\_cap}_{n,i,c} \le \mathrm{bigM} \cdot \mathit{purchased\_units}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C} \,:\, \mathit{flow\_cap}_{n,i,c} \text{ exists} \wedge \mathit{purchased\_units}_{n,i} \text{ exists} \wedge \neg \left( \mathrm{flow\_cap\_max}_{n,i} \text{ is defined} \right) +``` + +**`storage_capacity_max_purchase_milp`** + +```math +\mathit{storage}^{\mathrm{cap}}_{n,i} \le \mathrm{storage}^{\mathrm{cap,max}}_{n,i} \cdot \mathit{purchased\_units}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I} \,:\, \mathit{purchased\_units}_{n,i} \text{ exists} \wedge \mathrm{storage}^{\mathrm{cap,max}}_{n,i} \text{ is defined} +``` + +**`unit_capacity_max_systemwide_milp`** + +```math +\sum_{n \in \mathcal{N}} \mathit{purchased\_units}_{n,i} \le \mathrm{purchased\_units\_max\_systemwide}_{i} \qquad \forall\, i \in \mathcal{I} \,:\, \lvert \{ n \in \mathcal{N} \,:\, \mathit{purchased\_units}_{n,i} \text{ exists} \} \rvert \ge 1 \wedge \mathrm{purchased\_units\_max\_systemwide}_{i} \text{ is defined} +``` + +**`unit_capacity_min_systemwide_milp`** + +```math +\sum_{n \in \mathcal{N}} \mathit{purchased\_units}_{n,i} \ge \mathrm{purchased\_units\_min\_systemwide}_{i} \qquad \forall\, i \in \mathcal{I} \,:\, \lvert \{ n \in \mathcal{N} \,:\, \mathit{purchased\_units}_{n,i} \text{ exists} \} \rvert \ge 1 \wedge \mathrm{purchased\_units\_max\_systemwide}_{i} \text{ is defined} +``` + +**`async_flow_in_milp`** + +```math +\sum_{c \in \mathcal{C}} \mathit{flow\_in}_{n,i,c,t} \le \left( 1 - \mathit{async\_flow\_switch}_{n,i,t} \right) \cdot \mathrm{bigM} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T} \,:\, \mathit{async\_flow\_switch}_{n,i,t} \text{ exists} +``` + +**`async_flow_out_milp`** + +```math +\sum_{c \in \mathcal{C}} \mathit{flow\_out}_{n,i,c,t} \le \mathit{async\_flow\_switch}_{n,i,t} \cdot \mathrm{bigM} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T} \,:\, \mathit{async\_flow\_switch}_{n,i,t} \text{ exists} +``` + +**`available_flow_cap_continuous`** + +```math +\mathit{available\_flow\_cap}_{n,i,c,t} \le \mathit{flow\_cap}_{n,i,c} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} \,:\, \mathit{available\_flow\_cap}_{n,i,c,t} \text{ exists} +``` + +**`available_flow_cap_binary`** + +```math +\mathit{available\_flow\_cap}_{n,i,c,t} \le \mathrm{flow\_cap\_max}_{n,i} \cdot \mathit{operating\_units}_{n,i,t} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} \,:\, \mathit{available\_flow\_cap}_{n,i,c,t} \text{ exists} +``` + +**`available_flow_cap_max_binary_continuous_switch`** + +```math +\mathit{available\_flow\_cap}_{n,i,c,t} \ge \mathit{flow\_cap}_{n,i,c} + \left( \mathit{operating\_units}_{n,i,t} - \mathit{purchased\_units}_{n,i} \right) \cdot \mathrm{flow\_cap\_max}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} \,:\, \mathit{available\_flow\_cap}_{n,i,c,t} \text{ exists} +``` + +**`flow_capacity_minimum`** + +```math +\mathit{flow\_cap}_{n,i,c} \ge \mathrm{flow\_cap\_min}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C} \,:\, \mathit{flow\_cap}_{n,i,c} \text{ exists} \wedge \mathrm{flow\_cap\_min}_{n,i} \text{ is defined} \wedge \neg \left( \mathit{purchased\_units}_{n,i} \text{ exists} \right) +``` + +**`flow_capacity_minimum_purchased`** + +```math +\mathit{flow\_cap}_{n,i,c} \ge \mathrm{flow\_cap\_min}_{n,i} \cdot \mathit{purchased\_units}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C} \,:\, \mathit{flow\_cap}_{n,i,c} \text{ exists} \wedge \mathrm{flow\_cap\_min}_{n,i} \text{ is defined} \wedge \mathit{purchased\_units}_{n,i} \text{ exists} +``` + +**`storage_capacity_minimum`** + +```math +\mathit{storage}^{\mathrm{cap}}_{n,i} \ge \mathrm{storage}^{\mathrm{cap,min}}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I} \,:\, \mathrm{storage}^{\mathrm{cap,min}}_{n,i} \text{ is defined} \wedge \neg \left( \mathit{purchased\_units}_{n,i} \text{ exists} \right) +``` + +**`storage_capacity_minimum_purchased`** + +```math +\mathit{storage}^{\mathrm{cap}}_{n,i} \ge \mathrm{storage}^{\mathrm{cap,min}}_{n,i} \cdot \mathit{purchased\_units}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I} \,:\, \mathrm{storage}^{\mathrm{cap,min}}_{n,i} \text{ is defined} \wedge \mathit{purchased\_units}_{n,i} \text{ exists} +``` + +**`area_use_minimum`** + +```math +\mathit{area\_use}_{n,i} \ge \mathrm{area\_use\_min}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I} \,:\, \mathrm{area\_use\_min}_{n,i} \text{ is defined} \wedge \neg \left( \mathit{purchased\_units}_{n,i} \text{ exists} \right) +``` + +**`area_use_minimum_purchased`** + +```math +\mathit{area\_use}_{n,i} \ge \mathrm{area\_use\_min}_{n,i} \cdot \mathit{purchased\_units}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I} \,:\, \mathrm{area\_use\_min}_{n,i} \text{ is defined} \wedge \mathit{purchased\_units}_{n,i} \text{ exists} +``` + +**`source_capacity_minimum`** + +```math +\mathit{source\_cap}_{n,i} \ge \mathrm{source\_cap\_min}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I} \,:\, \mathrm{base\_tech}_{i} = \text{'}\mathrm{supply}\text{'} \wedge \mathrm{source\_cap\_min}_{n,i} \text{ is defined} \wedge \neg \left( \mathit{purchased\_units}_{n,i} \text{ exists} \right) +``` + +**`source_capacity_minimum_purchased`** + +```math +\mathit{source\_cap}_{n,i} \ge \mathrm{source\_cap\_min}_{n,i} \cdot \mathit{purchased\_units}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I} \,:\, \mathrm{base\_tech}_{i} = \text{'}\mathrm{supply}\text{'} \wedge \mathrm{source\_cap\_min}_{n,i} \text{ is defined} \wedge \mathit{purchased\_units}_{n,i} \text{ exists} +``` + +**`flow_capacity_systemwide_min_purchased`** + +```math +\sum_{n \in \mathcal{N}} \mathit{flow\_cap}_{n,i,c} \ge \mathrm{flow\_cap\_min\_systemwide}_{i,c} \cdot \left( \sum_{n \in \mathcal{N}} \mathit{purchased\_units}_{n,i} \right) \qquad \forall\, i \in \mathcal{I},\ c \in \mathcal{C} \,:\, \lvert \{ n \in \mathcal{N} \,:\, \mathit{flow\_cap}_{n,i,c} \text{ exists} \} \rvert \ge 1 \wedge \mathrm{flow\_cap\_min\_systemwide}_{i,c} \text{ is defined} \wedge \lvert \{ n \in \mathcal{N} \,:\, \mathit{purchased\_units}_{n,i} \text{ exists} \} \rvert \ge 1 +``` + +#### Definitions + +**`cost_investment_purchase`** + +```math +\mathit{cost\_investment\_purchase}_{n,i,k} = \begin{cases} \left( \mathrm{cost\_purchase}_{n,i,k} + \mathrm{cost\_purchase\_per\_distance}_{n,i,k} \cdot \mathrm{distance}_{i} \right) \cdot \mathit{purchased\_units}_{n,i} \cdot 0.5 & \text{if } \mathrm{base\_tech}_{i} = \text{'}\mathrm{transmission}\text{'} \\ \mathrm{cost\_purchase}_{n,i,k} \cdot \mathit{purchased\_units}_{n,i} & \text{otherwise} \end{cases} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ k \in \mathcal{K} +``` + +#### Variable domains + +**`purchased_units`** + +```math +\mathrm{purchased\_units\_min}_{n,i} \le \mathit{purchased\_units}_{n,i} \le \mathrm{purchased\_units\_max}_{n,i}, \mathit{purchased\_units}_{n,i} \in \mathbb{Z} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I} \,:\, \mathrm{cap\_method}_{n,i} = \text{'}\mathrm{integer}\text{'} +``` + +**`operating_units`** + +```math +\mathit{operating\_units}_{n,i,t} \ge 0, \mathit{operating\_units}_{n,i,t} \in \mathbb{Z} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T} \,:\, \mathrm{integer\_dispatch}_{n,i} \wedge \mathrm{cap\_method}_{n,i} = \text{'}\mathrm{integer}\text{'} +``` + +**`async_flow_switch`** + +```math +\mathit{async\_flow\_switch}_{n,i,t} \in \{0, 1\} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T} \,:\, \mathrm{force\_async\_flow}_{n,i} +``` + +**`available_flow_cap`** + +```math +\mathit{available\_flow\_cap}_{n,i,c,t} \ge 0 \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} \,:\, \mathit{flow\_cap}_{n,i,c} \text{ exists} \wedge \mathrm{integer\_dispatch}_{n,i} \wedge \mathrm{flow\_cap\_max}_{n,i} \text{ is defined} \wedge \neg \left( \mathrm{flow\_cap\_per\_unit}_{n,i} \text{ is defined} \right) +``` + +#### Assumptions + +**`distance_only_for_transmission_milp`** + +```math +\mathrm{base\_tech}_{i} = \text{'}\mathrm{transmission}\text{'} \vee \neg \left( \mathrm{cost\_purchase\_per\_distance}_{n,i,k} \text{ is defined} \right) \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ k \in \mathcal{K} +``` + +**`conflicting_flow_caps`** + +```math +\neg \left( \left( \mathrm{flow\_cap\_max}_{n,i} \text{ is defined} \vee \mathrm{flow\_cap\_min}_{n,i} \text{ is defined} \right) \wedge \mathrm{flow\_cap\_per\_unit}_{n,i} \text{ is defined} \right) \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I} +``` + +**`unit_commitment_only_for_units`** + +```math +\neg \mathrm{integer\_dispatch}_{n,i} \vee \mathrm{cap\_method}_{n,i} = \text{'}\mathrm{integer}\text{'} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I} +``` + +**`conflicting_storage_caps`** + +```math +\neg \left( \left( \mathrm{storage}^{\mathrm{cap,max}}_{n,i} \text{ is defined} \vee \mathrm{storage}^{\mathrm{cap,min}}_{n,i} \text{ is defined} \right) \wedge \mathrm{storage}^{\mathrm{cap,per,unit}}_{n,i} \text{ is defined} \right) \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I} +``` + +**`cap_method_one_of`** + +```math +\mathrm{cap\_method}_{n,i} = \text{'}\mathrm{continuous}\text{'} \vee \mathrm{cap\_method}_{n,i} = \text{'}\mathrm{integer}\text{'} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I} \,:\, \mathrm{cap\_method}_{n,i} \text{ is defined} +``` + diff --git a/docs/examples/calliope/extensions/monthly_peak_flow_charge.md b/docs/examples/calliope/extensions/monthly_peak_flow_charge.md new file mode 100644 index 00000000..31f546cb --- /dev/null +++ b/docs/examples/calliope/extensions/monthly_peak_flow_charge.md @@ -0,0 +1,140 @@ + + +# Monthly peak flow charge + +An extension of [Calliope in fragments](../index.md). Calliope's example `monthly_peak_flow_charge.yaml`: a cost on the peak outflow of each month. Calliope restates `cost_operation_fixed` to add it; here it is a term. The month of a time step is a relation, so the row is one per time step, not one per time step and month. + + +```yaml +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + costs: + description: Calliope's `costs` — cost classes, such as monetary and CO2 + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + months: + description: Calliope's `months` — the months of the year + dtype: int + +relations: + lookup_month: + description: >- + `lookup_month` — the month a time step falls in. Calliope ships it as a + boolean table over time step and month, and builds its row over both; + as a relation the row is one per time step + key: timesteps + values: months + +parameters: + monthly_peak_mode: + description: "`monthly_peak_mode` — whether a technology's peak outflow in a month is priced" + dims: [nodes, techs, carriers] + dtype: bool + cost_month_peak: + description: "`cost_month_peak` — the cost of one unit of peak outflow in a month" + dims: [nodes, techs, costs] + +variables: + flow_peak_month: + description: "`flow_peak_month` — a technology's peak outflow in a month" + dims: [nodes, techs, carriers, months] + where: carrier_out AND monthly_peak_mode + bounds: { lower: 0, upper: flow_cap_max } + absence: zero + +expressions: + cost_month_peak_charge: + description: "`sum(cost_month_peak * flow_peak_month, over=[carriers, months])` — the term Calliope writes into `cost_operation_fixed`, by restating it whole" + expression: sum(sum(cost_month_peak * flow_peak_month, over=carriers), over=months) + +given: + parameters: + carrier_out: { dims: [nodes, techs, carriers], dtype: bool } + flow_cap_max: { dims: [nodes, techs] } + variables: + flow_out: { dims: [nodes, techs, carriers, timesteps] } + expressions: + cost_operation_fixed: { dims: [nodes, techs, costs], term: cost_month_peak_charge } + +constraints: + set_peak_month_flow: + description: "`set_peak_month_flow` — the peak outflow in a month is at least the outflow in each of its time steps" + dims: [nodes, techs, carriers, timesteps] + where: at(flow_peak_month, by=lookup_month, over=months, into=timesteps) + expression: flow_out <= at(flow_peak_month, by=lookup_month, over=months, into=timesteps) +``` + +#### Sets + +| Symbol | Meaning | +|---|---| +| $`\mathcal{N}`$ | index $`n`$ — `nodes` — Calliope's `nodes` — the places technologies stand at | +| $`\mathcal{I}`$ | index $`i`$ — `techs` — Calliope's `techs` — technologies | +| $`\mathcal{C}`$ | index $`c`$ — `carriers` — Calliope's `carriers` — energy and commodity carriers | +| $`\mathcal{K}`$ | index $`k`$ — `costs` — Calliope's `costs` — cost classes, such as monetary and CO2 | +| $`\mathcal{T}`$ | index $`t`$ — `timesteps` with $`\mathrm{lookup\_month}: \mathcal{T} \to \mathcal{M}`$ — Calliope's `timesteps` — time steps, in order | +| $`\mathcal{M}`$ | index $`m`$ — `months` with $`\mathrm{lookup\_month}: \mathcal{T} \to \mathcal{M}`$ — Calliope's `months` — the months of the year | + +#### Parameters + +| Symbol | Meaning | +|---|---| +| $`\mathrm{monthly\_peak\_mode}`$ | `monthly_peak_mode` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C}`$ — `monthly_peak_mode` — whether a technology's peak outflow in a month is priced | +| $`\mathrm{cost\_month\_peak}`$ | `cost_month_peak` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K}`$ — `cost_month_peak` — the cost of one unit of peak outflow in a month | + +#### Variables + +| Symbol | Meaning | +|---|---| +| $`\mathit{flow\_peak\_month}`$ | `flow_peak_month` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{M}`$ — `flow_peak_month` — a technology's peak outflow in a month | + +#### Given + +| Symbol | Meaning | +|---|---| +| $`\mathrm{carrier\_out}`$ | `carrier_out` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C}`$, data another file declares | +| $`\mathrm{flow\_cap\_max}`$ | `flow_cap_max` over $`\mathcal{N} \times \mathcal{I}`$, data another file declares | +| $`\mathit{flow\_out}`$ | `flow_out` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ | +| $`\mathit{cost\_operation\_fixed}`$ | `cost_operation_fixed` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K}`$, an expression this file adds `cost_month_peak_charge` to | + +#### Definitions + +| Symbol | Meaning | +|---|---| +| $`\mathit{cost\_month\_peak\_charge}`$ | `cost_month_peak_charge` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K}`$ — `sum(cost_month_peak * flow_peak_month, over=[carriers, months])` — the term Calliope writes into `cost_operation_fixed`, by restating it whole | + +Upright is what the data supplies — a parameter such as $`\mathrm{monthly\_peak\_mode}`$, a coordinate map, a label — and italic is what the solver chooses, such as $`\mathit{flow\_peak\_month}`$. An index is italic too, being what a quantifier chooses, and a set is script. + +#### Subject to + +**`set_peak_month_flow`** + +```math +\mathit{flow\_out}_{n,i,c,t} \le \mathit{flow\_peak\_month}_{n,i,c,\mathrm{lookup\_month}(t)} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} \,:\, \mathit{flow\_peak\_month}_{n,i,c,\mathrm{lookup\_month}(t)} \text{ exists} +``` + +#### Definitions + +**`cost_month_peak_charge`** + +```math +\mathit{cost\_month\_peak\_charge}_{n,i,k} = \sum_{m \in \mathcal{M}} \sum_{c \in \mathcal{C}} \mathrm{cost\_month\_peak}_{n,i,k} \cdot \mathit{flow\_peak\_month}_{n,i,c,m} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ k \in \mathcal{K} +``` + +#### Variable domains + +**`flow_peak_month`** + +```math +0 \le \mathit{flow\_peak\_month}_{n,i,c,m} \le \mathrm{flow\_cap\_max}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ m \in \mathcal{M} \,:\, \mathrm{carrier\_out}_{n,i,c} \wedge \mathrm{monthly\_peak\_mode}_{n,i,c} +``` + diff --git a/docs/examples/calliope/extensions/net_import_share.md b/docs/examples/calliope/extensions/net_import_share.md new file mode 100644 index 00000000..0ce17f53 --- /dev/null +++ b/docs/examples/calliope/extensions/net_import_share.md @@ -0,0 +1,192 @@ + + +# Net import share + +An extension of [Calliope in fragments](../index.md). Calliope's example `net_import_share.yaml`: imports over transmission at most a share of a node's own balance, per time step, per year, and over a group of nodes. + + +```yaml +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + +parameters: + net_import_share: + description: >- + `net_import_share` — the share of a node's flows that imports may make + up. Calliope's default is 1, and data prep fills it. Calliope reads it + per node and in a row over a group of nodes; a parameter here has one + shape, so it is one number + dims: [] + +expressions: + flow_out_transmission_techs: + description: "`flow_out_transmission_techs` — the outflow of transmission technologies, that is, imports" + dims: [nodes, techs, carriers, timesteps] + cases: + transmission: + when: carrier_out AND base_tech == 'transmission' + expression: flow_out + otherwise: 0 + electricity_imports: + description: "`flow_out_transmission_techs[carriers=electricity]`" + dims: [nodes, techs, carriers, timesteps] + cases: + electricity: + when: carriers == electricity + expression: flow_out_transmission_techs + otherwise: 0 + electricity_balance: + description: "`$total_energy_balance` — the outflow of electricity at a node, less its inflow" + dims: [nodes, techs, carriers, timesteps] + cases: + electricity: + when: carriers == electricity + expression: flow_out - flow_in + otherwise: 0 + node_group_heat_imports: + description: "`flow_out_transmission_techs[nodes=$node_group, carriers=$carrier]` — heat imports at nodes `a` and `c`" + dims: [nodes, techs, carriers, timesteps] + cases: + group: + when: (nodes == 'a' OR nodes == 'c') AND carriers == heat + expression: flow_out_transmission_techs + otherwise: 0 + node_group_heat_balance: + description: "`$total_energy_balance` of the node group — the outflow of heat at nodes `a` and `c`, less its inflow" + dims: [nodes, techs, carriers, timesteps] + cases: + group: + when: (nodes == 'a' OR nodes == 'c') AND carriers == heat + expression: flow_out - flow_in + otherwise: 0 + +given: + parameters: + base_tech: { dims: [techs], dtype: str } + carrier_out: { dims: [nodes, techs, carriers], dtype: bool } + variables: + flow_out: { dims: [nodes, techs, carriers, timesteps] } + flow_in: { dims: [nodes, techs, carriers, timesteps] } + +constraints: + net_import_share_max: + description: >- + `net_import_share_max` — electricity imports at a node are at most + their share of its electricity balance in each time step. Calliope's + `where: any(flow_out_transmission_techs, over=techs)` reads as a link + at the node putting out any carrier + dims: [nodes, timesteps] + where: count(count(carrier_out, over=carriers) >= 1 AND base_tech == 'transmission', over=techs) >= 1 + expression: >- + net_import_share * sum(sum(electricity_imports, over=techs), over=carriers) + <= sum(sum(electricity_balance, over=techs), over=carriers) + net_annual_import_share_max: + description: "`net_annual_import_share_max` — electricity imports at a node are at most their share of its electricity balance over the year" + dims: [nodes] + where: count(count(carrier_out, over=carriers) >= 1 AND base_tech == 'transmission', over=techs) >= 1 + expression: >- + net_import_share * sum(sum(sum(electricity_imports, over=techs), over=carriers), over=timesteps) + <= sum(sum(sum(electricity_balance, over=techs), over=carriers), over=timesteps) + net_annual_import_share_max_node_group: + description: "`net_annual_import_share_max_node_group` — heat imports at nodes `a` and `c` are at most their share of the group's heat balance over the year" + dims: [] + expression: net_import_share * sum(node_group_heat_imports) <= sum(node_group_heat_balance) +``` + +#### Sets + +| Symbol | Meaning | +|---|---| +| $`\mathcal{N}`$ | index $`n`$ — `nodes` — Calliope's `nodes` — the places technologies stand at | +| $`\mathcal{I}`$ | index $`i`$ — `techs` — Calliope's `techs` — technologies | +| $`\mathcal{C}`$ | index $`c`$ — `carriers` — Calliope's `carriers` — energy and commodity carriers | +| $`\mathcal{T}`$ | index $`t`$ — `timesteps` — Calliope's `timesteps` — time steps, in order | + +#### Parameters + +| Symbol | Meaning | +|---|---| +| $`\mathrm{net\_import\_share}`$ | `net_import_share` (scalar) — `net_import_share` — the share of a node's flows that imports may make up. Calliope's default is 1, and data prep fills it. Calliope reads it per node and in a row over a group of nodes; a parameter here has one shape, so it is one number | + +#### Given + +| Symbol | Meaning | +|---|---| +| $`\mathrm{base\_tech}`$ | `base_tech` over $`\mathcal{I}`$, data another file declares | +| $`\mathrm{carrier\_out}`$ | `carrier_out` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C}`$, data another file declares | +| $`\mathit{flow\_out}`$ | `flow_out` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ | +| $`\mathit{flow\_in}`$ | `flow_in` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ | + +#### Definitions + +| Symbol | Meaning | +|---|---| +| $`\mathit{flow\_out\_transmission\_techs}`$ | `flow_out_transmission_techs` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ — `flow_out_transmission_techs` — the outflow of transmission technologies, that is, imports | +| $`\mathit{electricity\_imports}`$ | `electricity_imports` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ — `flow_out_transmission_techs[carriers=electricity]` | +| $`\mathit{electricity\_balance}`$ | `electricity_balance` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ — `$total_energy_balance` — the outflow of electricity at a node, less its inflow | +| $`\mathit{node\_group\_heat\_imports}`$ | `node_group_heat_imports` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ — `flow_out_transmission_techs[nodes=$node_group, carriers=$carrier]` — heat imports at nodes `a` and `c` | +| $`\mathit{node\_group\_heat\_balance}`$ | `node_group_heat_balance` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ — `$total_energy_balance` of the node group — the outflow of heat at nodes `a` and `c`, less its inflow | + +#### Subject to + +**`net_import_share_max`** + +```math +\mathrm{net\_import\_share} \cdot \left( \sum_{c \in \mathcal{C}} \sum_{i \in \mathcal{I}} \mathit{electricity\_imports}_{n,i,c,t} \right) \le \sum_{c \in \mathcal{C}} \sum_{i \in \mathcal{I}} \mathit{electricity\_balance}_{n,i,c,t} \qquad \forall\, n \in \mathcal{N},\ t \in \mathcal{T} \,:\, \lvert \{ i \in \mathcal{I} \,:\, \lvert \{ c \in \mathcal{C} \,:\, \mathrm{carrier\_out}_{n,i,c} \} \rvert \ge 1 \wedge \mathrm{base\_tech}_{i} = \text{'}\mathrm{transmission}\text{'} \} \rvert \ge 1 +``` + +**`net_annual_import_share_max`** + +```math +\mathrm{net\_import\_share} \cdot \left( \sum_{t \in \mathcal{T}} \sum_{c \in \mathcal{C}} \sum_{i \in \mathcal{I}} \mathit{electricity\_imports}_{n,i,c,t} \right) \le \sum_{t \in \mathcal{T}} \sum_{c \in \mathcal{C}} \sum_{i \in \mathcal{I}} \mathit{electricity\_balance}_{n,i,c,t} \qquad \forall\, n \in \mathcal{N} \,:\, \lvert \{ i \in \mathcal{I} \,:\, \lvert \{ c \in \mathcal{C} \,:\, \mathrm{carrier\_out}_{n,i,c} \} \rvert \ge 1 \wedge \mathrm{base\_tech}_{i} = \text{'}\mathrm{transmission}\text{'} \} \rvert \ge 1 +``` + +**`net_annual_import_share_max_node_group`** + +```math +\mathrm{net\_import\_share} \cdot \left( \sum_{n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T}} \mathit{node\_group\_heat\_imports}_{n,i,c,t} \right) \le \sum_{n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T}} \mathit{node\_group\_heat\_balance}_{n,i,c,t} +``` + +#### Definitions + +**`flow_out_transmission_techs`** + +```math +\mathit{flow\_out\_transmission\_techs}_{n,i,c,t} = \begin{cases} \mathit{flow\_out}_{n,i,c,t} & \text{if } \mathrm{carrier\_out}_{n,i,c} \wedge \mathrm{base\_tech}_{i} = \text{'}\mathrm{transmission}\text{'} \\ 0 & \text{otherwise} \end{cases} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} +``` + +**`electricity_imports`** + +```math +\mathit{electricity\_imports}_{n,i,c,t} = \begin{cases} \mathit{flow\_out\_transmission\_techs}_{n,i,c,t} & \text{if } c = \text{'}\mathrm{electricity}\text{'} \\ 0 & \text{otherwise} \end{cases} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} +``` + +**`electricity_balance`** + +```math +\mathit{electricity\_balance}_{n,i,c,t} = \begin{cases} \mathit{flow\_out}_{n,i,c,t} - \mathit{flow\_in}_{n,i,c,t} & \text{if } c = \text{'}\mathrm{electricity}\text{'} \\ 0 & \text{otherwise} \end{cases} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} +``` + +**`node_group_heat_imports`** + +```math +\mathit{node\_group\_heat\_imports}_{n,i,c,t} = \begin{cases} \mathit{flow\_out\_transmission\_techs}_{n,i,c,t} & \text{if } \left( n = \text{'}\mathrm{a}\text{'} \vee n = \text{'}\mathrm{c}\text{'} \right) \wedge c = \text{'}\mathrm{heat}\text{'} \\ 0 & \text{otherwise} \end{cases} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} +``` + +**`node_group_heat_balance`** + +```math +\mathit{node\_group\_heat\_balance}_{n,i,c,t} = \begin{cases} \mathit{flow\_out}_{n,i,c,t} - \mathit{flow\_in}_{n,i,c,t} & \text{if } \left( n = \text{'}\mathrm{a}\text{'} \vee n = \text{'}\mathrm{c}\text{'} \right) \wedge c = \text{'}\mathrm{heat}\text{'} \\ 0 & \text{otherwise} \end{cases} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} +``` + diff --git a/docs/examples/calliope/extensions/piecewise_linear_costs.md b/docs/examples/calliope/extensions/piecewise_linear_costs.md new file mode 100644 index 00000000..b4472b29 --- /dev/null +++ b/docs/examples/calliope/extensions/piecewise_linear_costs.md @@ -0,0 +1,127 @@ + + +# Piecewise linear costs + +An extension of [Calliope in fragments](../index.md). Calliope's example `piecewise_linear_costs.yaml`: a convex investment cost as the upper envelope of lines, which needs the units of the MILP file. The cost is a term of `cost_investment`. It declares the variable the SOS2 example declares, so the two do not compose. + + +```yaml +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + costs: + description: Calliope's `costs` — cost classes, such as monetary and CO2 + pieces: + description: Calliope's `pieces` — the lines a piecewise curve is the upper envelope of + dtype: int + +parameters: + cost_flow_cap_piecewise_slopes: + description: "`cost_flow_cap_piecewise_slopes` — the slope of each line of a convex investment cost curve" + dims: [nodes, techs, costs, pieces] + cost_flow_cap_piecewise_intercept: + description: "`cost_flow_cap_piecewise_intercept` — the intercept of each line of a convex investment cost curve" + dims: [nodes, techs, costs, pieces] + +variables: + piecewise_cost_investment: + description: "`piecewise_cost_investment` — an investment cost that grows faster the more capacity is built" + dims: [nodes, techs, costs] + where: >- + count(cost_flow_cap_piecewise_slopes, over=pieces) >= 1 + AND count(cost_flow_cap_piecewise_intercept, over=pieces) >= 1 AND purchased_units + bounds: { lower: 0 } + absence: zero + +given: + variables: + flow_cap: { dims: [nodes, techs, carriers] } + purchased_units: { dims: [nodes, techs] } + expressions: + cost_investment: { dims: [nodes, techs, costs], term: piecewise_cost_investment_term } + +expressions: + piecewise_cost_investment_term: + description: "`+ piecewise_cost_investment` — the term Calliope writes into `cost_investment`, by restating it whole" + expression: piecewise_cost_investment + +constraints: + piecewise_costs: + description: "`piecewise_costs` — the investment cost is at least every line of the curve, so at least the curve" + dims: [nodes, techs, costs, pieces] + where: piecewise_cost_investment + expression: >- + piecewise_cost_investment >= sum(cost_flow_cap_piecewise_slopes * flow_cap, over=carriers) + + cost_flow_cap_piecewise_intercept * purchased_units +``` + +#### Sets + +| Symbol | Meaning | +|---|---| +| $`\mathcal{N}`$ | index $`n`$ — `nodes` — Calliope's `nodes` — the places technologies stand at | +| $`\mathcal{I}`$ | index $`i`$ — `techs` — Calliope's `techs` — technologies | +| $`\mathcal{C}`$ | index $`c`$ — `carriers` — Calliope's `carriers` — energy and commodity carriers | +| $`\mathcal{K}`$ | index $`k`$ — `costs` — Calliope's `costs` — cost classes, such as monetary and CO2 | +| $`\mathcal{P}`$ | index $`p`$ — `pieces` — Calliope's `pieces` — the lines a piecewise curve is the upper envelope of | + +#### Parameters + +| Symbol | Meaning | +|---|---| +| $`\mathrm{cost\_flow\_cap\_piecewise\_slopes}`$ | `cost_flow_cap_piecewise_slopes` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K} \times \mathcal{P}`$ — `cost_flow_cap_piecewise_slopes` — the slope of each line of a convex investment cost curve | +| $`\mathrm{cost\_flow\_cap\_piecewise\_intercept}`$ | `cost_flow_cap_piecewise_intercept` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K} \times \mathcal{P}`$ — `cost_flow_cap_piecewise_intercept` — the intercept of each line of a convex investment cost curve | + +#### Variables + +| Symbol | Meaning | +|---|---| +| $`\mathit{piecewise\_cost\_investment}`$ | `piecewise_cost_investment` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K}`$ — `piecewise_cost_investment` — an investment cost that grows faster the more capacity is built | + +#### Given + +| Symbol | Meaning | +|---|---| +| $`\mathit{flow\_cap}`$ | `flow_cap` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C}`$ | +| $`\mathit{purchased\_units}`$ | `purchased_units` over $`\mathcal{N} \times \mathcal{I}`$ | +| $`\mathit{cost\_investment}`$ | `cost_investment` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K}`$, an expression this file adds `piecewise_cost_investment_term` to | + +#### Definitions + +| Symbol | Meaning | +|---|---| +| $`\mathit{piecewise\_cost\_investment\_term}`$ | `piecewise_cost_investment_term` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K}`$ — `+ piecewise_cost_investment` — the term Calliope writes into `cost_investment`, by restating it whole | + +Upright is what the data supplies — a parameter such as $`\mathrm{cost\_flow\_cap\_piecewise\_slopes}`$, a coordinate map, a label — and italic is what the solver chooses, such as $`\mathit{piecewise\_cost\_investment}`$. An index is italic too, being what a quantifier chooses, and a set is script. + +#### Subject to + +**`piecewise_costs`** + +```math +\mathit{piecewise\_cost\_investment}_{n,i,k} \ge \sum_{c \in \mathcal{C}} \mathrm{cost\_flow\_cap\_piecewise\_slopes}_{n,i,k,p} \cdot \mathit{flow\_cap}_{n,i,c} + \mathrm{cost\_flow\_cap\_piecewise\_intercept}_{n,i,k,p} \cdot \mathit{purchased\_units}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ k \in \mathcal{K},\ p \in \mathcal{P} \,:\, \mathit{piecewise\_cost\_investment}_{n,i,k} \text{ exists} +``` + +#### Definitions + +**`piecewise_cost_investment_term`** + +```math +\mathit{piecewise\_cost\_investment\_term}_{n,i,k} = \mathit{piecewise\_cost\_investment}_{n,i,k} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ k \in \mathcal{K} +``` + +#### Variable domains + +**`piecewise_cost_investment`** + +```math +\mathit{piecewise\_cost\_investment}_{n,i,k} \ge 0 \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ k \in \mathcal{K} \,:\, \lvert \{ p \in \mathcal{P} \,:\, \mathrm{cost\_flow\_cap\_piecewise\_slopes}_{n,i,k,p} \text{ is defined} \} \rvert \ge 1 \wedge \lvert \{ p \in \mathcal{P} \,:\, \mathrm{cost\_flow\_cap\_piecewise\_intercept}_{n,i,k,p} \text{ is defined} \} \rvert \ge 1 \wedge \mathit{purchased\_units}_{n,i} \text{ exists} +``` + diff --git a/docs/examples/calliope/extensions/piecewise_linear_efficiency.md b/docs/examples/calliope/extensions/piecewise_linear_efficiency.md new file mode 100644 index 00000000..7000baf9 --- /dev/null +++ b/docs/examples/calliope/extensions/piecewise_linear_efficiency.md @@ -0,0 +1,85 @@ + + +# Piecewise linear efficiency + +An extension of [Calliope in fragments](../index.md). Calliope's example `piecewise_linear_efficiency.yaml`: inflow at least a convex curve of outflow, which needs the available flow capacity of the MILP file. + + +```yaml +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + pieces: + description: Calliope's `pieces` — the lines a piecewise curve is the upper envelope of + dtype: int + +parameters: + flow_eff_piecewise_slopes: + description: "`flow_eff_piecewise_slopes` — the slope of each line of a convex inflow curve" + dims: [nodes, techs, pieces] + flow_eff_piecewise_intercept: + description: "`flow_eff_piecewise_intercept` — the intercept of each line of a convex inflow curve" + dims: [nodes, techs, pieces] + +given: + variables: + flow_out: { dims: [nodes, techs, carriers, timesteps] } + flow_in: { dims: [nodes, techs, carriers, timesteps] } + available_flow_cap: { dims: [nodes, techs, carriers, timesteps] } + +constraints: + piecewise_efficiency: + description: >- + `piecewise_efficiency` — inflow is at least every line of the curve of + outflow, so at least the curve. Calliope's `where: available_flow_cap` + over a technology reads as the technology having it for some carrier + dims: [nodes, techs, timesteps, pieces] + where: flow_eff_piecewise_slopes AND flow_eff_piecewise_intercept AND count(available_flow_cap, over=carriers) >= 1 + expression: >- + sum(flow_in, over=carriers) >= flow_eff_piecewise_slopes * sum(flow_out, over=carriers) + + flow_eff_piecewise_intercept * sum(available_flow_cap, over=carriers) +``` + +#### Sets + +| Symbol | Meaning | +|---|---| +| $`\mathcal{N}`$ | index $`n`$ — `nodes` — Calliope's `nodes` — the places technologies stand at | +| $`\mathcal{I}`$ | index $`i`$ — `techs` — Calliope's `techs` — technologies | +| $`\mathcal{C}`$ | index $`c`$ — `carriers` — Calliope's `carriers` — energy and commodity carriers | +| $`\mathcal{T}`$ | index $`t`$ — `timesteps` — Calliope's `timesteps` — time steps, in order | +| $`\mathcal{P}`$ | index $`p`$ — `pieces` — Calliope's `pieces` — the lines a piecewise curve is the upper envelope of | + +#### Parameters + +| Symbol | Meaning | +|---|---| +| $`\mathrm{flow\_eff\_piecewise\_slopes}`$ | `flow_eff_piecewise_slopes` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{P}`$ — `flow_eff_piecewise_slopes` — the slope of each line of a convex inflow curve | +| $`\mathrm{flow\_eff\_piecewise\_intercept}`$ | `flow_eff_piecewise_intercept` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{P}`$ — `flow_eff_piecewise_intercept` — the intercept of each line of a convex inflow curve | + +#### Given + +| Symbol | Meaning | +|---|---| +| $`\mathit{flow\_out}`$ | `flow_out` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ | +| $`\mathit{flow\_in}`$ | `flow_in` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ | +| $`\mathit{available\_flow\_cap}`$ | `available_flow_cap` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ | + +#### Subject to + +**`piecewise_efficiency`** + +```math +\sum_{c \in \mathcal{C}} \mathit{flow\_in}_{n,i,c,t} \ge \mathrm{flow\_eff\_piecewise\_slopes}_{n,i,p} \cdot \left( \sum_{c \in \mathcal{C}} \mathit{flow\_out}_{n,i,c,t} \right) + \mathrm{flow\_eff\_piecewise\_intercept}_{n,i,p} \cdot \left( \sum_{c \in \mathcal{C}} \mathit{available\_flow\_cap}_{n,i,c,t} \right) \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T},\ p \in \mathcal{P} \,:\, \mathrm{flow\_eff\_piecewise\_slopes}_{n,i,p} \text{ is defined} \wedge \mathrm{flow\_eff\_piecewise\_intercept}_{n,i,p} \text{ is defined} \wedge \lvert \{ c \in \mathcal{C} \,:\, \mathit{available\_flow\_cap}_{n,i,c,t} \text{ exists} \} \rvert \ge 1 +``` + diff --git a/docs/examples/calliope/extensions/share_all_timesteps.md b/docs/examples/calliope/extensions/share_all_timesteps.md new file mode 100644 index 00000000..0845ae1c --- /dev/null +++ b/docs/examples/calliope/extensions/share_all_timesteps.md @@ -0,0 +1,144 @@ + + +# Flow share over the whole time + +An extension of [Calliope in fragments](../index.md). Calliope's example `share_all_timesteps.yaml`: a technology meets a share of a demand, or of a node's outflow of a carrier, over the whole time. + + +```yaml +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + +relations: + demand_share_tech: + description: >- + `demand_share_tech` — the demand technology whose inflow a technology + meets a share of. Calliope slices `flow_in` by it; a read through the + relation is that slice + key: techs + values: { demand: techs } + supply_share_carrier: + description: >- + `supply_share_carrier` — the carrier a technology's share of outflow is + counted in. Calliope slices `flow_out` by it, which is a test of the + pair + key: [techs, carriers] + +parameters: + demand_share_equals: + description: "`demand_share_equals` — the share of a demand technology's inflow a technology meets; given only where set" + dims: [nodes, techs] + supply_share_equals: + description: "`supply_share_equals` — the share of a node's outflow of a carrier a technology puts out; given only where set" + dims: [nodes, techs] + +expressions: + supply_share_flow_out: + description: "`flow_out[carriers=$carrier]` — a technology's outflow of its share carrier" + dims: [nodes, techs, carriers, timesteps] + cases: + share_carrier: + when: supply_share_carrier + expression: flow_out + otherwise: 0 + supply_share_all_flow_out: + description: "`sum(flow_out[carriers=$carrier], over=techs)` — every technology's outflow of a technology's share carrier" + dims: [nodes, techs, carriers, timesteps] + cases: + share_carrier: + when: supply_share_carrier + expression: sum(flow_out, over=techs) + otherwise: 0 + +given: + variables: + flow_out: { dims: [nodes, techs, carriers, timesteps] } + flow_in: { dims: [nodes, techs, carriers, timesteps] } + +constraints: + demand_share_equals_per_tech: + description: "`demand_share_equals_per_tech` — a technology puts out its share of a demand technology's inflow over the whole time" + dims: [nodes, techs] + where: demand_share_equals + expression: >- + sum(sum(flow_out, over=timesteps), over=carriers) + == sum(sum(at(flow_in, by=demand_share_tech, over=demand, into=techs), over=timesteps), over=carriers) + * demand_share_equals + supply_share_equals_per_tech: + description: "`supply_share_equals_per_tech` — a technology puts out its share of a node's outflow of a carrier over the whole time" + dims: [nodes, techs] + where: supply_share_equals + expression: >- + sum(sum(supply_share_flow_out, over=carriers), over=timesteps) + == sum(sum(supply_share_all_flow_out, over=carriers), over=timesteps) * supply_share_equals +``` + +#### Sets + +| Symbol | Meaning | +|---|---| +| $`\mathcal{N}`$ | index $`n`$ — `nodes` — Calliope's `nodes` — the places technologies stand at | +| $`\mathcal{I}`$ | index $`i`$ — `techs` with $`\mathrm{demand\_share\_tech}: \mathcal{I} \to \mathcal{I},\ \mathrm{supply\_share\_carrier} \subseteq \mathcal{I} \times \mathcal{C}`$ — Calliope's `techs` — technologies | +| $`\mathcal{C}`$ | index $`c`$ — `carriers` with $`\mathrm{supply\_share\_carrier} \subseteq \mathcal{I} \times \mathcal{C}`$ — Calliope's `carriers` — energy and commodity carriers | +| $`\mathcal{T}`$ | index $`t`$ — `timesteps` — Calliope's `timesteps` — time steps, in order | + +#### Parameters + +| Symbol | Meaning | +|---|---| +| $`\mathrm{demand\_share\_equals}`$ | `demand_share_equals` over $`\mathcal{N} \times \mathcal{I}`$ — `demand_share_equals` — the share of a demand technology's inflow a technology meets; given only where set | +| $`\mathrm{supply\_share\_equals}`$ | `supply_share_equals` over $`\mathcal{N} \times \mathcal{I}`$ — `supply_share_equals` — the share of a node's outflow of a carrier a technology puts out; given only where set | + +#### Given + +| Symbol | Meaning | +|---|---| +| $`\mathit{flow\_out}`$ | `flow_out` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ | +| $`\mathit{flow\_in}`$ | `flow_in` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ | + +#### Definitions + +| Symbol | Meaning | +|---|---| +| $`\mathit{supply\_share\_flow\_out}`$ | `supply_share_flow_out` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ — `flow_out[carriers=$carrier]` — a technology's outflow of its share carrier | +| $`\mathit{supply\_share\_all\_flow\_out}`$ | `supply_share_all_flow_out` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ — `sum(flow_out[carriers=$carrier], over=techs)` — every technology's outflow of a technology's share carrier | + +#### Subject to + +**`demand_share_equals_per_tech`** + +```math +\sum_{c \in \mathcal{C}} \sum_{t \in \mathcal{T}} \mathit{flow\_out}_{n,i,c,t} = \left( \sum_{c \in \mathcal{C}} \sum_{t \in \mathcal{T}} \mathit{flow\_in}_{n,\mathrm{demand\_share\_tech}(i),c,t} \right) \cdot \mathrm{demand\_share\_equals}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I} \,:\, \mathrm{demand\_share\_equals}_{n,i} \text{ is defined} +``` + +**`supply_share_equals_per_tech`** + +```math +\sum_{t \in \mathcal{T}} \sum_{c \in \mathcal{C}} \mathit{supply\_share\_flow\_out}_{n,i,c,t} = \left( \sum_{t \in \mathcal{T}} \sum_{c \in \mathcal{C}} \mathit{supply\_share\_all\_flow\_out}_{n,i,c,t} \right) \cdot \mathrm{supply\_share\_equals}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I} \,:\, \mathrm{supply\_share\_equals}_{n,i} \text{ is defined} +``` + +#### Definitions + +**`supply_share_flow_out`** + +```math +\mathit{supply\_share\_flow\_out}_{n,i,c,t} = \begin{cases} \mathit{flow\_out}_{n,i,c,t} & \text{if } \left( i,\ c \right) \in \mathrm{supply\_share\_carrier} \\ 0 & \text{otherwise} \end{cases} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} +``` + +**`supply_share_all_flow_out`** + +```math +\mathit{supply\_share\_all\_flow\_out}_{n,i,c,t} = \begin{cases} \sum_{i' \in \mathcal{I}} \mathit{flow\_out}_{n,i',c,t} & \text{if } \left( i,\ c \right) \in \mathrm{supply\_share\_carrier} \\ 0 & \text{otherwise} \end{cases} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} +``` + diff --git a/docs/examples/calliope/extensions/share_per_timestep.md b/docs/examples/calliope/extensions/share_per_timestep.md new file mode 100644 index 00000000..b42ed8e9 --- /dev/null +++ b/docs/examples/calliope/extensions/share_per_timestep.md @@ -0,0 +1,148 @@ + + +# Flow share per time step + +An extension of [Calliope in fragments](../index.md). Calliope's example `share_per_timestep.yaml`: the same shares, in each time step. + + +```yaml +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + +relations: + demand_share_tech: + description: >- + `demand_share_tech` — the demand technology whose inflow a technology + meets a share of. Calliope slices `flow_in` by it; a read through the + relation is that slice + key: techs + values: { demand: techs } + supply_share_carrier: + description: >- + `supply_share_carrier` — the carrier a technology's share of outflow is + counted in. Calliope slices `flow_out` by it, which is a test of the + pair + key: [techs, carriers] + +parameters: + demand_share_per_timestep_equals: + description: "`demand_share_per_timestep_equals` — the share of a demand technology's inflow a technology meets in each time step; given only where set" + dims: [nodes, techs, timesteps] + supply_share_per_timestep_equals: + description: "`supply_share_per_timestep_equals` — the share of a node's outflow of a carrier a technology puts out in each time step; given only where set" + dims: [nodes, techs, timesteps] + +expressions: + supply_share_timestep_flow_out: + description: "`flow_out[carriers=$carrier]` — a technology's outflow of its share carrier" + dims: [nodes, techs, carriers, timesteps] + cases: + share_carrier: + when: supply_share_carrier + expression: flow_out + otherwise: 0 + supply_share_timestep_all_flow_out: + description: "`sum(flow_out[carriers=$carrier], over=techs)` — every technology's outflow of a technology's share carrier" + dims: [nodes, techs, carriers, timesteps] + cases: + share_carrier: + when: supply_share_carrier + expression: sum(flow_out, over=techs) + otherwise: 0 + +given: + variables: + flow_out: { dims: [nodes, techs, carriers, timesteps] } + flow_in: { dims: [nodes, techs, carriers, timesteps] } + +constraints: + demand_share_per_timestep_equals_per_tech: + description: "`demand_share_per_timestep_equals_per_tech` — a technology puts out its share of a demand technology's inflow in each time step" + dims: [nodes, techs, timesteps] + where: demand_share_per_timestep_equals + expression: >- + sum(flow_out, over=carriers) + == sum(at(flow_in, by=demand_share_tech, over=demand, into=techs), over=carriers) + * demand_share_per_timestep_equals + supply_share_per_timestep_equals_per_tech: + description: >- + `supply_share_per_timestep_equals_per_tech` — a technology puts out its + share of a node's outflow of a carrier in each time step. Calliope's + row keeps the carrier dimension of the slice; the slice here is summed + over the one carrier it keeps + dims: [nodes, techs, timesteps] + where: supply_share_per_timestep_equals + expression: >- + sum(supply_share_timestep_flow_out, over=carriers) + == sum(supply_share_timestep_all_flow_out, over=carriers) * supply_share_per_timestep_equals +``` + +#### Sets + +| Symbol | Meaning | +|---|---| +| $`\mathcal{N}`$ | index $`n`$ — `nodes` — Calliope's `nodes` — the places technologies stand at | +| $`\mathcal{I}`$ | index $`i`$ — `techs` with $`\mathrm{demand\_share\_tech}: \mathcal{I} \to \mathcal{I},\ \mathrm{supply\_share\_carrier} \subseteq \mathcal{I} \times \mathcal{C}`$ — Calliope's `techs` — technologies | +| $`\mathcal{C}`$ | index $`c`$ — `carriers` with $`\mathrm{supply\_share\_carrier} \subseteq \mathcal{I} \times \mathcal{C}`$ — Calliope's `carriers` — energy and commodity carriers | +| $`\mathcal{T}`$ | index $`t`$ — `timesteps` — Calliope's `timesteps` — time steps, in order | + +#### Parameters + +| Symbol | Meaning | +|---|---| +| $`\mathrm{demand\_share\_per\_timestep\_equals}`$ | `demand_share_per_timestep_equals` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{T}`$ — `demand_share_per_timestep_equals` — the share of a demand technology's inflow a technology meets in each time step; given only where set | +| $`\mathrm{supply\_share\_per\_timestep\_equals}`$ | `supply_share_per_timestep_equals` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{T}`$ — `supply_share_per_timestep_equals` — the share of a node's outflow of a carrier a technology puts out in each time step; given only where set | + +#### Given + +| Symbol | Meaning | +|---|---| +| $`\mathit{flow\_out}`$ | `flow_out` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ | +| $`\mathit{flow\_in}`$ | `flow_in` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ | + +#### Definitions + +| Symbol | Meaning | +|---|---| +| $`\mathit{supply\_share\_timestep\_flow\_out}`$ | `supply_share_timestep_flow_out` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ — `flow_out[carriers=$carrier]` — a technology's outflow of its share carrier | +| $`\mathit{supply\_share\_timestep\_all\_flow\_out}`$ | `supply_share_timestep_all_flow_out` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ — `sum(flow_out[carriers=$carrier], over=techs)` — every technology's outflow of a technology's share carrier | + +#### Subject to + +**`demand_share_per_timestep_equals_per_tech`** + +```math +\sum_{c \in \mathcal{C}} \mathit{flow\_out}_{n,i,c,t} = \left( \sum_{c \in \mathcal{C}} \mathit{flow\_in}_{n,\mathrm{demand\_share\_tech}(i),c,t} \right) \cdot \mathrm{demand\_share\_per\_timestep\_equals}_{n,i,t} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T} \,:\, \mathrm{demand\_share\_per\_timestep\_equals}_{n,i,t} \text{ is defined} +``` + +**`supply_share_per_timestep_equals_per_tech`** + +```math +\sum_{c \in \mathcal{C}} \mathit{supply\_share\_timestep\_flow\_out}_{n,i,c,t} = \left( \sum_{c \in \mathcal{C}} \mathit{supply\_share\_timestep\_all\_flow\_out}_{n,i,c,t} \right) \cdot \mathrm{supply\_share\_per\_timestep\_equals}_{n,i,t} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T} \,:\, \mathrm{supply\_share\_per\_timestep\_equals}_{n,i,t} \text{ is defined} +``` + +#### Definitions + +**`supply_share_timestep_flow_out`** + +```math +\mathit{supply\_share\_timestep\_flow\_out}_{n,i,c,t} = \begin{cases} \mathit{flow\_out}_{n,i,c,t} & \text{if } \left( i,\ c \right) \in \mathrm{supply\_share\_carrier} \\ 0 & \text{otherwise} \end{cases} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} +``` + +**`supply_share_timestep_all_flow_out`** + +```math +\mathit{supply\_share\_timestep\_all\_flow\_out}_{n,i,c,t} = \begin{cases} \sum_{i' \in \mathcal{I}} \mathit{flow\_out}_{n,i',c,t} & \text{if } \left( i,\ c \right) \in \mathrm{supply\_share\_carrier} \\ 0 & \text{otherwise} \end{cases} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} +``` + diff --git a/docs/examples/calliope/extensions/sos2_piecewise_linear_costs.md b/docs/examples/calliope/extensions/sos2_piecewise_linear_costs.md new file mode 100644 index 00000000..5a2c5fde --- /dev/null +++ b/docs/examples/calliope/extensions/sos2_piecewise_linear_costs.md @@ -0,0 +1,166 @@ + + +# Piecewise linear costs with SOS2 + +An extension of [Calliope in fragments](../index.md). Calliope's example `sos2_piecewise_linear_costs.yaml`: an investment cost with economies of scale, as a curve through breakpoints stated as an SOS2 set. The cost is a term of `cost_investment`. + + +```yaml +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + costs: + description: Calliope's `costs` — cost classes, such as monetary and CO2 + breakpoints: + description: Calliope's `breakpoints` — the corners of a piecewise-linear curve, in order + dtype: int + +parameters: + piecewise_cost_investment_x: + description: "`piecewise_cost_investment_x` — the flow capacity at each breakpoint" + dims: [techs, breakpoints] + piecewise_cost_investment_y: + description: "`piecewise_cost_investment_y` — the investment cost at each breakpoint" + dims: [techs, costs, breakpoints] + +variables: + piecewise_cost_investment: + description: "`piecewise_cost_investment` — an investment cost that grows more slowly the more capacity is built" + dims: [nodes, techs, carriers, costs] + where: count(piecewise_cost_investment_x, over=breakpoints) >= 1 AND count(piecewise_cost_investment_y, over=breakpoints) >= 1 + bounds: { lower: 0 } + absence: zero + piecewise_flow_cap: + description: >- + the flow capacity, where the technology has a cost curve. A + `piecewise:` block takes no `where:`, so its link rows would pin + `flow_cap` to the curve at every technology; a link over this copy is + built only where the copy is + dims: [nodes, techs, carriers, costs] + where: count(piecewise_cost_investment_x, over=breakpoints) >= 1 AND count(piecewise_cost_investment_y, over=breakpoints) >= 1 + +constraints: + piecewise_flow_cap_is_flow_cap: + description: the copy of the flow capacity the curve reads is the flow capacity + dims: [nodes, techs, carriers, costs] + where: piecewise_flow_cap + expression: piecewise_flow_cap == flow_cap + +piecewise: + sos2_piecewise_costs: + description: "`sos2_piecewise_costs` — the investment cost lies on the curve through the breakpoints, stated as an SOS2 set" + over: breakpoints + method: sos2 + points: piecewise_cost_investment_x + links: + - [piecewise_flow_cap, piecewise_cost_investment_x] + - [piecewise_cost_investment, piecewise_cost_investment_y] + +expressions: + cost_investment_piecewise: + description: "`sum(piecewise_cost_investment, over=carriers)` — the term Calliope writes into `cost_investment`" + expression: sum(piecewise_cost_investment, over=carriers) + +given: + variables: + flow_cap: { dims: [nodes, techs, carriers] } + expressions: + cost_investment: { dims: [nodes, techs, costs], term: cost_investment_piecewise } +``` + +#### Sets + +| Symbol | Meaning | +|---|---| +| $`\mathcal{N}`$ | index $`n`$ — `nodes` — Calliope's `nodes` — the places technologies stand at | +| $`\mathcal{I}`$ | index $`i`$ — `techs` — Calliope's `techs` — technologies | +| $`\mathcal{C}`$ | index $`c`$ — `carriers` — Calliope's `carriers` — energy and commodity carriers | +| $`\mathcal{K}`$ | index $`k`$ — `costs` — Calliope's `costs` — cost classes, such as monetary and CO2 | +| $`\mathcal{B}`$ | index $`b`$ — `breakpoints` — Calliope's `breakpoints` — the corners of a piecewise-linear curve, in order | + +#### Parameters + +| Symbol | Meaning | +|---|---| +| $`\mathrm{piecewise\_cost\_investment\_x}`$ | `piecewise_cost_investment_x` over $`\mathcal{I} \times \mathcal{B}`$ — `piecewise_cost_investment_x` — the flow capacity at each breakpoint | +| $`\mathrm{piecewise\_cost\_investment\_y}`$ | `piecewise_cost_investment_y` over $`\mathcal{I} \times \mathcal{K} \times \mathcal{B}`$ — `piecewise_cost_investment_y` — the investment cost at each breakpoint | + +#### Variables + +| Symbol | Meaning | +|---|---| +| $`\mathit{piecewise\_cost\_investment}`$ | `piecewise_cost_investment` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{K}`$ — `piecewise_cost_investment` — an investment cost that grows more slowly the more capacity is built | +| $`\mathit{piecewise\_flow\_cap}`$ | `piecewise_flow_cap` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{K}`$ — the flow capacity, where the technology has a cost curve. A `piecewise:` block takes no `where:`, so its link rows would pin `flow_cap` to the curve at every technology; a link over this copy is built only where the copy is | + +#### Given + +| Symbol | Meaning | +|---|---| +| $`\mathit{flow\_cap}`$ | `flow_cap` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C}`$ | +| $`\mathit{cost\_investment}`$ | `cost_investment` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K}`$, an expression this file adds `cost_investment_piecewise` to | + +#### Definitions + +| Symbol | Meaning | +|---|---| +| $`\mathit{cost\_investment\_piecewise}`$ | `cost_investment_piecewise` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K}`$ — `sum(piecewise_cost_investment, over=carriers)` — the term Calliope writes into `cost_investment` | + +Upright is what the data supplies — a parameter such as $`\mathrm{piecewise\_cost\_investment\_x}`$, a coordinate map, a label — and italic is what the solver chooses, such as $`\mathit{piecewise\_cost\_investment}`$. An index is italic too, being what a quantifier chooses, and a set is script. + +#### Subject to + +**`piecewise_flow_cap_is_flow_cap`** + +```math +\mathit{piecewise\_flow\_cap}_{n,i,c,k} = \mathit{flow\_cap}_{n,i,c} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ k \in \mathcal{K} \,:\, \mathit{piecewise\_flow\_cap}_{n,i,c,k} \text{ exists} +``` + +**`sos2_piecewise_costs`** + +```math +\left( \mathit{piecewise\_flow\_cap}_{n,i,c,k},\ \mathit{piecewise\_cost\_investment}_{n,i,c,k} \right) \in \mathrm{pwl}_{b \in \mathcal{B} \,:\, \mathrm{piecewise\_cost\_investment\_x}_{i,b} \text{ is defined}}(\mathrm{piecewise\_cost\_investment\_x}_{i,b},\ \mathrm{piecewise\_cost\_investment\_y}_{i,k,b}) \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ k \in \mathcal{K} +``` + +#### Definitions + +**`cost_investment_piecewise`** + +```math +\mathit{cost\_investment\_piecewise}_{n,i,k} = \sum_{c \in \mathcal{C}} \mathit{piecewise\_cost\_investment}_{n,i,c,k} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ k \in \mathcal{K} +``` + +#### Variable domains + +**`piecewise_cost_investment`** + +```math +\mathit{piecewise\_cost\_investment}_{n,i,c,k} \ge 0 \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ k \in \mathcal{K} \,:\, \lvert \{ b \in \mathcal{B} \,:\, \mathrm{piecewise\_cost\_investment\_x}_{i,b} \text{ is defined} \} \rvert \ge 1 \wedge \lvert \{ b \in \mathcal{B} \,:\, \mathrm{piecewise\_cost\_investment\_y}_{i,k,b} \text{ is defined} \} \rvert \ge 1 +``` + +**`piecewise_flow_cap`** + +```math +\mathit{piecewise\_flow\_cap}_{n,i,c,k} \in \mathbb{R} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ k \in \mathcal{K} \,:\, \lvert \{ b \in \mathcal{B} \,:\, \mathrm{piecewise\_cost\_investment\_x}_{i,b} \text{ is defined} \} \rvert \ge 1 \wedge \lvert \{ b \in \mathcal{B} \,:\, \mathrm{piecewise\_cost\_investment\_y}_{i,k,b} \text{ is defined} \} \rvert \ge 1 +``` + +#### Assumptions + +**`sos2_piecewise_costs_complete`** + +```math +\mathrm{piecewise\_cost\_investment\_x}_{i,b} \text{ is defined} \wedge \mathrm{piecewise\_cost\_investment\_y}_{i,k,b} \text{ is defined} \qquad \forall\, i \in \mathcal{I},\ k \in \mathcal{K},\ b \in \mathcal{B} \,:\, \mathrm{piecewise\_cost\_investment\_x}_{i,b} \text{ is defined} +``` + +**`sos2_piecewise_costs_contiguous`** + +```math +\lvert \{ b \in \mathcal{B} \,:\, \mathrm{piecewise\_cost\_investment\_x}_{i,b} \text{ is defined} \wedge \neg \left( \mathrm{piecewise\_cost\_investment\_x}_{i,b - 1} \text{ is defined} \right) \} \rvert = 1 \qquad \forall\, i \in \mathcal{I} +``` + diff --git a/docs/examples/calliope/extensions/uptime_downtime_limits.md b/docs/examples/calliope/extensions/uptime_downtime_limits.md new file mode 100644 index 00000000..4366c635 --- /dev/null +++ b/docs/examples/calliope/extensions/uptime_downtime_limits.md @@ -0,0 +1,147 @@ + + +# Uptime and downtime limits + +An extension of [Calliope in fragments](../index.md). Calliope's example `uptime_downtime_limits.yaml`: capacity factors over the whole time, forced downtime, and a cap on the time steps a unit-bought technology runs in. + + +```yaml +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + +parameters: + capacity_factor_min: + description: "`capacity_factor_min` — the least capacity factor a technology reaches over the whole time" + dims: [nodes, techs] + capacity_factor_max: + description: "`capacity_factor_max` — the most capacity factor a technology reaches over the whole time; given only where set" + dims: [nodes, techs] + uptime_limit: + description: "`uptime_limit` — the most time steps a technology runs in, weighted; given only where set" + dims: [nodes, techs] + downtime_periods: + description: "`downtime_periods` — whether a technology is down for maintenance in a time step" + dims: [nodes, techs, timesteps] + dtype: bool + +expressions: + total_time: + description: "`$total_time` — the hours the modelled time steps stand for" + expression: sum(timestep_resolution * timestep_weights, over=timesteps) + +given: + parameters: + carrier_out: { dims: [nodes, techs, carriers], dtype: bool } + timestep_resolution: { dims: [timesteps] } + timestep_weights: { dims: [timesteps] } + variables: + flow_out: { dims: [nodes, techs, carriers, timesteps] } + flow_cap: { dims: [nodes, techs, carriers] } + operating_units: { dims: [nodes, techs, timesteps] } + +constraints: + annual_capacity_factor_min: + description: "`annual_capacity_factor_min` — a technology's outflow over the whole time is at least its least capacity factor" + dims: [nodes, techs, carriers] + where: carrier_out AND capacity_factor_min + expression: sum(flow_out * timestep_weights, over=timesteps) >= flow_cap * capacity_factor_min * total_time + annual_capacity_factor_max: + description: "`annual_capacity_factor_max` — a technology's outflow over the whole time is at most its most capacity factor" + dims: [nodes, techs, carriers] + where: carrier_out AND capacity_factor_max + expression: sum(flow_out * timestep_weights, over=timesteps) <= flow_cap * capacity_factor_max * total_time + downtime_period: + description: "`downtime_period` — a technology puts out nothing in a time step it is down" + dims: [nodes, techs, timesteps] + where: downtime_periods + expression: sum(flow_out, over=carriers) == 0 + downtime_period_decision: + description: >- + `downtime_period_decision` — a unit-bought technology runs in at most + its limit of time steps. Calliope's `where: operating_units` over a + technology reads as the technology running in whole units at all + dims: [nodes, techs] + where: count(operating_units, over=timesteps) >= 1 AND uptime_limit + expression: sum(operating_units * timestep_weights, over=timesteps) <= uptime_limit +``` + +#### Sets + +| Symbol | Meaning | +|---|---| +| $`\mathcal{N}`$ | index $`n`$ — `nodes` — Calliope's `nodes` — the places technologies stand at | +| $`\mathcal{I}`$ | index $`i`$ — `techs` — Calliope's `techs` — technologies | +| $`\mathcal{C}`$ | index $`c`$ — `carriers` — Calliope's `carriers` — energy and commodity carriers | +| $`\mathcal{T}`$ | index $`t`$ — `timesteps` — Calliope's `timesteps` — time steps, in order | + +#### Parameters + +| Symbol | Meaning | +|---|---| +| $`\mathrm{capacity\_factor\_min}`$ | `capacity_factor_min` over $`\mathcal{N} \times \mathcal{I}`$ — `capacity_factor_min` — the least capacity factor a technology reaches over the whole time | +| $`\mathrm{capacity\_factor\_max}`$ | `capacity_factor_max` over $`\mathcal{N} \times \mathcal{I}`$ — `capacity_factor_max` — the most capacity factor a technology reaches over the whole time; given only where set | +| $`\mathrm{uptime\_limit}`$ | `uptime_limit` over $`\mathcal{N} \times \mathcal{I}`$ — `uptime_limit` — the most time steps a technology runs in, weighted; given only where set | +| $`\mathrm{downtime\_periods}`$ | `downtime_periods` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{T}`$ — `downtime_periods` — whether a technology is down for maintenance in a time step | + +#### Given + +| Symbol | Meaning | +|---|---| +| $`\mathrm{carrier\_out}`$ | `carrier_out` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C}`$, data another file declares | +| $`\mathrm{timestep\_resolution}`$ | `timestep_resolution` over $`\mathcal{T}`$, data another file declares | +| $`\mathrm{timestep\_weights}`$ | `timestep_weights` over $`\mathcal{T}`$, data another file declares | +| $`\mathit{flow\_out}`$ | `flow_out` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ | +| $`\mathit{flow\_cap}`$ | `flow_cap` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C}`$ | +| $`\mathit{operating\_units}`$ | `operating_units` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{T}`$ | + +#### Definitions + +| Symbol | Meaning | +|---|---| +| $`\mathrm{total\_time}`$ | `total_time` (scalar) — `$total_time` — the hours the modelled time steps stand for | + +#### Subject to + +**`annual_capacity_factor_min`** + +```math +\sum_{t \in \mathcal{T}} \mathit{flow\_out}_{n,i,c,t} \cdot \mathrm{timestep\_weights}_{t} \ge \mathit{flow\_cap}_{n,i,c} \cdot \mathrm{capacity\_factor\_min}_{n,i} \cdot \mathrm{total\_time} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C} \,:\, \mathrm{carrier\_out}_{n,i,c} \wedge \mathrm{capacity\_factor\_min}_{n,i} \text{ is defined} +``` + +**`annual_capacity_factor_max`** + +```math +\sum_{t \in \mathcal{T}} \mathit{flow\_out}_{n,i,c,t} \cdot \mathrm{timestep\_weights}_{t} \le \mathit{flow\_cap}_{n,i,c} \cdot \mathrm{capacity\_factor\_max}_{n,i} \cdot \mathrm{total\_time} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C} \,:\, \mathrm{carrier\_out}_{n,i,c} \wedge \mathrm{capacity\_factor\_max}_{n,i} \text{ is defined} +``` + +**`downtime_period`** + +```math +\sum_{c \in \mathcal{C}} \mathit{flow\_out}_{n,i,c,t} = 0 \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T} \,:\, \mathrm{downtime\_periods}_{n,i,t} +``` + +**`downtime_period_decision`** + +```math +\sum_{t \in \mathcal{T}} \mathit{operating\_units}_{n,i,t} \cdot \mathrm{timestep\_weights}_{t} \le \mathrm{uptime\_limit}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I} \,:\, \lvert \{ t \in \mathcal{T} \,:\, \mathit{operating\_units}_{n,i,t} \text{ exists} \} \rvert \ge 1 \wedge \mathrm{uptime\_limit}_{n,i} \text{ is defined} +``` + +#### Definitions + +**`total_time`** + +```math +\mathrm{total\_time} = \sum_{t \in \mathcal{T}} \mathrm{timestep\_resolution}_{t} \cdot \mathrm{timestep\_weights}_{t} +``` + diff --git a/docs/examples/calliope/extensions/urban_scale_chp.md b/docs/examples/calliope/extensions/urban_scale_chp.md new file mode 100644 index 00000000..154d8e44 --- /dev/null +++ b/docs/examples/calliope/extensions/urban_scale_chp.md @@ -0,0 +1,146 @@ + + +# Urban-scale CHP + +An extension of [Calliope in fragments](../index.md). The `additional_math.yaml` of Calliope's urban-scale example model: the technology `chp` puts out heat in a fixed ratio to its electricity. The new rows are here, and [its patch](../variants/urban_scale_chp.md) keeps the base `balance_conversion` off `chp`. + + +```yaml +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + +parameters: + heat_to_power_ratio: + description: "`heat_to_power_ratio` — the heat a combined heat and power plant puts out per unit of electricity. Calliope's default is 1, and data prep fills it" + dims: [nodes, techs] + +expressions: + urban_electricity_out: + description: "`flow_out[carriers=electricity]`" + dims: [nodes, techs, carriers, timesteps] + cases: + electricity: + when: carriers == electricity + expression: flow_out + otherwise: 0 + urban_heat_out: + description: "`flow_out[carriers=heat]`" + dims: [nodes, techs, carriers, timesteps] + cases: + heat: + when: carriers == heat + expression: flow_out + otherwise: 0 + urban_electricity_out_inc_eff: + description: "`flow_out_inc_eff[carriers=electricity]`" + dims: [nodes, techs, carriers, timesteps] + cases: + electricity: + when: carriers == electricity + expression: flow_out_inc_eff + otherwise: 0 + +given: + parameters: + base_tech: { dims: [techs], dtype: str } + include_storage: { dims: [nodes, techs], dtype: bool } + variables: + flow_out: { dims: [nodes, techs, carriers, timesteps] } + expressions: + flow_out_inc_eff: { dims: [nodes, techs, carriers, timesteps] } + flow_in_inc_eff: { dims: [nodes, techs, carriers, timesteps] } + +constraints: + link_chp_outputs: + description: "`link_chp_outputs` — the technology `chp` puts out heat in a fixed ratio to its electricity" + dims: [nodes, techs, timesteps] + where: techs == chp + expression: sum(urban_electricity_out, over=carriers) * heat_to_power_ratio == sum(urban_heat_out, over=carriers) + balance_conversion_chp: + description: >- + `balance_conversion` for the technology `chp` — it puts out, before + losses, as much electricity as it takes in fuel after them. The patch + keeps the base row off it + dims: [nodes, techs, timesteps] + where: base_tech == 'conversion' AND NOT include_storage AND techs == chp + expression: sum(urban_electricity_out_inc_eff, over=carriers) == sum(flow_in_inc_eff, over=carriers) +``` + +#### Sets + +| Symbol | Meaning | +|---|---| +| $`\mathcal{N}`$ | index $`n`$ — `nodes` — Calliope's `nodes` — the places technologies stand at | +| $`\mathcal{I}`$ | index $`i`$ — `techs` — Calliope's `techs` — technologies | +| $`\mathcal{C}`$ | index $`c`$ — `carriers` — Calliope's `carriers` — energy and commodity carriers | +| $`\mathcal{T}`$ | index $`t`$ — `timesteps` — Calliope's `timesteps` — time steps, in order | + +#### Parameters + +| Symbol | Meaning | +|---|---| +| $`\mathrm{heat\_to\_power\_ratio}`$ | `heat_to_power_ratio` over $`\mathcal{N} \times \mathcal{I}`$ — `heat_to_power_ratio` — the heat a combined heat and power plant puts out per unit of electricity. Calliope's default is 1, and data prep fills it | + +#### Given + +| Symbol | Meaning | +|---|---| +| $`\mathrm{base\_tech}`$ | `base_tech` over $`\mathcal{I}`$, data another file declares | +| $`\mathrm{include\_storage}`$ | `include_storage` over $`\mathcal{N} \times \mathcal{I}`$, data another file declares | +| $`\mathit{flow\_out}`$ | `flow_out` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ | +| $`\mathit{flow\_out\_inc\_eff}`$ | `flow_out_inc_eff` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$, an expression another file defines | +| $`\mathit{flow\_in\_inc\_eff}`$ | `flow_in_inc_eff` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$, an expression another file defines | + +#### Definitions + +| Symbol | Meaning | +|---|---| +| $`\mathit{urban\_electricity\_out}`$ | `urban_electricity_out` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ — `flow_out[carriers=electricity]` | +| $`\mathit{urban\_heat\_out}`$ | `urban_heat_out` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ — `flow_out[carriers=heat]` | +| $`\mathit{urban\_electricity\_out\_inc\_eff}`$ | `urban_electricity_out_inc_eff` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ — `flow_out_inc_eff[carriers=electricity]` | + +#### Subject to + +**`link_chp_outputs`** + +```math +\left( \sum_{c \in \mathcal{C}} \mathit{urban\_electricity\_out}_{n,i,c,t} \right) \cdot \mathrm{heat\_to\_power\_ratio}_{n,i} = \sum_{c \in \mathcal{C}} \mathit{urban\_heat\_out}_{n,i,c,t} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T} \,:\, i = \text{'}\mathrm{chp}\text{'} +``` + +**`balance_conversion_chp`** + +```math +\sum_{c \in \mathcal{C}} \mathit{urban\_electricity\_out\_inc\_eff}_{n,i,c,t} = \sum_{c \in \mathcal{C}} \mathit{flow\_in\_inc\_eff}_{n,i,c,t} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T} \,:\, \mathrm{base\_tech}_{i} = \text{'}\mathrm{conversion}\text{'} \wedge \neg \mathrm{include\_storage}_{n,i} \wedge i = \text{'}\mathrm{chp}\text{'} +``` + +#### Definitions + +**`urban_electricity_out`** + +```math +\mathit{urban\_electricity\_out}_{n,i,c,t} = \begin{cases} \mathit{flow\_out}_{n,i,c,t} & \text{if } c = \text{'}\mathrm{electricity}\text{'} \\ 0 & \text{otherwise} \end{cases} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} +``` + +**`urban_heat_out`** + +```math +\mathit{urban\_heat\_out}_{n,i,c,t} = \begin{cases} \mathit{flow\_out}_{n,i,c,t} & \text{if } c = \text{'}\mathrm{heat}\text{'} \\ 0 & \text{otherwise} \end{cases} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} +``` + +**`urban_electricity_out_inc_eff`** + +```math +\mathit{urban\_electricity\_out\_inc\_eff}_{n,i,c,t} = \begin{cases} \mathit{flow\_out\_inc\_eff}_{n,i,c,t} & \text{if } c = \text{'}\mathrm{electricity}\text{'} \\ 0 & \text{otherwise} \end{cases} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} +``` + diff --git a/docs/examples/calliope/feasibility.md b/docs/examples/calliope/feasibility.md new file mode 100644 index 00000000..8fc74886 --- /dev/null +++ b/docs/examples/calliope/feasibility.md @@ -0,0 +1,128 @@ + + +# Feasibility + +One of the base fragments of [Calliope in fragments](index.md). Unmet demand and unused supply at a high price, so a model that cannot balance still solves. Calliope builds them under `config.ensure_feasibility`; here the switch is whether this file is composed. + + +```yaml +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + +variables: + unmet_demand: + description: >- + `unmet_demand` — a source of any carrier at any node, at a high price, + so a model that cannot meet its demand still solves. Calliope builds + it under `config.ensure_feasibility`; here it is this file + dims: [nodes, carriers, timesteps] + where: count(carrier_in, over=techs) >= 1 OR count(carrier_out, over=techs) >= 1 + bounds: { lower: 0 } + absence: zero + unused_supply: + description: "`unused_supply` — a sink of any carrier at any node, at a high price, the counterpart of `unmet_demand`" + dims: [nodes, carriers, timesteps] + where: count(carrier_in, over=techs) >= 1 OR count(carrier_out, over=techs) >= 1 + bounds: { upper: 0 } + absence: zero + +expressions: + feasibility_carrier_flow: unmet_demand + unused_supply + unmet_demand_penalty: + description: "`$unmet_demand` of `min_cost_optimisation` — what unmet demand and unused supply cost" + expression: sum(sum(sum(unmet_demand - unused_supply, over=carriers), over=nodes) * timestep_weights) * bigM + unmet_sum: + description: "`unmet_sum` — net unmet demand; reported" + expression: unmet_demand + unused_supply + +given: + parameters: + carrier_in: { dims: [nodes, techs, carriers], dtype: bool } + carrier_out: { dims: [nodes, techs, carriers], dtype: bool } + timestep_weights: { dims: [timesteps] } + bigM: { dims: [] } + expressions: + carrier_flow: { dims: [nodes, carriers, timesteps], term: feasibility_carrier_flow } + penalty: { dims: [], term: unmet_demand_penalty } +``` + +#### Sets + +| Symbol | Meaning | +|---|---| +| $`\mathcal{N}`$ | index $`n`$ — `nodes` — Calliope's `nodes` — the places technologies stand at | +| $`\mathcal{I}`$ | index $`i`$ — `techs` — Calliope's `techs` — technologies | +| $`\mathcal{C}`$ | index $`c`$ — `carriers` — Calliope's `carriers` — energy and commodity carriers | +| $`\mathcal{T}`$ | index $`t`$ — `timesteps` — Calliope's `timesteps` — time steps, in order | + +#### Variables + +| Symbol | Meaning | +|---|---| +| $`\mathit{unmet\_demand}`$ | `unmet_demand` over $`\mathcal{N} \times \mathcal{C} \times \mathcal{T}`$ — `unmet_demand` — a source of any carrier at any node, at a high price, so a model that cannot meet its demand still solves. Calliope builds it under `config.ensure_feasibility`; here it is this file | +| $`\mathit{unused\_supply}`$ | `unused_supply` over $`\mathcal{N} \times \mathcal{C} \times \mathcal{T}`$ — `unused_supply` — a sink of any carrier at any node, at a high price, the counterpart of `unmet_demand` | + +#### Given + +| Symbol | Meaning | +|---|---| +| $`\mathrm{carrier\_in}`$ | `carrier_in` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C}`$, data another file declares | +| $`\mathrm{carrier\_out}`$ | `carrier_out` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C}`$, data another file declares | +| $`\mathrm{timestep\_weights}`$ | `timestep_weights` over $`\mathcal{T}`$, data another file declares | +| $`\mathrm{bigM}`$ | `bigM` (scalar), data another file declares | +| $`\mathit{carrier\_flow}`$ | `carrier_flow` over $`\mathcal{N} \times \mathcal{C} \times \mathcal{T}`$, an expression this file adds `feasibility_carrier_flow` to | +| $`\mathit{penalty}`$ | `penalty` (scalar), an expression this file adds `unmet_demand_penalty` to | + +#### Definitions + +| Symbol | Meaning | +|---|---| +| $`\mathit{feasibility\_carrier\_flow}`$ | `feasibility_carrier_flow` over $`\mathcal{N} \times \mathcal{C} \times \mathcal{T}`$ | +| $`\mathit{unmet\_demand\_penalty}`$ | `unmet_demand_penalty` (scalar) — `$unmet_demand` of `min_cost_optimisation` — what unmet demand and unused supply cost | +| $`\mathit{unmet\_sum}`$ | `unmet_sum` over $`\mathcal{N} \times \mathcal{C} \times \mathcal{T}`$ — `unmet_sum` — net unmet demand; reported | + +#### Definitions + +**`feasibility_carrier_flow`** + +```math +\mathit{feasibility\_carrier\_flow}_{n,c,t} = \mathit{unmet\_demand}_{n,c,t} + \mathit{unused\_supply}_{n,c,t} \qquad \forall\, n \in \mathcal{N},\ c \in \mathcal{C},\ t \in \mathcal{T} +``` + +**`unmet_demand_penalty`** + +```math +\mathit{unmet\_demand\_penalty} = \left( \sum_{t \in \mathcal{T}} \left( \sum_{n \in \mathcal{N}} \sum_{c \in \mathcal{C}} \left( \mathit{unmet\_demand}_{n,c,t} - \mathit{unused\_supply}_{n,c,t} \right) \right) \cdot \mathrm{timestep\_weights}_{t} \right) \cdot \mathrm{bigM} +``` + +**`unmet_sum`** + +```math +\mathit{unmet\_sum}_{n,c,t} = \mathit{unmet\_demand}_{n,c,t} + \mathit{unused\_supply}_{n,c,t} \qquad \forall\, n \in \mathcal{N},\ c \in \mathcal{C},\ t \in \mathcal{T} +``` + +#### Variable domains + +**`unmet_demand`** + +```math +\mathit{unmet\_demand}_{n,c,t} \ge 0 \qquad \forall\, n \in \mathcal{N},\ c \in \mathcal{C},\ t \in \mathcal{T} \,:\, \lvert \{ i \in \mathcal{I} \,:\, \mathrm{carrier\_in}_{n,i,c} \} \rvert \ge 1 \vee \lvert \{ i \in \mathcal{I} \,:\, \mathrm{carrier\_out}_{n,i,c} \} \rvert \ge 1 +``` + +**`unused_supply`** + +```math +\mathit{unused\_supply}_{n,c,t} \le 0 \qquad \forall\, n \in \mathcal{N},\ c \in \mathcal{C},\ t \in \mathcal{T} \,:\, \lvert \{ i \in \mathcal{I} \,:\, \mathrm{carrier\_in}_{n,i,c} \} \rvert \ge 1 \vee \lvert \{ i \in \mathcal{I} \,:\, \mathrm{carrier\_out}_{n,i,c} \} \rvert \ge 1 +``` + diff --git a/docs/examples/calliope/flows.md b/docs/examples/calliope/flows.md new file mode 100644 index 00000000..903ff42e --- /dev/null +++ b/docs/examples/calliope/flows.md @@ -0,0 +1,489 @@ + + +# Flows + +One of the base fragments of [Calliope in fragments](index.md). The core of every technology: flow capacity, outflow and inflow, their efficiencies, their limits and ramping. It adds the flows to the balance and their costs to the three cost sums. + + +```yaml +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + costs: + description: Calliope's `costs` — cost classes, such as monetary and CO2 + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + +relations: + link_from: + description: >- + `link_from` — the node a transmission technology links from. Calliope + reads it as `map_dim(nodes, link_from)`, a mask over technology and + node, which is the relation's own row test + key: [techs, nodes] + link_to: + description: >- + `link_to` — the node a transmission technology links to, read as + `link_from` is + key: [techs, nodes] + +parameters: + base_tech: + description: >- + `base_tech` — the abstract class a technology derives from: demand, + supply, conversion, storage or transmission + dims: [techs] + dtype: str + carrier_in: + description: "`carrier_in` — whether a technology consumes a carrier at a node" + dims: [nodes, techs, carriers] + dtype: bool + carrier_out: + description: "`carrier_out` — whether a technology produces a carrier at a node" + dims: [nodes, techs, carriers] + dtype: bool + include_storage: + description: >- + `include_storage` — whether a technology that is not a storage one + carries a store all the same + dims: [nodes, techs] + dtype: bool + one_way: + description: "`one_way` — whether a transmission technology carries flow only from `link_from` to `link_to`" + dims: [techs] + dtype: bool + flow_cap_min: + description: >- + `flow_cap_min` — least flow capacity. Calliope's default is 0; a bound + has a row wherever the variable has one, so data prep fills it + dims: [nodes, techs] + flow_cap_max: + description: >- + `flow_cap_max` — most flow capacity. Calliope's default is `.inf`, + which data prep fills, and a `where` reads as not given + dims: [nodes, techs] + flow_cap_min_systemwide: + description: "`flow_cap_min_systemwide` — least flow capacity of a technology over every node; given only where set" + dims: [techs, carriers] + flow_cap_max_systemwide: + description: "`flow_cap_max_systemwide` — most flow capacity of a technology over every node; given only where set" + dims: [techs, carriers] + flow_out_min_relative: + description: "`flow_out_min_relative` — least outflow, per unit of flow capacity; given only where set" + dims: [nodes, techs, timesteps] + flow_out_eff: + description: "`flow_out_eff` — the share of flow that leaves a technology as outflow. Calliope's default is 1, and data prep fills it" + dims: [nodes, techs, carriers, timesteps] + flow_in_eff: + description: "`flow_in_eff` — the share of inflow that enters a technology. Calliope's default is 1, and data prep fills it" + dims: [nodes, techs, carriers, timesteps] + flow_out_parasitic_eff: + description: "`flow_out_parasitic_eff` — what is left after the plant's own use. Calliope's default is 1, and data prep fills it" + dims: [nodes, techs, carriers, timesteps] + flow_out_eff_per_distance: + description: "`flow_out_eff_per_distance` — the outflow efficiency of a link per unit of distance. Calliope's default is 1, and data prep fills it" + dims: [nodes, techs, carriers, timesteps] + flow_in_eff_per_distance: + description: "`flow_in_eff_per_distance` — the inflow efficiency of a link per unit of distance. Calliope's default is 1, and data prep fills it" + dims: [nodes, techs, carriers, timesteps] + distance: + description: >- + `distance` — the length of a transmission link. Calliope's default is + 1, which data prep fills, where it does not derive one from the + coordinates of the nodes + dims: [techs] + flow_ramping: + description: "`flow_ramping` — the most flow may change in an hour, per unit of flow capacity; given only where set" + dims: [nodes, techs] + cost_flow_cap: + description: "`cost_flow_cap` — the cost of one unit of flow capacity" + dims: [nodes, techs, costs] + cost_flow_cap_per_distance: + description: "`cost_flow_cap_per_distance` — the cost of one unit of flow capacity per unit of link distance" + dims: [nodes, techs, costs] + cost_flow_out: + description: "`cost_flow_out` — the cost of one unit of outflow" + dims: [nodes, techs, costs, timesteps] + cost_flow_in: + description: "`cost_flow_in` — the cost of one unit of inflow" + dims: [nodes, techs, costs, timesteps] + cost_om_annual: + description: "`cost_om_annual` — the annual cost of one unit of flow capacity" + dims: [nodes, techs, costs] + +variables: + flow_cap: + description: "`flow_cap` — the flow capacity of a technology, its nominal or nameplate capacity" + dims: [nodes, techs, carriers] + where: carrier_in OR carrier_out + bounds: { lower: flow_cap_min, upper: flow_cap_max } + absence: zero + flow_out: + description: >- + `flow_out` — the outflow of a technology in a time step. A one-way link + has none at the node it links from + dims: [nodes, techs, carriers, timesteps] + where: carrier_out AND NOT (one_way AND link_from) + bounds: { lower: 0 } + absence: zero + flow_in: + description: >- + `flow_in` — the inflow to a technology in a time step. A one-way link + has none at the node it links to + dims: [nodes, techs, carriers, timesteps] + where: carrier_in AND NOT (one_way AND link_to) + bounds: { lower: 0 } + absence: zero + +expressions: + flow_out_inc_eff: + description: "`flow_out_inc_eff` — outflow before the losses on the way out" + dims: [nodes, techs, carriers, timesteps] + cases: + transmission: + when: base_tech == 'transmission' + expression: flow_out / (flow_out_eff * flow_out_parasitic_eff * flow_out_eff_per_distance ** distance) + otherwise: flow_out / (flow_out_eff * flow_out_parasitic_eff) + flow_in_inc_eff: + description: "`flow_in_inc_eff` — inflow after the losses on the way in" + dims: [nodes, techs, carriers, timesteps] + cases: + transmission: + when: base_tech == 'transmission' + expression: flow_in * flow_in_eff * flow_in_eff_per_distance ** distance + otherwise: flow_in * flow_in_eff + ramping_flow: + description: >- + `$flow` of `ramping_up` and `ramping_down` — the flow a ramping limit + holds, per hour: outflow, inflow, or their difference where a + technology has both + dims: [nodes, techs, carriers, timesteps] + cases: + out: + when: carrier_out AND NOT carrier_in + expression: flow_out / timestep_resolution + in: + when: carrier_in AND NOT carrier_out + expression: flow_in / timestep_resolution + otherwise: (flow_out - flow_in) / timestep_resolution + cost_flow_cap_sum: + description: >- + `$cost_sum` of `cost_investment_flow_cap` — what one unit of flow + capacity costs; a link's cost is split between its two ends + dims: [nodes, techs, costs] + cases: + transmission: + when: base_tech == 'transmission' + expression: (cost_flow_cap + cost_flow_cap_per_distance * distance) * 0.5 + otherwise: cost_flow_cap + cost_investment_flow_cap: + description: "`cost_investment_flow_cap` — the investment cost of flow capacity" + expression: cost_flow_cap_sum * flow_cap + flows_carrier_flow: sum(flow_out, over=techs) - sum(flow_in, over=techs) + flows_cost_investment: sum(cost_investment_flow_cap, over=carriers) + flows_cost_operation_variable: >- + timestep_weights * (sum(cost_flow_out * flow_out, over=carriers) + sum(cost_flow_in * flow_in, over=carriers)) + flows_cost_operation_fixed: annualisation_weight * sum(cost_om_annual * flow_cap, over=carriers) + +given: + parameters: + timestep_resolution: { dims: [timesteps] } + timestep_weights: { dims: [timesteps] } + expressions: + annualisation_weight: + description: the share of a year the modelled time steps stand for + dims: [] + carrier_flow: { dims: [nodes, carriers, timesteps], term: flows_carrier_flow } + cost_investment: { dims: [nodes, techs, costs], term: flows_cost_investment } + cost_operation_variable: { dims: [nodes, techs, costs, timesteps], term: flows_cost_operation_variable } + cost_operation_fixed: { dims: [nodes, techs, costs], term: flows_cost_operation_fixed } + +constraints: + flow_out_max: + description: "`flow_out_max` — outflow is at most the flow capacity over the time step, less the plant's own use" + dims: [nodes, techs, carriers, timesteps] + where: carrier_out + expression: flow_out <= flow_cap * timestep_resolution * flow_out_parasitic_eff + flow_out_min: + description: "`flow_out_min` — outflow is at least its least share of the flow capacity" + dims: [nodes, techs, carriers, timesteps] + where: flow_cap AND flow_out_min_relative + expression: flow_out >= flow_cap * timestep_resolution * flow_out_min_relative + flow_in_max: + description: "`flow_in_max` — inflow is at most the flow capacity over the time step" + dims: [nodes, techs, carriers, timesteps] + where: carrier_in + expression: flow_in <= flow_cap * timestep_resolution + flow_capacity_systemwide_max: + description: "`flow_capacity_systemwide_max` — the flow capacity of a technology over every node is at most its system-wide maximum" + dims: [techs, carriers] + where: count(flow_cap, over=nodes) >= 1 AND flow_cap_max_systemwide + expression: sum(flow_cap, over=nodes) <= flow_cap_max_systemwide + flow_capacity_systemwide_min: + description: "`flow_capacity_systemwide_min` — the flow capacity of a technology over every node is at least its system-wide minimum" + dims: [techs, carriers] + where: count(flow_cap, over=nodes) >= 1 AND flow_cap_min_systemwide + expression: sum(flow_cap, over=nodes) >= flow_cap_min_systemwide + ramping_up: + description: "`ramping_up` — flow rises from one time step to the next by at most its ramping share of the flow capacity" + dims: [nodes, techs, carriers, timesteps] + where: (carrier_in OR carrier_out) AND flow_ramping AND position(timesteps) > 0 + expression: ramping_flow - shift(ramping_flow, along=timesteps, offset=1) <= flow_ramping * flow_cap + ramping_down: + description: "`ramping_down` — flow falls from one time step to the next by at most its ramping share of the flow capacity" + dims: [nodes, techs, carriers, timesteps] + where: (carrier_in OR carrier_out) AND flow_ramping AND position(timesteps) > 0 + expression: -1 * flow_ramping * flow_cap <= ramping_flow - shift(ramping_flow, along=timesteps, offset=1) + +assumptions: + must_have_base: + description: Calliope's `must_have_base` — every technology derives from an abstract class + holds: base_tech + base_tech_one_of: + description: Calliope's `one_of` on `base_tech` + holds: >- + base_tech == 'demand' OR base_tech == 'supply' OR base_tech == 'conversion' + OR base_tech == 'storage' OR base_tech == 'transmission' + distance_only_for_transmission: + description: >- + Calliope's `distance_only_for_transmission` — only a link sets a + distance or a per-distance value. Data prep fills the defaults, so a + technology that is not a link keeps them + holds: >- + distance == 1 AND flow_in_eff_per_distance == 1 + AND flow_out_eff_per_distance == 1 AND NOT cost_flow_cap_per_distance + where: NOT base_tech == 'transmission' + unbounded_flow_cap_cost: + description: Calliope's `unbounded_flow_cap_cost` — a negative flow capacity cost needs a finite maximum + holds: NOT cost_flow_cap < 0 OR flow_cap_max +``` + +#### Sets + +| Symbol | Meaning | +|---|---| +| $`\mathcal{N}`$ | index $`n`$ — `nodes` with $`\mathrm{link\_from} \subseteq \mathcal{I} \times \mathcal{N},\ \mathrm{link\_to} \subseteq \mathcal{I} \times \mathcal{N}`$ — Calliope's `nodes` — the places technologies stand at | +| $`\mathcal{I}`$ | index $`i`$ — `techs` with $`\mathrm{link\_from} \subseteq \mathcal{I} \times \mathcal{N},\ \mathrm{link\_to} \subseteq \mathcal{I} \times \mathcal{N}`$ — Calliope's `techs` — technologies | +| $`\mathcal{C}`$ | index $`c`$ — `carriers` — Calliope's `carriers` — energy and commodity carriers | +| $`\mathcal{K}`$ | index $`k`$ — `costs` — Calliope's `costs` — cost classes, such as monetary and CO2 | +| $`\mathcal{T}`$ | index $`t`$ — `timesteps` — Calliope's `timesteps` — time steps, in order | + +#### Parameters + +| Symbol | Meaning | +|---|---| +| $`\mathrm{base\_tech}`$ | `base_tech` over $`\mathcal{I}`$ — `base_tech` — the abstract class a technology derives from: demand, supply, conversion, storage or transmission | +| $`\mathrm{carrier\_in}`$ | `carrier_in` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C}`$ — `carrier_in` — whether a technology consumes a carrier at a node | +| $`\mathrm{carrier\_out}`$ | `carrier_out` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C}`$ — `carrier_out` — whether a technology produces a carrier at a node | +| $`\mathrm{include\_storage}`$ | `include_storage` over $`\mathcal{N} \times \mathcal{I}`$ — `include_storage` — whether a technology that is not a storage one carries a store all the same | +| $`\mathrm{one\_way}`$ | `one_way` over $`\mathcal{I}`$ — `one_way` — whether a transmission technology carries flow only from `link_from` to `link_to` | +| $`\mathrm{flow\_cap\_min}`$ | `flow_cap_min` over $`\mathcal{N} \times \mathcal{I}`$ — `flow_cap_min` — least flow capacity. Calliope's default is 0; a bound has a row wherever the variable has one, so data prep fills it | +| $`\mathrm{flow\_cap\_max}`$ | `flow_cap_max` over $`\mathcal{N} \times \mathcal{I}`$ — `flow_cap_max` — most flow capacity. Calliope's default is `.inf`, which data prep fills, and a `where` reads as not given | +| $`\mathrm{flow\_cap\_min\_systemwide}`$ | `flow_cap_min_systemwide` over $`\mathcal{I} \times \mathcal{C}`$ — `flow_cap_min_systemwide` — least flow capacity of a technology over every node; given only where set | +| $`\mathrm{flow\_cap\_max\_systemwide}`$ | `flow_cap_max_systemwide` over $`\mathcal{I} \times \mathcal{C}`$ — `flow_cap_max_systemwide` — most flow capacity of a technology over every node; given only where set | +| $`\mathrm{flow\_out\_min\_relative}`$ | `flow_out_min_relative` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{T}`$ — `flow_out_min_relative` — least outflow, per unit of flow capacity; given only where set | +| $`\mathrm{flow\_out\_eff}`$ | `flow_out_eff` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ — `flow_out_eff` — the share of flow that leaves a technology as outflow. Calliope's default is 1, and data prep fills it | +| $`\mathrm{flow\_in\_eff}`$ | `flow_in_eff` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ — `flow_in_eff` — the share of inflow that enters a technology. Calliope's default is 1, and data prep fills it | +| $`\mathrm{flow\_out\_parasitic\_eff}`$ | `flow_out_parasitic_eff` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ — `flow_out_parasitic_eff` — what is left after the plant's own use. Calliope's default is 1, and data prep fills it | +| $`\mathrm{flow\_out\_eff\_per\_distance}`$ | `flow_out_eff_per_distance` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ — `flow_out_eff_per_distance` — the outflow efficiency of a link per unit of distance. Calliope's default is 1, and data prep fills it | +| $`\mathrm{flow\_in\_eff\_per\_distance}`$ | `flow_in_eff_per_distance` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ — `flow_in_eff_per_distance` — the inflow efficiency of a link per unit of distance. Calliope's default is 1, and data prep fills it | +| $`\mathrm{distance}`$ | `distance` over $`\mathcal{I}`$ — `distance` — the length of a transmission link. Calliope's default is 1, which data prep fills, where it does not derive one from the coordinates of the nodes | +| $`\mathrm{flow\_ramping}`$ | `flow_ramping` over $`\mathcal{N} \times \mathcal{I}`$ — `flow_ramping` — the most flow may change in an hour, per unit of flow capacity; given only where set | +| $`\mathrm{cost\_flow\_cap}`$ | `cost_flow_cap` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K}`$ — `cost_flow_cap` — the cost of one unit of flow capacity | +| $`\mathrm{cost\_flow\_cap\_per\_distance}`$ | `cost_flow_cap_per_distance` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K}`$ — `cost_flow_cap_per_distance` — the cost of one unit of flow capacity per unit of link distance | +| $`\mathrm{cost\_flow\_out}`$ | `cost_flow_out` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K} \times \mathcal{T}`$ — `cost_flow_out` — the cost of one unit of outflow | +| $`\mathrm{cost\_flow\_in}`$ | `cost_flow_in` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K} \times \mathcal{T}`$ — `cost_flow_in` — the cost of one unit of inflow | +| $`\mathrm{cost\_om\_annual}`$ | `cost_om_annual` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K}`$ — `cost_om_annual` — the annual cost of one unit of flow capacity | + +#### Variables + +| Symbol | Meaning | +|---|---| +| $`\mathit{flow\_cap}`$ | `flow_cap` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C}`$ — `flow_cap` — the flow capacity of a technology, its nominal or nameplate capacity | +| $`\mathit{flow\_out}`$ | `flow_out` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ — `flow_out` — the outflow of a technology in a time step. A one-way link has none at the node it links from | +| $`\mathit{flow\_in}`$ | `flow_in` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ — `flow_in` — the inflow to a technology in a time step. A one-way link has none at the node it links to | + +#### Given + +| Symbol | Meaning | +|---|---| +| $`\mathrm{timestep\_resolution}`$ | `timestep_resolution` over $`\mathcal{T}`$, data another file declares | +| $`\mathrm{timestep\_weights}`$ | `timestep_weights` over $`\mathcal{T}`$, data another file declares | +| $`\mathit{annualisation\_weight}`$ | `annualisation_weight` (scalar), an expression another file defines — the share of a year the modelled time steps stand for | +| $`\mathit{carrier\_flow}`$ | `carrier_flow` over $`\mathcal{N} \times \mathcal{C} \times \mathcal{T}`$, an expression this file adds `flows_carrier_flow` to | +| $`\mathit{cost\_investment}`$ | `cost_investment` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K}`$, an expression this file adds `flows_cost_investment` to | +| $`\mathit{cost\_operation\_variable}`$ | `cost_operation_variable` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K} \times \mathcal{T}`$, an expression this file adds `flows_cost_operation_variable` to | +| $`\mathit{cost\_operation\_fixed}`$ | `cost_operation_fixed` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K}`$, an expression this file adds `flows_cost_operation_fixed` to | + +#### Definitions + +| Symbol | Meaning | +|---|---| +| $`\mathit{flow\_out\_inc\_eff}`$ | `flow_out_inc_eff` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ — `flow_out_inc_eff` — outflow before the losses on the way out | +| $`\mathit{flow\_in\_inc\_eff}`$ | `flow_in_inc_eff` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ — `flow_in_inc_eff` — inflow after the losses on the way in | +| $`\mathit{ramping\_flow}`$ | `ramping_flow` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ — `$flow` of `ramping_up` and `ramping_down` — the flow a ramping limit holds, per hour: outflow, inflow, or their difference where a technology has both | +| $`\mathrm{cost\_flow\_cap\_sum}`$ | `cost_flow_cap_sum` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K}`$ — `$cost_sum` of `cost_investment_flow_cap` — what one unit of flow capacity costs; a link's cost is split between its two ends | +| $`\mathit{cost\_investment\_flow\_cap}`$ | `cost_investment_flow_cap` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{K}`$ — `cost_investment_flow_cap` — the investment cost of flow capacity | +| $`\mathit{flows\_carrier\_flow}`$ | `flows_carrier_flow` over $`\mathcal{N} \times \mathcal{C} \times \mathcal{T}`$ | +| $`\mathit{flows\_cost\_investment}`$ | `flows_cost_investment` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K}`$ | +| $`\mathit{flows\_cost\_operation\_variable}`$ | `flows_cost_operation_variable` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K} \times \mathcal{T}`$ | +| $`\mathit{flows\_cost\_operation\_fixed}`$ | `flows_cost_operation_fixed` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K}`$ | + +Upright is what the data supplies — a parameter such as $`\mathrm{base\_tech}`$, a coordinate map, a label — and italic is what the solver chooses, such as $`\mathit{flow\_cap}`$. An index is italic too, being what a quantifier chooses, and a set is script. + +$`\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 + +**`flow_out_max`** + +```math +\mathit{flow\_out}_{n,i,c,t} \le \mathit{flow\_cap}_{n,i,c} \cdot \mathrm{timestep\_resolution}_{t} \cdot \mathrm{flow\_out\_parasitic\_eff}_{n,i,c,t} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} \,:\, \mathrm{carrier\_out}_{n,i,c} +``` + +**`flow_out_min`** + +```math +\mathit{flow\_out}_{n,i,c,t} \ge \mathit{flow\_cap}_{n,i,c} \cdot \mathrm{timestep\_resolution}_{t} \cdot \mathrm{flow\_out\_min\_relative}_{n,i,t} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} \,:\, \mathit{flow\_cap}_{n,i,c} \text{ exists} \wedge \mathrm{flow\_out\_min\_relative}_{n,i,t} \text{ is defined} +``` + +**`flow_in_max`** + +```math +\mathit{flow\_in}_{n,i,c,t} \le \mathit{flow\_cap}_{n,i,c} \cdot \mathrm{timestep\_resolution}_{t} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} \,:\, \mathrm{carrier\_in}_{n,i,c} +``` + +**`flow_capacity_systemwide_max`** + +```math +\sum_{n \in \mathcal{N}} \mathit{flow\_cap}_{n,i,c} \le \mathrm{flow\_cap\_max\_systemwide}_{i,c} \qquad \forall\, i \in \mathcal{I},\ c \in \mathcal{C} \,:\, \lvert \{ n \in \mathcal{N} \,:\, \mathit{flow\_cap}_{n,i,c} \text{ exists} \} \rvert \ge 1 \wedge \mathrm{flow\_cap\_max\_systemwide}_{i,c} \text{ is defined} +``` + +**`flow_capacity_systemwide_min`** + +```math +\sum_{n \in \mathcal{N}} \mathit{flow\_cap}_{n,i,c} \ge \mathrm{flow\_cap\_min\_systemwide}_{i,c} \qquad \forall\, i \in \mathcal{I},\ c \in \mathcal{C} \,:\, \lvert \{ n \in \mathcal{N} \,:\, \mathit{flow\_cap}_{n,i,c} \text{ exists} \} \rvert \ge 1 \wedge \mathrm{flow\_cap\_min\_systemwide}_{i,c} \text{ is defined} +``` + +**`ramping_up`** + +```math +\mathit{ramping\_flow}_{n,i,c,t} - \mathit{ramping\_flow}_{n,i,c,t - 1} \le \mathrm{flow\_ramping}_{n,i} \cdot \mathit{flow\_cap}_{n,i,c} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} \,:\, \left( \mathrm{carrier\_in}_{n,i,c} \vee \mathrm{carrier\_out}_{n,i,c} \right) \wedge \mathrm{flow\_ramping}_{n,i} \text{ is defined} \wedge \mathrm{pos}(t) > 0 +``` + +**`ramping_down`** + +```math +-1 \cdot \mathrm{flow\_ramping}_{n,i} \cdot \mathit{flow\_cap}_{n,i,c} \le \mathit{ramping\_flow}_{n,i,c,t} - \mathit{ramping\_flow}_{n,i,c,t - 1} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} \,:\, \left( \mathrm{carrier\_in}_{n,i,c} \vee \mathrm{carrier\_out}_{n,i,c} \right) \wedge \mathrm{flow\_ramping}_{n,i} \text{ is defined} \wedge \mathrm{pos}(t) > 0 +``` + +#### Definitions + +**`flow_out_inc_eff`** + +```math +\mathit{flow\_out\_inc\_eff}_{n,i,c,t} = \begin{cases} \frac{\mathit{flow\_out}_{n,i,c,t}}{\mathrm{flow\_out\_eff}_{n,i,c,t} \cdot \mathrm{flow\_out\_parasitic\_eff}_{n,i,c,t} \cdot \mathrm{flow\_out\_eff\_per\_distance}_{n,i,c,t}^{\mathrm{distance}_{i}}} & \text{if } \mathrm{base\_tech}_{i} = \text{'}\mathrm{transmission}\text{'} \\ \frac{\mathit{flow\_out}_{n,i,c,t}}{\mathrm{flow\_out\_eff}_{n,i,c,t} \cdot \mathrm{flow\_out\_parasitic\_eff}_{n,i,c,t}} & \text{otherwise} \end{cases} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} +``` + +**`flow_in_inc_eff`** + +```math +\mathit{flow\_in\_inc\_eff}_{n,i,c,t} = \begin{cases} \mathit{flow\_in}_{n,i,c,t} \cdot \mathrm{flow\_in\_eff}_{n,i,c,t} \cdot \mathrm{flow\_in\_eff\_per\_distance}_{n,i,c,t}^{\mathrm{distance}_{i}} & \text{if } \mathrm{base\_tech}_{i} = \text{'}\mathrm{transmission}\text{'} \\ \mathit{flow\_in}_{n,i,c,t} \cdot \mathrm{flow\_in\_eff}_{n,i,c,t} & \text{otherwise} \end{cases} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} +``` + +**`ramping_flow`** + +```math +\mathit{ramping\_flow}_{n,i,c,t} = \begin{cases} \frac{\mathit{flow\_out}_{n,i,c,t}}{\mathrm{timestep\_resolution}_{t}} & \text{if } \mathrm{carrier\_out}_{n,i,c} \wedge \neg \mathrm{carrier\_in}_{n,i,c} \\ \frac{\mathit{flow\_in}_{n,i,c,t}}{\mathrm{timestep\_resolution}_{t}} & \text{if } \mathrm{carrier\_in}_{n,i,c} \wedge \neg \mathrm{carrier\_out}_{n,i,c} \\ \frac{\mathit{flow\_out}_{n,i,c,t} - \mathit{flow\_in}_{n,i,c,t}}{\mathrm{timestep\_resolution}_{t}} & \text{otherwise} \end{cases} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} +``` + +**`cost_flow_cap_sum`** + +```math +\mathrm{cost\_flow\_cap\_sum}_{n,i,k} = \begin{cases} \left( \mathrm{cost\_flow\_cap}_{n,i,k} + \mathrm{cost\_flow\_cap\_per\_distance}_{n,i,k} \cdot \mathrm{distance}_{i} \right) \cdot 0.5 & \text{if } \mathrm{base\_tech}_{i} = \text{'}\mathrm{transmission}\text{'} \\ \mathrm{cost\_flow\_cap}_{n,i,k} & \text{otherwise} \end{cases} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ k \in \mathcal{K} +``` + +**`cost_investment_flow_cap`** + +```math +\mathit{cost\_investment\_flow\_cap}_{n,i,c,k} = \mathrm{cost\_flow\_cap\_sum}_{n,i,k} \cdot \mathit{flow\_cap}_{n,i,c} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ k \in \mathcal{K} +``` + +**`flows_carrier_flow`** + +```math +\mathit{flows\_carrier\_flow}_{n,c,t} = \sum_{i \in \mathcal{I}} \mathit{flow\_out}_{n,i,c,t} - \left( \sum_{i \in \mathcal{I}} \mathit{flow\_in}_{n,i,c,t} \right) \qquad \forall\, n \in \mathcal{N},\ c \in \mathcal{C},\ t \in \mathcal{T} +``` + +**`flows_cost_investment`** + +```math +\mathit{flows\_cost\_investment}_{n,i,k} = \sum_{c \in \mathcal{C}} \mathit{cost\_investment\_flow\_cap}_{n,i,c,k} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ k \in \mathcal{K} +``` + +**`flows_cost_operation_variable`** + +```math +\mathit{flows\_cost\_operation\_variable}_{n,i,k,t} = \mathrm{timestep\_weights}_{t} \cdot \left( \sum_{c \in \mathcal{C}} \mathrm{cost\_flow\_out}_{n,i,k,t} \cdot \mathit{flow\_out}_{n,i,c,t} + \sum_{c \in \mathcal{C}} \mathrm{cost\_flow\_in}_{n,i,k,t} \cdot \mathit{flow\_in}_{n,i,c,t} \right) \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ k \in \mathcal{K},\ t \in \mathcal{T} +``` + +**`flows_cost_operation_fixed`** + +```math +\mathit{flows\_cost\_operation\_fixed}_{n,i,k} = \mathit{annualisation\_weight} \cdot \left( \sum_{c \in \mathcal{C}} \mathrm{cost\_om\_annual}_{n,i,k} \cdot \mathit{flow\_cap}_{n,i,c} \right) \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ k \in \mathcal{K} +``` + +#### Variable domains + +**`flow_cap`** + +```math +\mathrm{flow\_cap\_min}_{n,i} \le \mathit{flow\_cap}_{n,i,c} \le \mathrm{flow\_cap\_max}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C} \,:\, \mathrm{carrier\_in}_{n,i,c} \vee \mathrm{carrier\_out}_{n,i,c} +``` + +**`flow_out`** + +```math +\mathit{flow\_out}_{n,i,c,t} \ge 0 \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} \,:\, \mathrm{carrier\_out}_{n,i,c} \wedge \neg \left( \mathrm{one\_way}_{i} \wedge \left( i,\ n \right) \in \mathrm{link\_from} \right) +``` + +**`flow_in`** + +```math +\mathit{flow\_in}_{n,i,c,t} \ge 0 \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} \,:\, \mathrm{carrier\_in}_{n,i,c} \wedge \neg \left( \mathrm{one\_way}_{i} \wedge \left( i,\ n \right) \in \mathrm{link\_to} \right) +``` + +#### Assumptions + +**`must_have_base`** + +```math +\mathrm{base\_tech}_{i} \text{ is defined} \qquad \forall\, i \in \mathcal{I} +``` + +**`base_tech_one_of`** + +```math +\mathrm{base\_tech}_{i} = \text{'}\mathrm{demand}\text{'} \vee \mathrm{base\_tech}_{i} = \text{'}\mathrm{supply}\text{'} \vee \mathrm{base\_tech}_{i} = \text{'}\mathrm{conversion}\text{'} \vee \mathrm{base\_tech}_{i} = \text{'}\mathrm{storage}\text{'} \vee \mathrm{base\_tech}_{i} = \text{'}\mathrm{transmission}\text{'} \qquad \forall\, i \in \mathcal{I} +``` + +**`distance_only_for_transmission`** + +```math +\mathrm{distance}_{i} = 1 \wedge \mathrm{flow\_in\_eff\_per\_distance}_{n,i,c,t} = 1 \wedge \mathrm{flow\_out\_eff\_per\_distance}_{n,i,c,t} = 1 \wedge \neg \left( \mathrm{cost\_flow\_cap\_per\_distance}_{n,i,k} \text{ is defined} \right) \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ k \in \mathcal{K},\ t \in \mathcal{T} \,:\, \neg \left( \mathrm{base\_tech}_{i} = \text{'}\mathrm{transmission}\text{'} \right) +``` + +**`unbounded_flow_cap_cost`** + +```math +\neg \left( \mathrm{cost\_flow\_cap}_{n,i,k} < 0 \right) \vee \mathrm{flow\_cap\_max}_{n,i} \text{ is defined} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ k \in \mathcal{K} +``` + diff --git a/docs/examples/calliope/index.md b/docs/examples/calliope/index.md new file mode 100644 index 00000000..4662f132 --- /dev/null +++ b/docs/examples/calliope/index.md @@ -0,0 +1,143 @@ + + +# Calliope in fragments + +[Calliope](https://github.com/calliope-project/calliope) states its math in +YAML: a base, modes laid over it, and examples of math a modeller adds. This +is all of it, from Calliope `v0.7.0` (`src/calliope/math/` and +`docs/user_defined_math/examples/`), as files under `examples/calliope/`. +[The port record](port.md) lists every Calliope block with how it is stated +here, and what mathspec is missing where the port is not one block for one. + +Calliope composes its math by overriding: a mode or an example restates a +base block whole to change it. Here, three kinds of file do that work, and +[the PyPSA split](../pypsa/index.md) uses the first two. + +- **A base fragment** is one topic of Calliope's base math. `merge` composes + the fragments into one spec. +- **An extension** adds to the base: new rows, new decisions, and a term to a + sum the base declares. `merge` composes it with the base. Where Calliope + restates `system_balance`, `cost_investment`, `cost_operation_fixed` or the + objective to add one term, the extension adds the term and no base file + changes. +- **A variant** changes what the base states: a mode, or an example that + rewrites a base row. `override` lays it over the composition. A variant is + a patch, not a spec, and it prints as the declarations it writes. + +## Compose a model + +```python +from pathlib import Path + +import mathspec as ms + +here = Path('examples/calliope') +base = sorted(here.glob('*.yaml')) + +plan = ms.merge(base) +milp = ms.override(ms.merge([*base, here / 'extensions/milp.yaml']), [here / 'variants/milp.yaml']) +operate = ms.override(plan, [here / 'variants/operate.yaml']) +spores = ms.override(plan, [here / 'variants/spores.yaml']) +clustered = ms.override(plan, [here / 'variants/storage_inter_cluster.yaml']) +``` + +A mode and an extension compose as Calliope's do: the MILP fragment and its +patch, then operate mode and its MILP half, then an example. + +```python +fuel = ms.merge([*base, here / 'extensions/fuel_dist.yaml']) +chp = ms.override(ms.merge([*base, here / 'extensions/chp_htp.yaml']), [here / 'variants/chp_htp.yaml']) +``` + +## What a file needs beside it + +A fragment reads what it does not declare under +[`given`](../../reference/language/declarations.md#given), and loads and +prints alone. A model needs the file that declares each name a fragment +reads, and `merge` keeps what no file declares under `given:`. + +- `settings`, `balance`, `flows` and `cost` are the core. They read one + another, and the four compose with nothing left under `given:`. +- `conversion` and `transmission` read `flows` alone. `storage`, `export`, + `feasibility` and `reporting` read the core, and `reporting` reads `export`. +- `area`, `demand` and `supply` read one another. A source or a sink per unit + of area reads `area_use`, and `area_use` is built where a source or a sink + is per unit of area. Compose the three together. Without `demand` and + `supply`, `area` keeps `sink_unit` and `source_unit` under `given:`. +- `supply_storage` couples `supply` and `storage`, and needs both. +- `extensions/milp.yaml` reads the capacities of `storage`, `area` and + `supply`. `piecewise_linear_costs`, `piecewise_linear_efficiency` and + `uptime_downtime_limits` read the units it builds, so they need it. +- `piecewise_linear_costs` and `sos2_piecewise_linear_costs` both declare + `piecewise_cost_investment`, so `merge` refuses the two together. The CHP + variants both rewrite `balance_conversion`. Calliope's examples are + alternatives in the same way. + +## Conventions + +- **Names are Calliope's.** A declaration Calliope names is spelled as + Calliope spells it, and its description opens with that name. A sub-expression + Calliope writes as `$name` is a named expression. A name Calliope has no word + for, such as a term of a sum, carries the name of the file that adds it. +- **A parameter's default is data preparation.** Calliope reads a missing + value as the parameter's `default:` in arithmetic, and as not given in a + `where:`. Here a missing row reads as `0` and as false. Where a default is + not zero and the math reads the value, data prep fills it, and the + description says so. A bound has a row wherever its variable has one, so + data prep fills its default too. Where the math reads the parameter only + behind a `where:` that tests it, the description says "given only where set". +- **A variable with a mask is zero outside it.** Calliope's `default: 0` is + `absence: zero`, so a sum of costs keeps its terms where one of them is + masked. +- **A parameter carries the dimensions Calliope's examples give it.** The + timesteps are added where Calliope resamples the parameter. + + +### The sums + +| Sum | Over | Declared in | The terms, by the fragment that adds each | +| --- | --- | --- | --- | +| `cost_investment` | `nodes, techs, costs` | [cost](cost.md) | [`cost_investment_area_use`](area.md), [`flows_cost_investment`](flows.md), [`cost_investment_purchase`](extensions/milp.md), [`piecewise_cost_investment_term`](extensions/piecewise_linear_costs.md), [`cost_investment_piecewise`](extensions/sos2_piecewise_linear_costs.md), [`cost_investment_storage_cap`](storage.md), [`cost_investment_source_cap`](supply.md) | +| `carrier_flow` | `nodes, carriers, timesteps` | [balance](balance.md) | [`export_carrier_flow`](export.md), [`feasibility_carrier_flow`](feasibility.md), [`flows_carrier_flow`](flows.md), [`fuel_dist_carrier_flow`](extensions/fuel_dist.md) | +| `cost_operation_variable` | `nodes, techs, costs, timesteps` | [cost](cost.md) | [`export_cost_operation_variable`](export.md), [`flows_cost_operation_variable`](flows.md), [`supply_cost_operation_variable`](supply.md) | +| `system_cost` | nothing: one number | [settings](settings.md) | [`cost_of_techs`](cost.md), [`fuel_dist_system_cost`](extensions/fuel_dist.md) | +| `cost_operation_fixed` | `nodes, techs, costs` | [cost](cost.md) | [`flows_cost_operation_fixed`](flows.md), [`cost_month_peak_charge`](extensions/monthly_peak_flow_charge.md) | +| `penalty` | nothing: one number | [settings](settings.md) | [`unmet_demand_penalty`](feasibility.md) | + +### The fragments + +| Fragment | Parameters | Variables | Constraints | Reads | Adds to | +| --- | --- | --- | --- | --- | --- | +| [area](area.md) | 5 | 1 | 3 | 5 | `cost_investment` | +| [balance](balance.md) | 0 | 0 | 1 | 2 | | +| [conversion](conversion.md) | 0 | 0 | 1 | 4 | | +| [cost](cost.md) | 5 | 0 | 0 | 4 | `system_cost` | +| [demand](demand.md) | 4 | 0 | 3 | 5 | | +| [export](export.md) | 4 | 1 | 1 | 5 | `carrier_flow`, `cost_operation_variable` | +| [feasibility](feasibility.md) | 0 | 2 | 0 | 6 | `carrier_flow`, `penalty` | +| [flows](flows.md) | 22 | 3 | 7 | 7 | `carrier_flow`, `cost_investment`, `cost_operation_variable`, `cost_operation_fixed` | +| [reporting](reporting.md) | 0 | 0 | 0 | 6 | | +| [settings](settings.md) | 4 | 0 | 0 | 0 | | +| [storage](storage.md) | 10 | 2 | 6 | 7 | `cost_investment` | +| [supply](supply.md) | 10 | 2 | 6 | 10 | `cost_investment`, `cost_operation_variable` | +| [supply_storage](supply_storage.md) | 0 | 0 | 1 | 7 | | +| [transmission](transmission.md) | 0 | 0 | 2 | 5 | | +| [annual_energy_balance](extensions/annual_energy_balance.md) | 5 | 0 | 5 | 4 | | +| [chp_htp](extensions/chp_htp.md) | 4 | 0 | 6 | 6 | | +| [demand_share_per_timestep_decision](extensions/demand_share_per_timestep_decision.md) | 2 | 1 | 3 | 2 | | +| [fuel_dist](extensions/fuel_dist.md) | 4 | 1 | 3 | 4 | `carrier_flow`, `system_cost` | +| [max_time_varying](extensions/max_time_varying.md) | 1 | 0 | 1 | 3 | | +| [milp](extensions/milp.md) | 11 | 4 | 26 | 22 | `cost_investment` | +| [monthly_peak_flow_charge](extensions/monthly_peak_flow_charge.md) | 2 | 1 | 1 | 4 | `cost_operation_fixed` | +| [net_import_share](extensions/net_import_share.md) | 1 | 0 | 3 | 4 | | +| [piecewise_linear_costs](extensions/piecewise_linear_costs.md) | 2 | 1 | 1 | 3 | `cost_investment` | +| [piecewise_linear_efficiency](extensions/piecewise_linear_efficiency.md) | 2 | 0 | 1 | 3 | | +| [share_all_timesteps](extensions/share_all_timesteps.md) | 2 | 0 | 2 | 2 | | +| [share_per_timestep](extensions/share_per_timestep.md) | 2 | 0 | 2 | 2 | | +| [sos2_piecewise_linear_costs](extensions/sos2_piecewise_linear_costs.md) | 2 | 2 | 1 | 2 | `cost_investment` | +| [uptime_downtime_limits](extensions/uptime_downtime_limits.md) | 4 | 0 | 4 | 6 | | +| [urban_scale_chp](extensions/urban_scale_chp.md) | 1 | 0 | 2 | 5 | | + diff --git a/docs/examples/calliope/port.md b/docs/examples/calliope/port.md new file mode 100644 index 00000000..cf7ce777 --- /dev/null +++ b/docs/examples/calliope/port.md @@ -0,0 +1,194 @@ + + +# The port record + +Every block of Calliope `v0.7.0`'s math, with the file that states it here. +A block is **done** where one block states it as Calliope does. It is +**split** where the same rows and the same optimum have a different +statement, such as two blocks for two `equations:`, or a term of a sum for a +restated sum. It is **prep** where a column the file cannot compute comes +from data preparation. It is **out** where the file deliberately states +nothing, and the note says why. + +Parameters are not listed: each one Calliope declares is a parameter of the +same name, in the file that reads it, with [the conventions](index.md#conventions) +for defaults and dimensions. The four exceptions are rows below. + +## What mathspec is missing + +Each gap below is a place the port is not one block for one. None stops a +block from being stated. + +1. **`**` and `/` over a sum of parameters.** mathspec refuses + `(1 - storage_loss) ** timestep_resolution` and Calliope's annuity factor, + whose base and divisor are sums, although no variable is in them. Data + prep supplies `storage_retention` and `cost_annuity_factor`. +2. **A default that is not zero.** Calliope's `default:` is the value + arithmetic reads and, at the same time, "not given" to a `where:`. A + mathspec parameter has one reading of a missing row, `0`, so data prep + fills each default the math reads. Two masks change: `area_use` is built + where `area_use_min > 0`, where Calliope builds it where `area_use_min` is + given at all, and `distance_only_for_transmission` checks the filled + defaults, not what the modeller wrote. +3. **A `where:` on a `piecewise:` block.** A curve has a row per link at every + coordinate of its frame. Calliope's SOS2 costs hold only where a technology + has breakpoints, and elsewhere the link row would pin `flow_cap` to zero. + The link reads `piecewise_flow_cap`, a copy of `flow_cap` masked to the + technologies with a curve. +4. **One label of a dimension.** Calliope writes + `flow_out[carriers=electricity]`. Here a named expression takes the value + where `carriers == electricity` and zero elsewhere, and a sum over + `carriers` reads it. The CHP, net-import and urban-scale files hold eleven + such expressions. +5. **A label read off the coordinate it indexes.** Calliope's `map_dim(nodes, +link_from)`, and a slice by a per-technology carrier, compare a lookup with + the dimension the operand already carries. `at` reads onto a dimension the + operand does not carry, and a `where:` does not compare a relation column + with its dimension. The port declares these lookups as bare relations over + the pair, `link_from: {key: [techs, nodes]}`, and tests the pair in a case. +6. **One parameter at several shapes.** Calliope's `annual_flow_max` and + `net_import_share` are read per technology, per node and over a group, and + the data decides. A mathspec parameter has one shape, so the group row of + `annual_energy_balance` reads a scalar of its own, and `net_import_share` + is one number. +7. **An empty sum that no file adds to.** An `empty: true` sum with no term is + a column the spec reads and does not build, not zero. `penalty` has the body + `0`, so a model with no feasibility file reads a zero penalty. +8. **`sum(over=[a, b])`.** Calliope sums over a list of dimensions. mathspec + takes one dimension per `sum`, so the port nests them. +9. **A warning.** An assumption holds or the data is refused. Calliope's three + checks at `errors: warn` are out. + +The port also found two mathspec bugs, fixed in their own pull requests: +`advice` raised `KeyError` on an objective that reads a sum other files add +to ([#767](https://github.com/energy-models/mathspec/pull/767)), and `merge` +refused a given variable read by its frame alone where the introducer is an +integer ([#768](https://github.com/energy-models/mathspec/pull/768)). + +## `base.yaml` + +| Calliope | status | here | note | +| -------------------------------------------------------------------------------------------------------------------------------------- | ------ | ---------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | +| dimensions `carriers`, `costs`, `nodes`, `techs`, `timesteps` | done | every fragment | `clusters` and `datesteps` in the inter-cluster variant | +| lookups `base_tech`, `carrier_in`, `carrier_out`, `include_storage`, `one_way` | done | [flows](flows.md) | a `one_of` is an assumption | +| lookups `link_from`, `link_to` | split | [flows](flows.md), [transmission](transmission.md) | a bare relation over technology and node, gap 5 | +| lookups `carrier_export`, `cyclic_storage`, `cluster_first_timestep`, `source_cap_equals_flow_cap` | done | [export](export.md), [storage](storage.md), [supply](supply.md) | | +| lookup `lookup_cluster_last_timestep` | done | [storage](storage.md) | a relation from a time step onto a time step | +| lookups `sink_unit`, `source_unit` | done | [demand](demand.md), [supply](supply.md) | `absolute`, the default, is what no row reads as | +| lookups `latitude`, `longitude` | out | | read by no math; Calliope derives `distance` from them in data preparation | +| parameters `storage_loss`, `cost_interest_rate` with `lifetime` | prep | [storage](storage.md), [cost](cost.md) | `storage_retention` and `cost_annuity_factor`, gap 1 | +| `flow_capacity_per_storage_capacity_min`, `_max` | done | [storage](storage.md) | | +| `source_capacity_equals_flow_capacity` | done | [supply](supply.md) | | +| `force_zero_area_use`, `area_use_per_flow_capacity` | done | [area](area.md) | | +| `area_use_capacity_per_loc` | split | [area](area.md) | `where: area_use` over a node is a count over technologies | +| `flow_capacity_systemwide_max`, `_min` | done | [flows](flows.md) | `any(…, over=nodes)` is `count(…, over=nodes) >= 1` | +| `balance_conversion` | done | [conversion](conversion.md) | | +| `flow_out_max`, `flow_out_min`, `flow_in_max` | done | [flows](flows.md) | | +| `source_max` | done | [supply](supply.md) | | +| `storage_max`, `storage_discharge_depth_limit` | done | [storage](storage.md) | | +| `system_balance` | split | [balance](balance.md) | `carrier_flow == 0`, the sum of a term from each file that moves a carrier | +| `balance_demand` | split | [demand](demand.md) | `balance_demand_equals` and `balance_demand_max`, one block per equation | +| `balance_demand_min_use` | done | [demand](demand.md) | | +| `balance_supply_no_storage` | done | [supply](supply.md) | | +| `balance_supply_with_storage` | done | [supply_storage](supply_storage.md) | | +| `source_availability_supply` | split | [supply](supply.md) | `_equals` and `_max`, one block per equation | +| `balance_supply_min_use` | done | [supply](supply.md) | | +| `balance_storage` | done | [storage](storage.md) | | +| `set_storage_initial` | split | [storage](storage.md) | one row per store, built at the last time step rather than read there | +| `balance_transmission`, `symmetric_transmission` | done | [transmission](transmission.md) | `where(flow_cap, map_dim(…))` is a case over the bare relation, gap 5 | +| `export_balance` | done | [export](export.md) | | +| `ramping_up`, `ramping_down` | done | [flows](flows.md) | the first time step is out of the row, so `roll` is a `shift` | +| variables `flow_cap`, `flow_out`, `flow_in`, `flow_export`, `source_use`, `source_cap`, `storage_cap`, `storage` | done | [flows](flows.md), [export](export.md), [supply](supply.md), [storage](storage.md) | `default: 0` is `absence: zero` | +| variable `area_use` | split | [area](area.md) | built where `area_use_min > 0`, gap 2 | +| variables `unmet_demand`, `unused_supply` | split | [feasibility](feasibility.md) | `config.ensure_feasibility` is whether the file is composed | +| objective `min_cost_optimisation` | split | [settings](settings.md) | `system_cost + penalty`, each a sum other files add to | +| `$unmet_demand` | done | [feasibility](feasibility.md) | `unmet_demand_penalty`, a term of `penalty` | +| `flow_out_inc_eff`, `flow_in_inc_eff` | done | [flows](flows.md) | | +| `cost_operation_variable` | split | [cost](cost.md) | a sum; flows, supply and export each add their cost. Calliope's two cases, supply or not, are one sum, since a supply technology has no inflow | +| `cost_investment_flow_cap`, `_storage_cap`, `_source_cap`, `_area_use` | done | [flows](flows.md), [storage](storage.md), [supply](supply.md), [area](area.md) | each is a term of `cost_investment`, and the flow capacity one through `flows_cost_investment` | +| `cost_investment` | split | [cost](cost.md) | a sum each capacity adds to | +| `cost_investment_annualised` | prep | [cost](cost.md) | the annuity factor, gap 1 | +| `cost_operation_fixed` | split | [cost](cost.md) | its body here, and `flows_cost_operation_fixed` added | +| `cost` | done | [cost](cost.md) | | +| postprocessed `capacity_factor`, `systemwide_capacity_factor`, `total_generation`, `systemwide_levelised_cost`, `total_levelised_cost` | done | [reporting](reporting.md) | reported; the `where:` is the absence of what they read | +| postprocessed `unmet_sum` | done | [feasibility](feasibility.md) | reported | +| postprocessed `curtailment`, `total_curtailment` | done | [supply](supply.md) | reported | +| check `must_have_base` | done | [flows](flows.md) | an assumption | +| check `distance_only_for_transmission` | split | [flows](flows.md) | checks the filled defaults, gap 2 | +| checks `unbounded_*_cost` | done | [flows](flows.md), [storage](storage.md), [area](area.md), [supply](supply.md) | | +| check `finite_source_use` | split | [supply](supply.md), [demand](demand.md) | one assumption per parameter | +| check `all_or_nothing_lat_lon` | out | | the coordinates are data preparation | +| checks `export_only_for_outflows`, `storage_initial_max`, `cyclic_storage_needs_inter_cluster` | done | [export](export.md), [storage](storage.md) | | +| checks `flow_in_for_supply_tech`, `flow_out_for_demand_tech`, `no_storage_discharge_depth_when_clustering` | out | | warnings, gap 9 | + +## `milp.yaml` + +| Calliope | status | here | note | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ------------------------------------------------------- | ----------------------------------------------------------------------------- | +| lookups `cap_method`, `integer_dispatch`, `force_async_flow` | done | [milp](extensions/milp.md) | | +| variables `purchased_units`, `operating_units`, `available_flow_cap` | done | [milp](extensions/milp.md) | | +| variable `async_flow_switch` | done | [milp](extensions/milp.md) | an integer in `[0, 1]` is `domain: binary` | +| bounds of `flow_cap`, `area_use`, `source_cap`, `storage_cap` | done | [variant](variants/milp.md) | | +| `unit_commitment_milp`, `flow_out_max_milp`, `flow_in_max_milp`, `storage_capacity_units_milp`, `flow_capacity_units_milp`, `storage_capacity_max_purchase_milp`, `unit_capacity_max_systemwide_milp`, `async_flow_in_milp`, `async_flow_out_milp`, `available_flow_cap_continuous`, `available_flow_cap_binary`, `available_flow_cap_max_binary_continuous_switch` | done | [milp](extensions/milp.md) | | +| `unit_capacity_min_systemwide_milp` | done | [milp](extensions/milp.md) | built where the system-wide maximum is set, as Calliope writes it | +| `flow_out_min_milp`, `flow_capacity_max_purchase_milp`, `flow_capacity_minimum`, `storage_capacity_minimum`, `area_use_minimum`, `source_capacity_minimum` | split | [milp](extensions/milp.md) | one block per equation | +| `flow_out_max`, `flow_out_min`, `flow_in_max` | done | [variant](variants/milp.md) | the `where:` only | +| `flow_capacity_systemwide_min` | split | [variant](variants/milp.md), [milp](extensions/milp.md) | the patch narrows the base row; the purchased case is a block of the fragment | +| `cost_investment_purchase` | done | [milp](extensions/milp.md) | a term of `cost_investment` | +| `cost_investment` | split | [milp](extensions/milp.md) | Calliope restates it whole; here the purchase is a term | +| descriptions of `flow_cap_min`, `flow_cap_min_systemwide`, `flow_out_min_relative` | done | [variant](variants/milp.md) | | +| checks | done | [milp](extensions/milp.md) | assumptions; `cap_method`'s `one_of` too | + +## `operate.yaml` + +| Calliope | status | here | note | +| -------------------------------------------------------------- | ------ | ------------------------------------------------------------------------ | -------------------------------------------------------------- | +| parameters `flow_cap`, `area_use`, `source_cap`, `storage_cap` | done | [variant](variants/operate.md) | each replaces the variable of its name | +| parameter `purchased_units` | done | [variant](variants/operate_milp.md) | | +| every `.active: false` | done | [operate](variants/operate.md), [operate_milp](variants/operate_milp.md) | a removal; the terms of the removed sums are removed with them | +| `cost` | done | [variant](variants/operate.md) | | +| check `operate_mode_cyclic_storage` | done | [variant](variants/operate.md) | | +| operate mode's window and horizon | out | | a loop of solves, not math | + +## `spores.yaml` + +| Calliope | status | here | note | +| ----------------------------------------------------------------------- | ------ | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | +| parameters | done | [variant](variants/spores.md) | | +| `total_system_cost_max` | split | [variant](variants/spores.md) | one row over `system_cost + penalty`. Calliope restates the objective, and misses a cost an example adds to it, such as fuel distribution | +| objective `min_spores` | done | [variant](variants/spores.md) | | +| postprocessed `spores_score_cumulative`, `spores_baseline_cost_tracked` | done | [variant](variants/spores.md) | reported | +| the SPORES iterations | out | | a loop of solves that updates `spores_score`, not math | + +## `storage_inter_cluster.yaml` + +| Calliope | status | here | note | +| ----------------------------------------------------------------------------------------------------------- | ------ | -------------------------------------------- | ----------------------------------------------------------------------------------------- | +| lookups `timestep_cluster`, `lookup_datestep_cluster`, `lookup_datestep_last_cluster_timestep` | done | [variant](variants/storage_inter_cluster.md) | relations | +| `storage_max`, `cyclic_storage_needs_inter_cluster` off | done | [variant](variants/storage_inter_cluster.md) | removals | +| `$storage_previous_step` of `balance_supply_with_storage` and `balance_storage` | done | [variant](variants/storage_inter_cluster.md) | one named expression, patched once | +| `set_storage_initial` | split | [variant](variants/storage_inter_cluster.md) | built at the last day, as in the base | +| `storage_intra_max`, `storage_intra_min`, `storage_inter_max`, `storage_inter_min`, `balance_storage_inter` | done | [variant](variants/storage_inter_cluster.md) | a slice by a lookup is `at` through the relation | +| variables | done | [variant](variants/storage_inter_cluster.md) | | +| `storage_loss` over time steps | prep | [variant](variants/storage_inter_cluster.md) | `storage_retention ** 24` reads it per day, so the variant declares it without time steps | + +## The examples + +| Calliope | status | here | note | +| ----------------------------------------------------- | ------ | ---------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| `annual_energy_balance.yaml` | split | [annual_energy_balance](extensions/annual_energy_balance.md) | the group row reads `annual_flow_max_group`, gap 6; `where: source_use` over a technology is `base_tech == 'supply'` | +| `chp_htp.yaml` | split | [chp_htp](extensions/chp_htp.md), [variant](variants/chp_htp.md) | the rewritten `balance_conversion` is a patch and a block of its own; a carrier slice is a case, gap 4 | +| `demand_share_per_timestep_decision.yaml` | split | [demand_share_per_timestep_decision](extensions/demand_share_per_timestep_decision.md) | the sum row is one per node: Calliope repeats it in every time step, and mathspec refuses a row repeated along a dimension it does not read | +| `fuel_dist.yaml` | split | [fuel_dist](extensions/fuel_dist.md) | Calliope restates `system_balance` and the objective; here each gains a term | +| `max_time_varying.yaml` | done | [max_time_varying](extensions/max_time_varying.md) | | +| `monthly_peak_flow_charge.yaml` | split | [monthly_peak_flow_charge](extensions/monthly_peak_flow_charge.md) | the month is a relation, so the row is one per time step; the charge is a term of `cost_operation_fixed` | +| `net_import_share.yaml` | split | [net_import_share](extensions/net_import_share.md) | a carrier and a node group are cases, gap 4; the share is one number, gap 6 | +| `piecewise_linear_costs.yaml` | split | [piecewise_linear_costs](extensions/piecewise_linear_costs.md) | a term of `cost_investment` | +| `piecewise_linear_efficiency.yaml` | done | [piecewise_linear_efficiency](extensions/piecewise_linear_efficiency.md) | | +| `share_all_timesteps.yaml`, `share_per_timestep.yaml` | split | [share_all_timesteps](extensions/share_all_timesteps.md), [share_per_timestep](extensions/share_per_timestep.md) | the demand technology is `at` through a relation; the carrier is a case, gap 5 | +| `sos2_piecewise_linear_costs.yaml` | split | [sos2_piecewise_linear_costs](extensions/sos2_piecewise_linear_costs.md) | `piecewise:` with `method: sos2`, over a masked copy of `flow_cap`, gap 3 | +| `uptime_downtime_limits.yaml` | done | [uptime_downtime_limits](extensions/uptime_downtime_limits.md) | | +| urban-scale `additional_math.yaml` | split | [urban_scale_chp](extensions/urban_scale_chp.md), [variant](variants/urban_scale_chp.md) | as the CHP example | diff --git a/docs/examples/calliope/reporting.md b/docs/examples/calliope/reporting.md new file mode 100644 index 00000000..e8cc4d0a --- /dev/null +++ b/docs/examples/calliope/reporting.md @@ -0,0 +1,120 @@ + + +# Reporting + +One of the base fragments of [Calliope in fragments](index.md). Calliope's postprocessed results: capacity factors, total generation and levelised costs. Every entry is reported, so none of it is in the math, and a quotient may divide by a variable. + + +```yaml +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + costs: + description: Calliope's `costs` — cost classes, such as monetary and CO2 + +given: + parameters: + timestep_resolution: { dims: [timesteps] } + timestep_weights: { dims: [timesteps] } + variables: + flow_cap: { dims: [nodes, techs, carriers] } + flow_out: { dims: [nodes, techs, carriers, timesteps] } + flow_export: { dims: [nodes, techs, carriers, timesteps] } + expressions: + cost: { dims: [nodes, techs, costs] } + +expressions: + capacity_factor: + description: "`capacity_factor` — the share of its flow capacity a technology puts out in a time step" + expression: flow_out / (flow_cap * timestep_resolution) + systemwide_capacity_factor: + description: "`systemwide_capacity_factor` — the share of its flow capacity a technology puts out over every node and time step" + expression: >- + sum(sum(flow_out * timestep_weights, over=nodes), over=timesteps) + / (sum(flow_cap, over=nodes) * sum(timestep_resolution * timestep_weights, over=timesteps)) + total_generation: + description: >- + `total_generation` — outflow over every node and time step. Calliope + weights only the export, as written here + expression: sum(sum(flow_out + flow_export * timestep_weights, over=nodes), over=timesteps) + systemwide_levelised_cost: + description: "`systemwide_levelised_cost` — a technology's cost per unit of what it generates, over every node" + expression: sum(cost, over=nodes) / total_generation + total_levelised_cost: + description: "`total_levelised_cost` — the system's cost per unit of a carrier generated" + expression: sum(sum(cost, over=nodes), over=techs) / sum(total_generation, over=techs) +``` + +#### Sets + +| Symbol | Meaning | +|---|---| +| $`\mathcal{N}`$ | index $`n`$ — `nodes` — Calliope's `nodes` — the places technologies stand at | +| $`\mathcal{I}`$ | index $`i`$ — `techs` — Calliope's `techs` — technologies | +| $`\mathcal{C}`$ | index $`c`$ — `carriers` — Calliope's `carriers` — energy and commodity carriers | +| $`\mathcal{T}`$ | index $`t`$ — `timesteps` — Calliope's `timesteps` — time steps, in order | +| $`\mathcal{K}`$ | index $`k`$ — `costs` — Calliope's `costs` — cost classes, such as monetary and CO2 | + +#### Given + +| Symbol | Meaning | +|---|---| +| $`\mathrm{timestep\_resolution}`$ | `timestep_resolution` over $`\mathcal{T}`$, data another file declares | +| $`\mathrm{timestep\_weights}`$ | `timestep_weights` over $`\mathcal{T}`$, data another file declares | +| $`\mathit{flow\_cap}`$ | `flow_cap` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C}`$ | +| $`\mathit{flow\_out}`$ | `flow_out` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ | +| $`\mathit{flow\_export}`$ | `flow_export` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ | +| $`\mathit{cost}`$ | `cost` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K}`$, an expression another file defines | + +#### Definitions + +| Symbol | Meaning | +|---|---| +| $`\mathit{capacity\_factor}`$ | `capacity_factor` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$ — `capacity_factor` — the share of its flow capacity a technology puts out in a time step | +| $`\mathit{systemwide\_capacity\_factor}`$ | `systemwide_capacity_factor` over $`\mathcal{I} \times \mathcal{C}`$ — `systemwide_capacity_factor` — the share of its flow capacity a technology puts out over every node and time step | +| $`\mathit{total\_generation}`$ | `total_generation` over $`\mathcal{I} \times \mathcal{C}`$ — `total_generation` — outflow over every node and time step. Calliope weights only the export, as written here | +| $`\mathit{systemwide\_levelised\_cost}`$ | `systemwide_levelised_cost` over $`\mathcal{I} \times \mathcal{C} \times \mathcal{K}`$ — `systemwide_levelised_cost` — a technology's cost per unit of what it generates, over every node | +| $`\mathit{total\_levelised\_cost}`$ | `total_levelised_cost` over $`\mathcal{C} \times \mathcal{K}`$ — `total_levelised_cost` — the system's cost per unit of a carrier generated | + +#### Definitions + +**`capacity_factor`** + +```math +\mathit{capacity\_factor}_{n,i,c,t} = \frac{\mathit{flow\_out}_{n,i,c,t}}{\mathit{flow\_cap}_{n,i,c} \cdot \mathrm{timestep\_resolution}_{t}} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} +``` + +**`systemwide_capacity_factor`** + +```math +\mathit{systemwide\_capacity\_factor}_{i,c} = \frac{\sum_{t \in \mathcal{T}} \sum_{n \in \mathcal{N}} \mathit{flow\_out}_{n,i,c,t} \cdot \mathrm{timestep\_weights}_{t}}{\left( \sum_{n \in \mathcal{N}} \mathit{flow\_cap}_{n,i,c} \right) \cdot \left( \sum_{t \in \mathcal{T}} \mathrm{timestep\_resolution}_{t} \cdot \mathrm{timestep\_weights}_{t} \right)} \qquad \forall\, i \in \mathcal{I},\ c \in \mathcal{C} +``` + +**`total_generation`** + +```math +\mathit{total\_generation}_{i,c} = \sum_{t \in \mathcal{T}} \sum_{n \in \mathcal{N}} \left( \mathit{flow\_out}_{n,i,c,t} + \mathit{flow\_export}_{n,i,c,t} \cdot \mathrm{timestep\_weights}_{t} \right) \qquad \forall\, i \in \mathcal{I},\ c \in \mathcal{C} +``` + +**`systemwide_levelised_cost`** + +```math +\mathit{systemwide\_levelised\_cost}_{i,c,k} = \frac{\sum_{n \in \mathcal{N}} \mathit{cost}_{n,i,k}}{\mathit{total\_generation}_{i,c}} \qquad \forall\, i \in \mathcal{I},\ c \in \mathcal{C},\ k \in \mathcal{K} +``` + +**`total_levelised_cost`** + +```math +\mathit{total\_levelised\_cost}_{c,k} = \frac{\sum_{i \in \mathcal{I}} \sum_{n \in \mathcal{N}} \mathit{cost}_{n,i,k}}{\sum_{i \in \mathcal{I}} \mathit{total\_generation}_{i,c}} \qquad \forall\, c \in \mathcal{C},\ k \in \mathcal{K} +``` + diff --git a/docs/examples/calliope/settings.md b/docs/examples/calliope/settings.md new file mode 100644 index 00000000..bb7cb004 --- /dev/null +++ b/docs/examples/calliope/settings.md @@ -0,0 +1,107 @@ + + +# Settings + +One of the base fragments of [Calliope in fragments](index.md). The weightings every topic reads, and the objective. The objective is the system cost plus a penalty, and both are sums other files add to, so this file names no technology. + + +```yaml +dimensions: + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + costs: + description: Calliope's `costs` — cost classes, such as monetary and CO2 + +parameters: + timestep_resolution: + description: >- + `timestep_resolution` — hours a time step lasts. Calliope's default is + 1, and data prep fills it + dims: [timesteps] + timestep_weights: + description: >- + `timestep_weights` — how many times a time step counts, as after + clustering. Calliope's default is 1, and data prep fills it + dims: [timesteps] + objective_cost_weights: + description: >- + `objective_cost_weights` — what one unit of a cost class weighs in the + objective. Calliope's default is 1, and data prep fills it + dims: [costs] + bigM: + description: >- + `bigM` — a number larger than any decision can take. Calliope's + default is 1e6, and data prep fills it + dims: [] + +expressions: + system_cost: + description: >- + the weighted cost of the system, over every cost class — Calliope's + `min_cost_optimisation` less its unmet-demand penalty. The cost file and + every file that prices something outside a technology add to it + dims: [] + empty: true + penalty: + description: >- + what the objective adds to the system cost to keep a model feasible — + Calliope's `$unmet_demand` sub-expression. It is zero, and a file that + keeps a model feasible adds to it + dims: [] + expression: "0" + +objective: + description: >- + `min_cost_optimisation` — the weighted cost of installing and operating + every technology, plus the penalty on unmet demand + sense: minimize + expression: system_cost + penalty +``` + +#### Sets + +| Symbol | Meaning | +|---|---| +| $`\mathcal{T}`$ | index $`t`$ — `timesteps` — Calliope's `timesteps` — time steps, in order | +| $`\mathcal{K}`$ | index $`k`$ — `costs` — Calliope's `costs` — cost classes, such as monetary and CO2 | + +#### Parameters + +| Symbol | Meaning | +|---|---| +| $`\mathrm{timestep\_resolution}`$ | `timestep_resolution` over $`\mathcal{T}`$ — `timestep_resolution` — hours a time step lasts. Calliope's default is 1, and data prep fills it | +| $`\mathrm{timestep\_weights}`$ | `timestep_weights` over $`\mathcal{T}`$ — `timestep_weights` — how many times a time step counts, as after clustering. Calliope's default is 1, and data prep fills it | +| $`\mathrm{objective\_cost\_weights}`$ | `objective_cost_weights` over $`\mathcal{K}`$ — `objective_cost_weights` — what one unit of a cost class weighs in the objective. Calliope's default is 1, and data prep fills it | +| $`\mathrm{bigM}`$ | `bigM` (scalar) — `bigM` — a number larger than any decision can take. Calliope's default is 1e6, and data prep fills it | + +#### Definitions + +| Symbol | Meaning | +|---|---| +| $`\mathrm{penalty}`$ | `penalty` (scalar) — what the objective adds to the system cost to keep a model feasible — Calliope's `$unmet_demand` sub-expression. It is zero, and a file that keeps a model feasible adds to it | +| $`\mathit{system\_cost}`$ | `system_cost` (scalar) — the weighted cost of the system, over every cost class — Calliope's `min_cost_optimisation` less its unmet-demand penalty. The cost file and every file that prices something outside a technology add to it | + +#### Objective + +```math +\min \mathit{system\_cost} + \mathrm{penalty} +``` + +#### Definitions + +**`penalty`** + +```math +\mathrm{penalty} = 0 +``` + +**`system_cost`** + +```math +\mathit{system\_cost} = \cdots +``` + diff --git a/docs/examples/calliope/storage.md b/docs/examples/calliope/storage.md new file mode 100644 index 00000000..8df957ed --- /dev/null +++ b/docs/examples/calliope/storage.md @@ -0,0 +1,321 @@ + + +# Storage + +One of the base fragments of [Calliope in fragments](index.md). Storage capacity, the stored carrier, and how a store carries its fill from one time step to the next, clustered days included. + + +```yaml +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + costs: + description: Calliope's `costs` — cost classes, such as monetary and CO2 + +relations: + lookup_cluster_last_timestep: + description: >- + `lookup_cluster_last_timestep` — the last time step of the cluster a + time step stands for, at the first time step of each clustered day + key: timesteps + values: { last: timesteps } + +parameters: + storage_cap_min: + description: "`storage_cap_min` — least storage capacity. Calliope's default is 0, and data prep fills it" + dims: [nodes, techs] + storage_cap_max: + description: "`storage_cap_max` — most storage capacity. Calliope's default is `.inf`, and data prep fills it" + dims: [nodes, techs] + storage_discharge_depth: + description: "`storage_discharge_depth` — the least a store holds, as a share of its capacity" + dims: [nodes, techs, timesteps] + storage_initial: + description: "`storage_initial` — what a store holds at the start, as a share of its capacity; given only where set" + dims: [nodes, techs] + storage_retention: + description: >- + `1 - storage_loss` — the share of what a store holds that it keeps + for an hour, data prep. mathspec refuses a sum as the base of `**`, + over parameters too + dims: [nodes, techs, timesteps] + cyclic_storage: + description: >- + `cyclic_storage` — whether a store ends where it starts. Calliope's + default is true, and data prep fills it + dims: [nodes, techs] + dtype: bool + cluster_first_timestep: + description: "`cluster_first_timestep` — whether a time step is the first of its clustered day" + dims: [timesteps] + dtype: bool + flow_cap_per_storage_cap_min: + description: "`flow_cap_per_storage_cap_min` — least flow capacity per unit of storage capacity; given only where set" + dims: [nodes, techs] + flow_cap_per_storage_cap_max: + description: "`flow_cap_per_storage_cap_max` — most flow capacity per unit of storage capacity; given only where set" + dims: [nodes, techs] + cost_storage_cap: + description: "`cost_storage_cap` — the cost of one unit of storage capacity" + dims: [nodes, techs, costs] + +variables: + storage_cap: + description: "`storage_cap` — the most a technology can store" + dims: [nodes, techs] + where: include_storage OR base_tech == 'storage' + bounds: { lower: storage_cap_min, upper: storage_cap_max } + absence: zero + storage: + description: "`storage` — what a technology holds at the end of a time step" + dims: [nodes, techs, timesteps] + where: include_storage OR base_tech == 'storage' + bounds: { lower: 0 } + absence: zero + +expressions: + storage_previous_step: + description: >- + `$storage_previous_step` — what a store carries into a time step: + its initial fill at the first step of a store that is not cyclic, what + is left of the last step of its clustered day at the first step of a + cluster, and what is left of the step before everywhere else + dims: [nodes, techs, timesteps] + cases: + initial: + when: position(timesteps) == 0 AND NOT cyclic_storage + expression: storage_initial * storage_cap + cluster_start: + when: cluster_first_timestep AND NOT (position(timesteps) == 0 AND NOT cyclic_storage) + expression: >- + storage_retention ** at(timestep_resolution, by=lookup_cluster_last_timestep, over=last, into=timesteps) + * at(storage, by=lookup_cluster_last_timestep, over=last, into=timesteps) + otherwise: >- + storage_retention ** shift(timestep_resolution, along=timesteps, offset=1, edge='wrap') + * shift(storage, along=timesteps, offset=1, edge='wrap') + cost_investment_storage_cap: + description: "`cost_investment_storage_cap` — the investment cost of storage capacity" + expression: cost_storage_cap * storage_cap + +given: + parameters: + base_tech: { dims: [techs], dtype: str } + include_storage: { dims: [nodes, techs], dtype: bool } + timestep_resolution: { dims: [timesteps] } + variables: + flow_cap: { dims: [nodes, techs, carriers] } + expressions: + flow_out_inc_eff: { dims: [nodes, techs, carriers, timesteps] } + flow_in_inc_eff: { dims: [nodes, techs, carriers, timesteps] } + cost_investment: { dims: [nodes, techs, costs], term: cost_investment_storage_cap } + +constraints: + flow_capacity_per_storage_capacity_min: + description: "`flow_capacity_per_storage_capacity_min` — flow capacity is at least its least share of storage capacity" + dims: [nodes, techs, carriers] + where: flow_cap AND storage_cap AND flow_cap_per_storage_cap_min + expression: flow_cap >= storage_cap * flow_cap_per_storage_cap_min + flow_capacity_per_storage_capacity_max: + description: "`flow_capacity_per_storage_capacity_max` — flow capacity is at most its most share of storage capacity" + dims: [nodes, techs, carriers] + where: flow_cap AND storage_cap AND flow_cap_per_storage_cap_max + expression: flow_cap <= storage_cap * flow_cap_per_storage_cap_max + storage_max: + description: "`storage_max` — a store holds at most its capacity" + dims: [nodes, techs, timesteps] + where: storage + expression: storage <= storage_cap + storage_discharge_depth_limit: + description: "`storage_discharge_depth_limit` — a store holds at least its depth of discharge" + dims: [nodes, techs, timesteps] + where: storage AND storage_discharge_depth + expression: storage - storage_discharge_depth * storage_cap >= 0 + balance_storage: + description: >- + `balance_storage` — what a store holds at the end of a time step is + what it carried in, less what it put out before losses, plus what it + took in after them + dims: [nodes, techs, timesteps] + where: (include_storage OR base_tech == 'storage') AND NOT (base_tech == 'supply' OR base_tech == 'demand') + expression: >- + storage == storage_previous_step + - sum(flow_out_inc_eff, over=carriers) + sum(flow_in_inc_eff, over=carriers) + set_storage_initial: + description: >- + `set_storage_initial` — a cyclic store with an initial fill holds it + at the end, after the last step's loss. Calliope builds one row per + store and reads the last step; this builds that row at the last step + dims: [nodes, techs, timesteps] + where: position(timesteps) == -1 AND storage AND storage_initial AND cyclic_storage + expression: storage * storage_retention ** timestep_resolution == storage_initial * storage_cap + +assumptions: + unbounded_storage_cap_cost: + description: Calliope's `unbounded_storage_cap_cost` — a negative storage capacity cost needs a finite maximum + holds: NOT cost_storage_cap < 0 OR storage_cap_max + storage_initial_max: + description: Calliope's `storage_initial_max` — the initial fill is a share + holds: storage_initial >= 0 AND storage_initial <= 1 + where: storage_initial + cyclic_storage_needs_inter_cluster: + description: >- + Calliope's `cyclic_storage_needs_inter_cluster` — a cyclic store under + clustering needs the inter-cluster patch + holds: NOT (cyclic_storage AND lookup_cluster_last_timestep) +``` + +#### Sets + +| Symbol | Meaning | +|---|---| +| $`\mathcal{N}`$ | index $`n`$ — `nodes` — Calliope's `nodes` — the places technologies stand at | +| $`\mathcal{I}`$ | index $`i`$ — `techs` — Calliope's `techs` — technologies | +| $`\mathcal{C}`$ | index $`c`$ — `carriers` — Calliope's `carriers` — energy and commodity carriers | +| $`\mathcal{T}`$ | index $`t`$ — `timesteps` with $`\mathrm{lookup\_cluster\_last\_timestep}: \mathcal{T} \to \mathcal{T}`$ — Calliope's `timesteps` — time steps, in order | +| $`\mathcal{K}`$ | index $`k`$ — `costs` — Calliope's `costs` — cost classes, such as monetary and CO2 | + +#### Parameters + +| Symbol | Meaning | +|---|---| +| $`\mathrm{storage}^{\mathrm{cap,min}}`$ | `storage_cap_min` over $`\mathcal{N} \times \mathcal{I}`$ — `storage_cap_min` — least storage capacity. Calliope's default is 0, and data prep fills it | +| $`\mathrm{storage}^{\mathrm{cap,max}}`$ | `storage_cap_max` over $`\mathcal{N} \times \mathcal{I}`$ — `storage_cap_max` — most storage capacity. Calliope's default is `.inf`, and data prep fills it | +| $`\mathrm{storage}^{\mathrm{discharge,depth}}`$ | `storage_discharge_depth` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{T}`$ — `storage_discharge_depth` — the least a store holds, as a share of its capacity | +| $`\mathrm{storage}^{\mathrm{initial}}`$ | `storage_initial` over $`\mathcal{N} \times \mathcal{I}`$ — `storage_initial` — what a store holds at the start, as a share of its capacity; given only where set | +| $`\mathrm{storage}^{\mathrm{retention}}`$ | `storage_retention` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{T}`$ — `1 - storage_loss` — the share of what a store holds that it keeps for an hour, data prep. mathspec refuses a sum as the base of `**`, over parameters too | +| $`\mathrm{cyclic\_storage}`$ | `cyclic_storage` over $`\mathcal{N} \times \mathcal{I}`$ — `cyclic_storage` — whether a store ends where it starts. Calliope's default is true, and data prep fills it | +| $`\mathrm{cluster\_first\_timestep}`$ | `cluster_first_timestep` over $`\mathcal{T}`$ — `cluster_first_timestep` — whether a time step is the first of its clustered day | +| $`\mathrm{flow\_cap\_per\_storage\_cap\_min}`$ | `flow_cap_per_storage_cap_min` over $`\mathcal{N} \times \mathcal{I}`$ — `flow_cap_per_storage_cap_min` — least flow capacity per unit of storage capacity; given only where set | +| $`\mathrm{flow\_cap\_per\_storage\_cap\_max}`$ | `flow_cap_per_storage_cap_max` over $`\mathcal{N} \times \mathcal{I}`$ — `flow_cap_per_storage_cap_max` — most flow capacity per unit of storage capacity; given only where set | +| $`\mathrm{cost\_storage\_cap}`$ | `cost_storage_cap` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K}`$ — `cost_storage_cap` — the cost of one unit of storage capacity | + +#### Variables + +| Symbol | Meaning | +|---|---| +| $`\mathit{storage}^{\mathrm{cap}}`$ | `storage_cap` over $`\mathcal{N} \times \mathcal{I}`$ — `storage_cap` — the most a technology can store | +| $`\mathit{storage}`$ | `storage` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{T}`$ — `storage` — what a technology holds at the end of a time step | + +#### Given + +| Symbol | Meaning | +|---|---| +| $`\mathrm{base\_tech}`$ | `base_tech` over $`\mathcal{I}`$, data another file declares | +| $`\mathrm{include\_storage}`$ | `include_storage` over $`\mathcal{N} \times \mathcal{I}`$, data another file declares | +| $`\mathrm{timestep\_resolution}`$ | `timestep_resolution` over $`\mathcal{T}`$, data another file declares | +| $`\mathit{flow\_cap}`$ | `flow_cap` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C}`$ | +| $`\mathit{flow\_out\_inc\_eff}`$ | `flow_out_inc_eff` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$, an expression another file defines | +| $`\mathit{flow\_in\_inc\_eff}`$ | `flow_in_inc_eff` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$, an expression another file defines | +| $`\mathit{cost\_investment}`$ | `cost_investment` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K}`$, an expression this file adds `cost_investment_storage_cap` to | + +#### Definitions + +| Symbol | Meaning | +|---|---| +| $`\mathit{storage}^{\mathrm{previous,step}}`$ | `storage_previous_step` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{T}`$ — `$storage_previous_step` — what a store carries into a time step: its initial fill at the first step of a store that is not cyclic, what is left of the last step of its clustered day at the first step of a cluster, and what is left of the step before everywhere else | +| $`\mathit{cost\_investment\_storage\_cap}`$ | `cost_investment_storage_cap` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K}`$ — `cost_investment_storage_cap` — the investment cost of storage capacity | + +Upright is what the data supplies — a parameter such as $`\mathrm{storage}^{\mathrm{cap,min}}`$, a coordinate map, a label — and italic is what the solver chooses, such as $`\mathit{storage}^{\mathrm{cap}}`$. 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. + +$`\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. + +$`\lvert \mathcal{T} \rvert`$ denotes the size of the set being counted along, and a position counted from the end prints against it — $`\lvert \mathcal{T} \rvert - 1`$ is the last position, one less than the size because the first is $`0`$. + +#### Subject to + +**`flow_capacity_per_storage_capacity_min`** + +```math +\mathit{flow\_cap}_{n,i,c} \ge \mathit{storage}^{\mathrm{cap}}_{n,i} \cdot \mathrm{flow\_cap\_per\_storage\_cap\_min}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C} \,:\, \mathit{flow\_cap}_{n,i,c} \text{ exists} \wedge \mathit{storage}^{\mathrm{cap}}_{n,i} \text{ exists} \wedge \mathrm{flow\_cap\_per\_storage\_cap\_min}_{n,i} \text{ is defined} +``` + +**`flow_capacity_per_storage_capacity_max`** + +```math +\mathit{flow\_cap}_{n,i,c} \le \mathit{storage}^{\mathrm{cap}}_{n,i} \cdot \mathrm{flow\_cap\_per\_storage\_cap\_max}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C} \,:\, \mathit{flow\_cap}_{n,i,c} \text{ exists} \wedge \mathit{storage}^{\mathrm{cap}}_{n,i} \text{ exists} \wedge \mathrm{flow\_cap\_per\_storage\_cap\_max}_{n,i} \text{ is defined} +``` + +**`storage_max`** + +```math +\mathit{storage}_{n,i,t} \le \mathit{storage}^{\mathrm{cap}}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T} \,:\, \mathit{storage}_{n,i,t} \text{ exists} +``` + +**`storage_discharge_depth_limit`** + +```math +\mathit{storage}_{n,i,t} - \mathrm{storage}^{\mathrm{discharge,depth}}_{n,i,t} \cdot \mathit{storage}^{\mathrm{cap}}_{n,i} \ge 0 \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T} \,:\, \mathit{storage}_{n,i,t} \text{ exists} \wedge \mathrm{storage}^{\mathrm{discharge,depth}}_{n,i,t} \text{ is defined} +``` + +**`balance_storage`** + +```math +\mathit{storage}_{n,i,t} = \mathit{storage}^{\mathrm{previous,step}}_{n,i,t} - \left( \sum_{c \in \mathcal{C}} \mathit{flow\_out\_inc\_eff}_{n,i,c,t} \right) + \sum_{c \in \mathcal{C}} \mathit{flow\_in\_inc\_eff}_{n,i,c,t} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T} \,:\, \left( \mathrm{include\_storage}_{n,i} \vee \mathrm{base\_tech}_{i} = \text{'}\mathrm{storage}\text{'} \right) \wedge \neg \left( \mathrm{base\_tech}_{i} = \text{'}\mathrm{supply}\text{'} \vee \mathrm{base\_tech}_{i} = \text{'}\mathrm{demand}\text{'} \right) +``` + +**`set_storage_initial`** + +```math +\mathit{storage}_{n,i,t} \cdot \mathrm{storage}^{\mathrm{retention}}_{n,i,t}^{\mathrm{timestep\_resolution}_{t}} = \mathrm{storage}^{\mathrm{initial}}_{n,i} \cdot \mathit{storage}^{\mathrm{cap}}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T} \,:\, \mathrm{pos}(t) = \lvert \mathcal{T} \rvert - 1 \wedge \mathit{storage}_{n,i,t} \text{ exists} \wedge \mathrm{storage}^{\mathrm{initial}}_{n,i} \text{ is defined} \wedge \mathrm{cyclic\_storage}_{n,i} +``` + +#### Definitions + +**`storage_previous_step`** + +```math +\mathit{storage}^{\mathrm{previous,step}}_{n,i,t} = \begin{cases} \mathrm{storage}^{\mathrm{initial}}_{n,i} \cdot \mathit{storage}^{\mathrm{cap}}_{n,i} & \text{if } \mathrm{pos}(t) = 0 \wedge \neg \mathrm{cyclic\_storage}_{n,i} \\ \mathrm{storage}^{\mathrm{retention}}_{n,i,t}^{\mathrm{timestep\_resolution}_{\mathrm{lookup\_cluster\_last\_timestep}(t)}} \cdot \mathit{storage}_{n,i,\mathrm{lookup\_cluster\_last\_timestep}(t)} & \text{if } \mathrm{cluster\_first\_timestep}_{t} \wedge \neg \left( \mathrm{pos}(t) = 0 \wedge \neg \mathrm{cyclic\_storage}_{n,i} \right) \\ \mathrm{storage}^{\mathrm{retention}}_{n,i,t}^{\mathrm{timestep\_resolution}_{t \ominus 1}} \cdot \mathit{storage}_{n,i,t \ominus 1} & \text{otherwise} \end{cases} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T} +``` + +**`cost_investment_storage_cap`** + +```math +\mathit{cost\_investment\_storage\_cap}_{n,i,k} = \mathrm{cost\_storage\_cap}_{n,i,k} \cdot \mathit{storage}^{\mathrm{cap}}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ k \in \mathcal{K} +``` + +#### Variable domains + +**`storage_cap`** + +```math +\mathrm{storage}^{\mathrm{cap,min}}_{n,i} \le \mathit{storage}^{\mathrm{cap}}_{n,i} \le \mathrm{storage}^{\mathrm{cap,max}}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I} \,:\, \mathrm{include\_storage}_{n,i} \vee \mathrm{base\_tech}_{i} = \text{'}\mathrm{storage}\text{'} +``` + +**`storage`** + +```math +\mathit{storage}_{n,i,t} \ge 0 \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T} \,:\, \mathrm{include\_storage}_{n,i} \vee \mathrm{base\_tech}_{i} = \text{'}\mathrm{storage}\text{'} +``` + +#### Assumptions + +**`unbounded_storage_cap_cost`** + +```math +\neg \left( \mathrm{cost\_storage\_cap}_{n,i,k} < 0 \right) \vee \mathrm{storage}^{\mathrm{cap,max}}_{n,i} \text{ is defined} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ k \in \mathcal{K} +``` + +**`storage_initial_max`** + +```math +\mathrm{storage}^{\mathrm{initial}}_{n,i} \ge 0 \wedge \mathrm{storage}^{\mathrm{initial}}_{n,i} \le 1 \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I} \,:\, \mathrm{storage}^{\mathrm{initial}}_{n,i} \text{ is defined} +``` + +**`cyclic_storage_needs_inter_cluster`** + +```math +\neg \left( \mathrm{cyclic\_storage}_{n,i} \wedge \mathrm{lookup\_cluster\_last\_timestep}(t) \text{ is defined} \right) \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T} +``` + diff --git a/docs/examples/calliope/supply.md b/docs/examples/calliope/supply.md new file mode 100644 index 00000000..044dd559 --- /dev/null +++ b/docs/examples/calliope/supply.md @@ -0,0 +1,338 @@ + + +# Supply + +One of the base fragments of [Calliope in fragments](index.md). Supply technologies: the source a technology takes from outside the system, its capacity and its availability. The source scaler reads `area_use`, so a model with a per-area source composes this file with the area file. + + +```yaml +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + costs: + description: Calliope's `costs` — cost classes, such as monetary and CO2 + +parameters: + source_eff: + description: "`source_eff` — the share of the source a supply technology takes in. Calliope's default is 1, and data prep fills it" + dims: [nodes, techs, timesteps] + source_use_min: + description: "`source_use_min` — least source use in a time step, per unit of `source_unit`" + dims: [nodes, techs, timesteps] + source_use_max: + description: "`source_use_max` — most source use in a time step, per unit of `source_unit`; given only where set" + dims: [nodes, techs, timesteps] + source_use_equals: + description: "`source_use_equals` — the source use required in a time step, per unit of `source_unit`; given only where set" + dims: [nodes, techs, timesteps] + source_unit: + description: >- + `source_unit` — what the source is per: `absolute`, `per_area` of + area use, or `per_cap` of flow capacity. Calliope's default is + `absolute`, which is what a technology with no row reads as + dims: [nodes, techs] + dtype: str + source_cap_min: + description: "`source_cap_min` — least source capacity. Calliope's default is 0, and data prep fills it" + dims: [nodes, techs] + source_cap_max: + description: "`source_cap_max` — most source capacity. Calliope's default is `.inf`, and data prep fills it" + dims: [nodes, techs] + source_cap_equals_flow_cap: + description: "`source_cap_equals_flow_cap` — whether the source capacity equals the flow capacity" + dims: [nodes, techs] + dtype: bool + cost_source_use: + description: "`cost_source_use` — the cost of one unit of source use" + dims: [nodes, techs, costs, timesteps] + cost_source_cap: + description: "`cost_source_cap` — the cost of one unit of source capacity" + dims: [nodes, techs, costs] + +variables: + source_use: + description: "`source_use` — what a supply technology takes in from outside the system in a time step" + dims: [nodes, techs, timesteps] + where: base_tech == 'supply' + bounds: { lower: 0 } + absence: zero + source_cap: + description: "`source_cap` — the most a supply technology can take in from outside the system" + dims: [nodes, techs] + where: base_tech == 'supply' + bounds: { lower: source_cap_min, upper: source_cap_max } + absence: zero + +expressions: + flow_cap_out: + description: "`where(flow_cap, carrier_out)` — the flow capacity of the carriers a technology produces" + dims: [nodes, techs, carriers] + cases: + produced: + when: carrier_out + expression: flow_cap + otherwise: 0 + source_scaler: + description: "`$source_scaler` — what the source parameters are per: area use, flow capacity, or one" + dims: [nodes, techs] + cases: + per_area: + when: source_unit == per_area + expression: area_use + per_cap: + when: source_unit == per_cap + expression: sum(flow_cap_out, over=carriers) + otherwise: 1 + cost_investment_source_cap: + description: "`cost_investment_source_cap` — the investment cost of source capacity" + expression: cost_source_cap * source_cap + supply_cost_operation_variable: timestep_weights * cost_source_use * source_use + curtailment: + description: >- + `curtailment` — the share of the available source a supply technology + leaves unused in a time step; reported + expression: 1 - source_use / (source_use_max * source_scaler) + total_curtailment: + description: "`total_curtailment` — the share of the available source left unused over the whole time; reported" + expression: 1 - sum(source_use, over=timesteps) / sum(source_use_max * source_scaler, over=timesteps) + +given: + parameters: + base_tech: { dims: [techs], dtype: str } + carrier_out: { dims: [nodes, techs, carriers], dtype: bool } + include_storage: { dims: [nodes, techs], dtype: bool } + timestep_resolution: { dims: [timesteps] } + timestep_weights: { dims: [timesteps] } + variables: + flow_cap: { dims: [nodes, techs, carriers] } + area_use: { dims: [nodes, techs] } + expressions: + flow_out_inc_eff: { dims: [nodes, techs, carriers, timesteps] } + cost_investment: { dims: [nodes, techs, costs], term: cost_investment_source_cap } + cost_operation_variable: { dims: [nodes, techs, costs, timesteps], term: supply_cost_operation_variable } + +constraints: + source_max: + description: "`source_max` — source use is at most the source capacity over the time step" + dims: [nodes, techs, timesteps] + where: source_cap + expression: source_use <= timestep_resolution * source_cap + source_capacity_equals_flow_capacity: + description: "`source_capacity_equals_flow_capacity` — a supply technology's source capacity equals its flow capacity, where set" + dims: [nodes, techs, carriers] + where: flow_cap AND source_cap AND source_cap_equals_flow_cap + expression: source_cap == flow_cap + balance_supply_no_storage: + description: "`balance_supply_no_storage` — a supply technology with no store puts out what it takes from its source" + dims: [nodes, techs, carriers, timesteps] + where: carrier_out AND base_tech == 'supply' AND NOT include_storage + expression: flow_out_inc_eff == source_use * source_eff + source_availability_supply_equals: + description: "`source_availability_supply` where `source_use_equals` is set — source use is what is available" + dims: [nodes, techs, timesteps] + where: source_use AND source_use_equals + expression: source_use == source_use_equals * source_scaler + source_availability_supply_max: + description: "`source_availability_supply` where only `source_use_max` is set — source use is at most what is available" + dims: [nodes, techs, timesteps] + where: source_use AND NOT source_use_equals AND source_use_max + expression: source_use <= source_use_max * source_scaler + balance_supply_min_use: + description: "`balance_supply_min_use` — source use is at least its least use" + dims: [nodes, techs, timesteps] + where: source_use_min AND NOT source_use_equals AND base_tech == 'supply' + expression: source_use >= source_use_min * source_scaler + +assumptions: + unbounded_source_use_cost: + description: Calliope's `unbounded_source_use_cost` — a negative source capacity cost needs a finite maximum + holds: NOT cost_source_cap < 0 OR source_cap_max + finite_source_use: + description: Calliope's `finite_source_use`, for the source — a required use is finite + holds: NOT source_use_equals == inf + source_unit_one_of: + description: Calliope's `one_of` on `source_unit` + holds: source_unit == absolute OR source_unit == per_area OR source_unit == per_cap + where: source_unit +``` + +#### Sets + +| Symbol | Meaning | +|---|---| +| $`\mathcal{N}`$ | index $`n`$ — `nodes` — Calliope's `nodes` — the places technologies stand at | +| $`\mathcal{I}`$ | index $`i`$ — `techs` — Calliope's `techs` — technologies | +| $`\mathcal{C}`$ | index $`c`$ — `carriers` — Calliope's `carriers` — energy and commodity carriers | +| $`\mathcal{T}`$ | index $`t`$ — `timesteps` — Calliope's `timesteps` — time steps, in order | +| $`\mathcal{K}`$ | index $`k`$ — `costs` — Calliope's `costs` — cost classes, such as monetary and CO2 | + +#### Parameters + +| Symbol | Meaning | +|---|---| +| $`\mathrm{source\_eff}`$ | `source_eff` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{T}`$ — `source_eff` — the share of the source a supply technology takes in. Calliope's default is 1, and data prep fills it | +| $`\mathrm{source\_use\_min}`$ | `source_use_min` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{T}`$ — `source_use_min` — least source use in a time step, per unit of `source_unit` | +| $`\mathrm{source\_use\_max}`$ | `source_use_max` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{T}`$ — `source_use_max` — most source use in a time step, per unit of `source_unit`; given only where set | +| $`\mathrm{source\_use\_equals}`$ | `source_use_equals` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{T}`$ — `source_use_equals` — the source use required in a time step, per unit of `source_unit`; given only where set | +| $`\mathrm{source\_unit}`$ | `source_unit` over $`\mathcal{N} \times \mathcal{I}`$ — `source_unit` — what the source is per: `absolute`, `per_area` of area use, or `per_cap` of flow capacity. Calliope's default is `absolute`, which is what a technology with no row reads as | +| $`\mathrm{source\_cap\_min}`$ | `source_cap_min` over $`\mathcal{N} \times \mathcal{I}`$ — `source_cap_min` — least source capacity. Calliope's default is 0, and data prep fills it | +| $`\mathrm{source\_cap\_max}`$ | `source_cap_max` over $`\mathcal{N} \times \mathcal{I}`$ — `source_cap_max` — most source capacity. Calliope's default is `.inf`, and data prep fills it | +| $`\mathrm{source\_cap\_equals\_flow\_cap}`$ | `source_cap_equals_flow_cap` over $`\mathcal{N} \times \mathcal{I}`$ — `source_cap_equals_flow_cap` — whether the source capacity equals the flow capacity | +| $`\mathrm{cost\_source\_use}`$ | `cost_source_use` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K} \times \mathcal{T}`$ — `cost_source_use` — the cost of one unit of source use | +| $`\mathrm{cost\_source\_cap}`$ | `cost_source_cap` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K}`$ — `cost_source_cap` — the cost of one unit of source capacity | + +#### Variables + +| Symbol | Meaning | +|---|---| +| $`\mathit{source\_use}`$ | `source_use` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{T}`$ — `source_use` — what a supply technology takes in from outside the system in a time step | +| $`\mathit{source\_cap}`$ | `source_cap` over $`\mathcal{N} \times \mathcal{I}`$ — `source_cap` — the most a supply technology can take in from outside the system | + +#### Given + +| Symbol | Meaning | +|---|---| +| $`\mathrm{base\_tech}`$ | `base_tech` over $`\mathcal{I}`$, data another file declares | +| $`\mathrm{carrier\_out}`$ | `carrier_out` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C}`$, data another file declares | +| $`\mathrm{include\_storage}`$ | `include_storage` over $`\mathcal{N} \times \mathcal{I}`$, data another file declares | +| $`\mathrm{timestep\_resolution}`$ | `timestep_resolution` over $`\mathcal{T}`$, data another file declares | +| $`\mathrm{timestep\_weights}`$ | `timestep_weights` over $`\mathcal{T}`$, data another file declares | +| $`\mathit{flow\_cap}`$ | `flow_cap` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C}`$ | +| $`\mathit{area\_use}`$ | `area_use` over $`\mathcal{N} \times \mathcal{I}`$ | +| $`\mathit{flow\_out\_inc\_eff}`$ | `flow_out_inc_eff` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$, an expression another file defines | +| $`\mathit{cost\_investment}`$ | `cost_investment` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K}`$, an expression this file adds `cost_investment_source_cap` to | +| $`\mathit{cost\_operation\_variable}`$ | `cost_operation_variable` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K} \times \mathcal{T}`$, an expression this file adds `supply_cost_operation_variable` to | + +#### Definitions + +| Symbol | Meaning | +|---|---| +| $`\mathit{flow\_cap\_out}`$ | `flow_cap_out` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C}`$ — `where(flow_cap, carrier_out)` — the flow capacity of the carriers a technology produces | +| $`\mathit{source\_scaler}`$ | `source_scaler` over $`\mathcal{N} \times \mathcal{I}`$ — `$source_scaler` — what the source parameters are per: area use, flow capacity, or one | +| $`\mathit{cost\_investment\_source\_cap}`$ | `cost_investment_source_cap` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K}`$ — `cost_investment_source_cap` — the investment cost of source capacity | +| $`\mathit{supply\_cost\_operation\_variable}`$ | `supply_cost_operation_variable` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{T} \times \mathcal{K}`$ | +| $`\mathit{curtailment}`$ | `curtailment` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{T}`$ — `curtailment` — the share of the available source a supply technology leaves unused in a time step; reported | +| $`\mathit{total\_curtailment}`$ | `total_curtailment` over $`\mathcal{N} \times \mathcal{I}`$ — `total_curtailment` — the share of the available source left unused over the whole time; reported | + +Upright is what the data supplies — a parameter such as $`\mathrm{source\_eff}`$, a coordinate map, a label — and italic is what the solver chooses, such as $`\mathit{source\_use}`$. An index is italic too, being what a quantifier chooses, and a set is script. + +#### Subject to + +**`source_max`** + +```math +\mathit{source\_use}_{n,i,t} \le \mathrm{timestep\_resolution}_{t} \cdot \mathit{source\_cap}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T} \,:\, \mathit{source\_cap}_{n,i} \text{ exists} +``` + +**`source_capacity_equals_flow_capacity`** + +```math +\mathit{source\_cap}_{n,i} = \mathit{flow\_cap}_{n,i,c} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C} \,:\, \mathit{flow\_cap}_{n,i,c} \text{ exists} \wedge \mathit{source\_cap}_{n,i} \text{ exists} \wedge \mathrm{source\_cap\_equals\_flow\_cap}_{n,i} +``` + +**`balance_supply_no_storage`** + +```math +\mathit{flow\_out\_inc\_eff}_{n,i,c,t} = \mathit{source\_use}_{n,i,t} \cdot \mathrm{source\_eff}_{n,i,t} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} \,:\, \mathrm{carrier\_out}_{n,i,c} \wedge \mathrm{base\_tech}_{i} = \text{'}\mathrm{supply}\text{'} \wedge \neg \mathrm{include\_storage}_{n,i} +``` + +**`source_availability_supply_equals`** + +```math +\mathit{source\_use}_{n,i,t} = \mathrm{source\_use\_equals}_{n,i,t} \cdot \mathit{source\_scaler}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T} \,:\, \mathit{source\_use}_{n,i,t} \text{ exists} \wedge \mathrm{source\_use\_equals}_{n,i,t} \text{ is defined} +``` + +**`source_availability_supply_max`** + +```math +\mathit{source\_use}_{n,i,t} \le \mathrm{source\_use\_max}_{n,i,t} \cdot \mathit{source\_scaler}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T} \,:\, \mathit{source\_use}_{n,i,t} \text{ exists} \wedge \neg \left( \mathrm{source\_use\_equals}_{n,i,t} \text{ is defined} \right) \wedge \mathrm{source\_use\_max}_{n,i,t} \text{ is defined} +``` + +**`balance_supply_min_use`** + +```math +\mathit{source\_use}_{n,i,t} \ge \mathrm{source\_use\_min}_{n,i,t} \cdot \mathit{source\_scaler}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T} \,:\, \mathrm{source\_use\_min}_{n,i,t} \text{ is defined} \wedge \neg \left( \mathrm{source\_use\_equals}_{n,i,t} \text{ is defined} \right) \wedge \mathrm{base\_tech}_{i} = \text{'}\mathrm{supply}\text{'} +``` + +#### Definitions + +**`flow_cap_out`** + +```math +\mathit{flow\_cap\_out}_{n,i,c} = \begin{cases} \mathit{flow\_cap}_{n,i,c} & \text{if } \mathrm{carrier\_out}_{n,i,c} \\ 0 & \text{otherwise} \end{cases} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C} +``` + +**`source_scaler`** + +```math +\mathit{source\_scaler}_{n,i} = \begin{cases} \mathit{area\_use}_{n,i} & \text{if } \mathrm{source\_unit}_{n,i} = \text{'}\mathrm{per\_area}\text{'} \\ \sum_{c \in \mathcal{C}} \mathit{flow\_cap\_out}_{n,i,c} & \text{if } \mathrm{source\_unit}_{n,i} = \text{'}\mathrm{per\_cap}\text{'} \\ 1 & \text{otherwise} \end{cases} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I} +``` + +**`cost_investment_source_cap`** + +```math +\mathit{cost\_investment\_source\_cap}_{n,i,k} = \mathrm{cost\_source\_cap}_{n,i,k} \cdot \mathit{source\_cap}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ k \in \mathcal{K} +``` + +**`supply_cost_operation_variable`** + +```math +\mathit{supply\_cost\_operation\_variable}_{n,i,t,k} = \mathrm{timestep\_weights}_{t} \cdot \mathrm{cost\_source\_use}_{n,i,k,t} \cdot \mathit{source\_use}_{n,i,t} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T},\ k \in \mathcal{K} +``` + +**`curtailment`** + +```math +\mathit{curtailment}_{n,i,t} = 1 - \frac{\mathit{source\_use}_{n,i,t}}{\mathrm{source\_use\_max}_{n,i,t} \cdot \mathit{source\_scaler}_{n,i}} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T} +``` + +**`total_curtailment`** + +```math +\mathit{total\_curtailment}_{n,i} = 1 - \frac{\sum_{t \in \mathcal{T}} \mathit{source\_use}_{n,i,t}}{\sum_{t \in \mathcal{T}} \mathrm{source\_use\_max}_{n,i,t} \cdot \mathit{source\_scaler}_{n,i}} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I} +``` + +#### Variable domains + +**`source_use`** + +```math +\mathit{source\_use}_{n,i,t} \ge 0 \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T} \,:\, \mathrm{base\_tech}_{i} = \text{'}\mathrm{supply}\text{'} +``` + +**`source_cap`** + +```math +\mathrm{source\_cap\_min}_{n,i} \le \mathit{source\_cap}_{n,i} \le \mathrm{source\_cap\_max}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I} \,:\, \mathrm{base\_tech}_{i} = \text{'}\mathrm{supply}\text{'} +``` + +#### Assumptions + +**`unbounded_source_use_cost`** + +```math +\neg \left( \mathrm{cost\_source\_cap}_{n,i,k} < 0 \right) \vee \mathrm{source\_cap\_max}_{n,i} \text{ is defined} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ k \in \mathcal{K} +``` + +**`finite_source_use`** + +```math +\neg \left( \mathrm{source\_use\_equals}_{n,i,t} = \infty \right) \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T} +``` + +**`source_unit_one_of`** + +```math +\mathrm{source\_unit}_{n,i} = \text{'}\mathrm{absolute}\text{'} \vee \mathrm{source\_unit}_{n,i} = \text{'}\mathrm{per\_area}\text{'} \vee \mathrm{source\_unit}_{n,i} = \text{'}\mathrm{per\_cap}\text{'} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I} \,:\, \mathrm{source\_unit}_{n,i} \text{ is defined} +``` + diff --git a/docs/examples/calliope/supply_storage.md b/docs/examples/calliope/supply_storage.md new file mode 100644 index 00000000..89ec8c69 --- /dev/null +++ b/docs/examples/calliope/supply_storage.md @@ -0,0 +1,73 @@ + + +# Supply with storage + +One of the base fragments of [Calliope in fragments](index.md). Calliope's `balance_supply_with_storage`, the one row that couples a supply technology to a store. It is a file of its own so that supply and storage each compose without the other. + + +```yaml +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + +given: + parameters: + base_tech: { dims: [techs], dtype: str } + carrier_out: { dims: [nodes, techs, carriers], dtype: bool } + source_eff: { dims: [nodes, techs, timesteps] } + variables: + storage: { dims: [nodes, techs, timesteps] } + source_use: { dims: [nodes, techs, timesteps] } + expressions: + storage_previous_step: { dims: [nodes, techs, timesteps] } + flow_out_inc_eff: { dims: [nodes, techs, carriers, timesteps] } + +constraints: + balance_supply_with_storage: + description: >- + `balance_supply_with_storage` — a supply technology with a store puts + in what it takes from its source and draws out what it puts out + dims: [nodes, techs, carriers, timesteps] + where: carrier_out AND storage AND base_tech == 'supply' + expression: storage == storage_previous_step + source_use * source_eff - flow_out_inc_eff +``` + +#### Sets + +| Symbol | Meaning | +|---|---| +| $`\mathcal{N}`$ | index $`n`$ — `nodes` — Calliope's `nodes` — the places technologies stand at | +| $`\mathcal{I}`$ | index $`i`$ — `techs` — Calliope's `techs` — technologies | +| $`\mathcal{C}`$ | index $`c`$ — `carriers` — Calliope's `carriers` — energy and commodity carriers | +| $`\mathcal{T}`$ | index $`t`$ — `timesteps` — Calliope's `timesteps` — time steps, in order | + +#### Given + +| Symbol | Meaning | +|---|---| +| $`\mathrm{base\_tech}`$ | `base_tech` over $`\mathcal{I}`$, data another file declares | +| $`\mathrm{carrier\_out}`$ | `carrier_out` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C}`$, data another file declares | +| $`\mathrm{source\_eff}`$ | `source_eff` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{T}`$, data another file declares | +| $`\mathit{storage}`$ | `storage` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{T}`$ | +| $`\mathit{source\_use}`$ | `source_use` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{T}`$ | +| $`\mathit{storage}^{\mathrm{previous,step}}`$ | `storage_previous_step` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{T}`$, an expression another file defines | +| $`\mathit{flow\_out\_inc\_eff}`$ | `flow_out_inc_eff` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$, an expression another file defines | + +#### Subject to + +**`balance_supply_with_storage`** + +```math +\mathit{storage}_{n,i,t} = \mathit{storage}^{\mathrm{previous,step}}_{n,i,t} + \mathit{source\_use}_{n,i,t} \cdot \mathrm{source\_eff}_{n,i,t} - \mathit{flow\_out\_inc\_eff}_{n,i,c,t} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} \,:\, \mathrm{carrier\_out}_{n,i,c} \wedge \mathit{storage}_{n,i,t} \text{ exists} \wedge \mathrm{base\_tech}_{i} = \text{'}\mathrm{supply}\text{'} +``` + diff --git a/docs/examples/calliope/transmission.md b/docs/examples/calliope/transmission.md new file mode 100644 index 00000000..13764bc4 --- /dev/null +++ b/docs/examples/calliope/transmission.md @@ -0,0 +1,132 @@ + + +# Transmission + +One of the base fragments of [Calliope in fragments](index.md). Links between nodes: a link carries what it takes in at one end to the other, and has one capacity at both ends. + + +```yaml +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + +relations: + link_from: + description: >- + `link_from` — the node a transmission technology links from. Calliope + reads it as `map_dim(nodes, link_from)`, a mask over technology and + node, which is the relation's own row test + key: [techs, nodes] + link_to: + description: >- + `link_to` — the node a transmission technology links to, read as + `link_from` is + key: [techs, nodes] + +expressions: + flow_cap_from: + description: "`where(flow_cap, map_dim(nodes, link_from))` — a link's flow capacity at the node it links from" + dims: [nodes, techs, carriers] + cases: + from: + when: link_from + expression: flow_cap + otherwise: 0 + flow_cap_to: + description: "`where(flow_cap, map_dim(nodes, link_to))` — a link's flow capacity at the node it links to" + dims: [nodes, techs, carriers] + cases: + to: + when: link_to + expression: flow_cap + otherwise: 0 + +given: + parameters: + base_tech: { dims: [techs], dtype: str } + carrier_out: { dims: [nodes, techs, carriers], dtype: bool } + variables: + flow_cap: { dims: [nodes, techs, carriers] } + expressions: + flow_out_inc_eff: { dims: [nodes, techs, carriers, timesteps] } + flow_in_inc_eff: { dims: [nodes, techs, carriers, timesteps] } + +constraints: + balance_transmission: + description: "`balance_transmission` — a link puts out at one end, before losses, what it takes in at the other after them" + dims: [techs, timesteps] + where: base_tech == 'transmission' + expression: >- + sum(sum(flow_out_inc_eff, over=nodes), over=carriers) + == sum(sum(flow_in_inc_eff, over=nodes), over=carriers) + symmetric_transmission: + description: "`symmetric_transmission` — a link has the same flow capacity at both ends" + dims: [techs, carriers] + where: count(carrier_out, over=nodes) >= 1 AND base_tech == 'transmission' + expression: sum(flow_cap_from, over=nodes) == sum(flow_cap_to, over=nodes) +``` + +#### Sets + +| Symbol | Meaning | +|---|---| +| $`\mathcal{N}`$ | index $`n`$ — `nodes` with $`\mathrm{link\_from} \subseteq \mathcal{I} \times \mathcal{N},\ \mathrm{link\_to} \subseteq \mathcal{I} \times \mathcal{N}`$ — Calliope's `nodes` — the places technologies stand at | +| $`\mathcal{I}`$ | index $`i`$ — `techs` with $`\mathrm{link\_from} \subseteq \mathcal{I} \times \mathcal{N},\ \mathrm{link\_to} \subseteq \mathcal{I} \times \mathcal{N}`$ — Calliope's `techs` — technologies | +| $`\mathcal{C}`$ | index $`c`$ — `carriers` — Calliope's `carriers` — energy and commodity carriers | +| $`\mathcal{T}`$ | index $`t`$ — `timesteps` — Calliope's `timesteps` — time steps, in order | + +#### Given + +| Symbol | Meaning | +|---|---| +| $`\mathrm{base\_tech}`$ | `base_tech` over $`\mathcal{I}`$, data another file declares | +| $`\mathrm{carrier\_out}`$ | `carrier_out` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C}`$, data another file declares | +| $`\mathit{flow\_cap}`$ | `flow_cap` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C}`$ | +| $`\mathit{flow\_out\_inc\_eff}`$ | `flow_out_inc_eff` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$, an expression another file defines | +| $`\mathit{flow\_in\_inc\_eff}`$ | `flow_in_inc_eff` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C} \times \mathcal{T}`$, an expression another file defines | + +#### Definitions + +| Symbol | Meaning | +|---|---| +| $`\mathit{flow\_cap\_from}`$ | `flow_cap_from` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C}`$ — `where(flow_cap, map_dim(nodes, link_from))` — a link's flow capacity at the node it links from | +| $`\mathit{flow\_cap\_to}`$ | `flow_cap_to` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{C}`$ — `where(flow_cap, map_dim(nodes, link_to))` — a link's flow capacity at the node it links to | + +#### Subject to + +**`balance_transmission`** + +```math +\sum_{c \in \mathcal{C}} \sum_{n \in \mathcal{N}} \mathit{flow\_out\_inc\_eff}_{n,i,c,t} = \sum_{c \in \mathcal{C}} \sum_{n \in \mathcal{N}} \mathit{flow\_in\_inc\_eff}_{n,i,c,t} \qquad \forall\, i \in \mathcal{I},\ t \in \mathcal{T} \,:\, \mathrm{base\_tech}_{i} = \text{'}\mathrm{transmission}\text{'} +``` + +**`symmetric_transmission`** + +```math +\sum_{n \in \mathcal{N}} \mathit{flow\_cap\_from}_{n,i,c} = \sum_{n \in \mathcal{N}} \mathit{flow\_cap\_to}_{n,i,c} \qquad \forall\, i \in \mathcal{I},\ c \in \mathcal{C} \,:\, \lvert \{ n \in \mathcal{N} \,:\, \mathrm{carrier\_out}_{n,i,c} \} \rvert \ge 1 \wedge \mathrm{base\_tech}_{i} = \text{'}\mathrm{transmission}\text{'} +``` + +#### Definitions + +**`flow_cap_from`** + +```math +\mathit{flow\_cap\_from}_{n,i,c} = \begin{cases} \mathit{flow\_cap}_{n,i,c} & \text{if } \left( i,\ n \right) \in \mathrm{link\_from} \\ 0 & \text{otherwise} \end{cases} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C} +``` + +**`flow_cap_to`** + +```math +\mathit{flow\_cap\_to}_{n,i,c} = \begin{cases} \mathit{flow\_cap}_{n,i,c} & \text{if } \left( i,\ n \right) \in \mathrm{link\_to} \\ 0 & \text{otherwise} \end{cases} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C} +``` + diff --git a/docs/examples/calliope/variants/chp_htp.md b/docs/examples/calliope/variants/chp_htp.md new file mode 100644 index 00000000..42a7b3f9 --- /dev/null +++ b/docs/examples/calliope/variants/chp_htp.md @@ -0,0 +1,33 @@ + + +# The CHP patch + +A patch of [Calliope in fragments](../index.md). What Calliope's example `chp_htp.yaml` changes in the base: `balance_conversion` holds only for a plant with no turbine type. Its rows are [the CHP fragment](../extensions/chp_htp.md). A patch is not a spec, so it prints as the declarations it writes, in the spec it lands on. + + +```python +ms.override( + ms.merge(base + ['extensions/chp_htp.yaml']), + ['variants/chp_htp.yaml'], +) +``` + +```yaml title="variants/chp_htp.yaml" +constraints: + balance_conversion: + description: >- + `balance_conversion` for a plant with no turbine type — a conversion + technology puts out, before its losses, what it takes in after them. + Extraction and backpressure plants have rows of their own + where: base_tech == 'conversion' AND NOT include_storage AND NOT turbine_type +``` + +**`balance_conversion`** + +```math +\sum_{c \in \mathcal{C}} \mathit{flow\_out\_inc\_eff}_{n,i,c,t} = \sum_{c \in \mathcal{C}} \mathit{flow\_in\_inc\_eff}_{n,i,c,t} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T} \,:\, \mathrm{base\_tech}_{i} = \text{'}\mathrm{conversion}\text{'} \wedge \neg \mathrm{include\_storage}_{n,i} \wedge \neg \left( \mathrm{turbine\_type}_{n,i} \text{ is defined} \right) +``` + diff --git a/docs/examples/calliope/variants/milp.md b/docs/examples/calliope/variants/milp.md new file mode 100644 index 00000000..0ddddeb8 --- /dev/null +++ b/docs/examples/calliope/variants/milp.md @@ -0,0 +1,111 @@ + + +# The MILP patch + +A patch of [Calliope in fragments](../index.md). What Calliope's `milp.yaml` changes in the base: the capacity bounds open to zero, since the minimums become rows the units scale, and the continuous flow limits hold only where no unit runs. What it adds is [the MILP fragment](../extensions/milp.md). A patch is not a spec, so it prints as the declarations it writes, in the spec it lands on. + + +```python +ms.override( + ms.merge(base + ['extensions/milp.yaml']), + ['variants/milp.yaml'], +) +``` + +```yaml title="variants/milp.yaml" +parameters: + flow_cap_min: + description: >- + `flow_cap_min` — least flow capacity, scaled by the units bought where + a technology buys units; given only where set, as no bound reads it + flow_cap_min_systemwide: + description: >- + `flow_cap_min_systemwide` — least flow capacity of a technology over + every node, scaled by the units bought where it buys units; given only + where set + flow_out_min_relative: + description: >- + `flow_out_min_relative` — least outflow, per unit of flow capacity. For + a continuous technology it holds in every time step; given only where + set + storage_cap_min: + description: "`storage_cap_min` — least storage capacity; given only where set, as no bound reads it" + area_use_min: + description: "`area_use_min` — least area use; given only where set, as no bound reads it" + source_cap_min: + description: "`source_cap_min` — least source capacity; given only where set, as no bound reads it" + +variables: + flow_cap: { bounds: { lower: 0 } } + area_use: { bounds: { lower: 0 } } + source_cap: { bounds: { lower: 0 } } + storage_cap: { bounds: { lower: 0 } } + +constraints: + flow_out_max: + description: "`flow_out_max` — a continuous technology's outflow is at most its flow capacity over the time step" + where: carrier_out AND NOT operating_units + flow_out_min: + description: "`flow_out_min` — a continuous technology's outflow is at least its least share of the flow capacity" + where: flow_cap AND flow_out_min_relative AND NOT operating_units + flow_in_max: + description: "`flow_in_max` — a continuous technology's inflow is at most its flow capacity over the time step" + where: carrier_in AND NOT operating_units + flow_capacity_systemwide_min: + description: >- + `flow_capacity_systemwide_min` where no unit is bought — the flow + capacity over every node is at least the system-wide minimum + where: count(flow_cap, over=nodes) >= 1 AND flow_cap_min_systemwide AND NOT count(purchased_units, over=nodes) >= 1 +``` + +**`flow_cap`** + +```math +0 \le \mathit{flow\_cap}_{n,i,c} \le \mathrm{flow\_cap\_max}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C} \,:\, \mathrm{carrier\_in}_{n,i,c} \vee \mathrm{carrier\_out}_{n,i,c} +``` + +**`area_use`** + +```math +0 \le \mathit{area\_use}_{n,i} \le \mathrm{area\_use\_max}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I} \,:\, \mathrm{area\_use\_min}_{n,i} > 0 \vee \mathrm{area\_use\_max}_{n,i} \text{ is defined} \vee \mathrm{area\_use\_per\_flow\_cap}_{n,i} \text{ is defined} \vee \mathrm{sink\_unit}_{n,i} = \text{'}\mathrm{per\_area}\text{'} \vee \mathrm{source\_unit}_{n,i} = \text{'}\mathrm{per\_area}\text{'} +``` + +**`source_cap`** + +```math +0 \le \mathit{source\_cap}_{n,i} \le \mathrm{source\_cap\_max}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I} \,:\, \mathrm{base\_tech}_{i} = \text{'}\mathrm{supply}\text{'} +``` + +**`storage_cap`** + +```math +0 \le \mathit{storage}^{\mathrm{cap}}_{n,i} \le \mathrm{storage}^{\mathrm{cap,max}}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I} \,:\, \mathrm{include\_storage}_{n,i} \vee \mathrm{base\_tech}_{i} = \text{'}\mathrm{storage}\text{'} +``` + +**`flow_out_max`** + +```math +\mathit{flow\_out}_{n,i,c,t} \le \mathit{flow\_cap}_{n,i,c} \cdot \mathrm{timestep\_resolution}_{t} \cdot \mathrm{flow\_out\_parasitic\_eff}_{n,i,c,t} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} \,:\, \mathrm{carrier\_out}_{n,i,c} \wedge \neg \left( \mathit{operating\_units}_{n,i,t} \text{ exists} \right) +``` + +**`flow_out_min`** + +```math +\mathit{flow\_out}_{n,i,c,t} \ge \mathit{flow\_cap}_{n,i,c} \cdot \mathrm{timestep\_resolution}_{t} \cdot \mathrm{flow\_out\_min\_relative}_{n,i,t} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} \,:\, \mathit{flow\_cap}_{n,i,c} \text{ exists} \wedge \mathrm{flow\_out\_min\_relative}_{n,i,t} \text{ is defined} \wedge \neg \left( \mathit{operating\_units}_{n,i,t} \text{ exists} \right) +``` + +**`flow_in_max`** + +```math +\mathit{flow\_in}_{n,i,c,t} \le \mathit{flow\_cap}_{n,i,c} \cdot \mathrm{timestep\_resolution}_{t} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T} \,:\, \mathrm{carrier\_in}_{n,i,c} \wedge \neg \left( \mathit{operating\_units}_{n,i,t} \text{ exists} \right) +``` + +**`flow_capacity_systemwide_min`** + +```math +\sum_{n \in \mathcal{N}} \mathit{flow\_cap}_{n,i,c} \ge \mathrm{flow\_cap\_min\_systemwide}_{i,c} \qquad \forall\, i \in \mathcal{I},\ c \in \mathcal{C} \,:\, \lvert \{ n \in \mathcal{N} \,:\, \mathit{flow\_cap}_{n,i,c} \text{ exists} \} \rvert \ge 1 \wedge \mathrm{flow\_cap\_min\_systemwide}_{i,c} \text{ is defined} \wedge \neg \left( \lvert \{ n \in \mathcal{N} \,:\, \mathit{purchased\_units}_{n,i} \text{ exists} \} \rvert \ge 1 \right) +``` + diff --git a/docs/examples/calliope/variants/operate.md b/docs/examples/calliope/variants/operate.md new file mode 100644 index 00000000..5c83e281 --- /dev/null +++ b/docs/examples/calliope/variants/operate.md @@ -0,0 +1,87 @@ + + +# Operate mode + +A patch of [Calliope in fragments](../index.md). Calliope's `operate.yaml`: every capacity is data, not a decision. The patch turns each capacity variable into a parameter of the same name, removes the rows and the costs that only a capacity decision has, and leaves the operating cost. Calliope's rolling horizon is a loop of solves, and is not in the math. A patch is not a spec, so it prints as the declarations it writes, in the spec it lands on. + + +```python +ms.override( + ms.merge(base), + ['variants/operate.yaml'], +) +``` + +```yaml title="variants/operate.yaml" +parameters: + flow_cap: + description: "`flow_cap` — the flow capacity, fixed in operate mode. Calliope's default is `.inf`" + dims: [nodes, techs, carriers] + area_use: + description: "`area_use` — the area used, fixed in operate mode. Calliope's default is `.inf`" + dims: [nodes, techs] + source_cap: + description: "`source_cap` — the source capacity, fixed in operate mode. Calliope's default is `.inf`" + dims: [nodes, techs] + storage_cap: + description: "`storage_cap` — the storage capacity, fixed in operate mode. Calliope's default is `.inf`" + dims: [nodes, techs] + +variables: + flow_cap: null + area_use: null + source_cap: null + storage_cap: null + +constraints: + flow_capacity_per_storage_capacity_min: null + flow_capacity_per_storage_capacity_max: null + source_capacity_equals_flow_capacity: null + force_zero_area_use: null + area_use_per_flow_capacity: null + area_use_capacity_per_loc: null + flow_capacity_systemwide_max: null + flow_capacity_systemwide_min: null + symmetric_transmission: null + +expressions: + cost_investment: null + cost_investment_annualised: null + cost_investment_flow_cap: null + cost_investment_storage_cap: null + cost_investment_source_cap: null + cost_investment_area_use: null + cost_operation_fixed: null + cost_flow_cap_sum: null + depreciation_rate: null + flows_cost_investment: null + flows_cost_operation_fixed: null + flow_cap_from: null + flow_cap_to: null + cost: + description: "`cost` — the operating cost of a technology, over every time step" + expression: sum(cost_operation_variable, over=timesteps) + +assumptions: + operate_mode_cyclic_storage: + description: Calliope's `operate_mode_cyclic_storage` — a store in operate mode is not cyclic + holds: NOT (cyclic_storage AND (base_tech == 'storage' OR include_storage)) +``` + +**`cost`** + +```math +\mathit{cost}_{n,i,k} = \sum_{t \in \mathcal{T}} \mathit{cost}^{\mathrm{operation,variable}}_{n,i,k,t} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ k \in \mathcal{K} +``` + +**`operate_mode_cyclic_storage`** + +```math +\neg \left( \mathrm{cyclic\_storage}_{n,i} \wedge \left( \mathrm{base\_tech}_{i} = \text{'}\mathrm{storage}\text{'} \vee \mathrm{include\_storage}_{n,i} \right) \right) \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I} +``` + +Removed: `flow_cap`, `area_use`, `source_cap`, `storage_cap`, `cost_investment`, `cost_investment_annualised`, `cost_investment_flow_cap`, `cost_investment_storage_cap`, `cost_investment_source_cap`, `cost_investment_area_use`, `cost_operation_fixed`, `cost_flow_cap_sum`, `depreciation_rate`, `flows_cost_investment`, `flows_cost_operation_fixed`, `flow_cap_from`, `flow_cap_to`, `flow_capacity_per_storage_capacity_min`, `flow_capacity_per_storage_capacity_max`, `source_capacity_equals_flow_capacity`, `force_zero_area_use`, `area_use_per_flow_capacity`, `area_use_capacity_per_loc`, `flow_capacity_systemwide_max`, `flow_capacity_systemwide_min`, `symmetric_transmission`. + diff --git a/docs/examples/calliope/variants/operate_milp.md b/docs/examples/calliope/variants/operate_milp.md new file mode 100644 index 00000000..baf3c1de --- /dev/null +++ b/docs/examples/calliope/variants/operate_milp.md @@ -0,0 +1,50 @@ + + +# Operate mode, with MILP + +A patch of [Calliope in fragments](../index.md). The half of Calliope's `operate.yaml` that lands on the MILP fragment: the units bought are data too. It is laid after the MILP patch and operate mode. A patch is not a spec, so it prints as the declarations it writes, in the spec it lands on. + + +```python +ms.override( + ms.merge(base + ['extensions/milp.yaml']), + ['variants/milp.yaml', 'variants/operate.yaml', 'variants/operate_milp.yaml'], +) +``` + +```yaml title="variants/operate_milp.yaml" +parameters: + purchased_units: + description: "`purchased_units` — the units bought, fixed in operate mode. Calliope's default is `.inf`" + dims: [nodes, techs] + +variables: + purchased_units: null + +constraints: + storage_capacity_units_milp: null + flow_capacity_units_milp: null + unit_capacity_max_systemwide_milp: null + unit_capacity_min_systemwide_milp: null + flow_capacity_max_purchase_milp: null + flow_capacity_max_purchase_milp_big_m: null + storage_capacity_max_purchase_milp: null + flow_capacity_minimum: null + flow_capacity_minimum_purchased: null + storage_capacity_minimum: null + storage_capacity_minimum_purchased: null + area_use_minimum: null + area_use_minimum_purchased: null + source_capacity_minimum: null + source_capacity_minimum_purchased: null + flow_capacity_systemwide_min_purchased: null + +expressions: + cost_investment_purchase: null +``` + +Removed: `purchased_units`, `cost_investment_purchase`, `storage_capacity_units_milp`, `flow_capacity_units_milp`, `unit_capacity_max_systemwide_milp`, `unit_capacity_min_systemwide_milp`, `flow_capacity_max_purchase_milp`, `flow_capacity_max_purchase_milp_big_m`, `storage_capacity_max_purchase_milp`, `flow_capacity_minimum`, `flow_capacity_minimum_purchased`, `storage_capacity_minimum`, `storage_capacity_minimum_purchased`, `area_use_minimum`, `area_use_minimum_purchased`, `source_capacity_minimum`, `source_capacity_minimum_purchased`, `flow_capacity_systemwide_min_purchased`. + diff --git a/docs/examples/calliope/variants/spores.md b/docs/examples/calliope/variants/spores.md new file mode 100644 index 00000000..990b46de --- /dev/null +++ b/docs/examples/calliope/variants/spores.md @@ -0,0 +1,79 @@ + + +# SPORES + +A patch of [Calliope in fragments](../index.md). Calliope's `spores.yaml`: the objective is the SPORES score of the capacity built, and the least-cost objective becomes a row capped at the least cost plus a slack. The row reads the sums the objective read, so a file that adds a cost is capped too. The iteration that updates the scores is a loop of solves, and is not in the math. A patch is not a spec, so it prints as the declarations it writes, in the spec it lands on. + + +```python +ms.override( + ms.merge(base), + ['variants/spores.yaml'], +) +``` + +```yaml title="variants/spores.yaml" +parameters: + spores_baseline_cost: + description: >- + `spores_baseline_cost` — the least cost of the system, which a SPORES + iteration may exceed by its slack. Calliope's default is `.inf` + dims: [] + spores_slack: + description: "`spores_slack` — the share by which a SPORES iteration may exceed the least cost" + dims: [] + spores_score: + description: "`spores_score` — the score a technology at a node carries from the SPORES iterations before" + dims: [nodes, techs, carriers] + +constraints: + total_system_cost_max: + description: >- + `total_system_cost_max` — the cost the least-cost objective reads is at + most the least cost plus the slack. It reads the same sums the + objective did, so a file that adds a cost adds it here too + dims: [] + expression: system_cost + penalty <= spores_baseline_cost * (1 + spores_slack) + +expressions: + spores_score_cumulative: + description: "`spores_score_cumulative` — the SPORES score, reported with the results" + expression: spores_score + spores_baseline_cost_tracked: + description: "`spores_baseline_cost_tracked` — the SPORES baseline cost, reported with the results" + expression: spores_baseline_cost + +objective: + description: >- + `min_spores` — the SPORES score of the flow capacity built, plus the + penalty on unmet demand + expression: sum(flow_cap * spores_score) + penalty +``` + +**`spores_score_cumulative`** + +```math +\mathrm{spores\_score\_cumulative}_{n,i,c} = \mathrm{spores\_score}_{n,i,c} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C} +``` + +**`spores_baseline_cost_tracked`** + +```math +\mathrm{spores\_baseline\_cost\_tracked} = \mathrm{spores\_baseline\_cost} +``` + +**`total_system_cost_max`** + +```math +\mathit{system\_cost} + \mathit{penalty} \le \mathrm{spores\_baseline\_cost} \cdot \left( 1 + \mathrm{spores\_slack} \right) +``` + +**The objective** + +```math +\min \sum_{n \in \mathcal{N},\ i \in \mathcal{I},\ c \in \mathcal{C}} \mathit{flow\_cap}_{n,i,c} \cdot \mathrm{spores\_score}_{n,i,c} + \mathit{penalty} +``` + diff --git a/docs/examples/calliope/variants/storage_inter_cluster.md b/docs/examples/calliope/variants/storage_inter_cluster.md new file mode 100644 index 00000000..ae630f19 --- /dev/null +++ b/docs/examples/calliope/variants/storage_inter_cluster.md @@ -0,0 +1,237 @@ + + +# Inter-cluster storage + +A patch of [Calliope in fragments](../index.md). Calliope's `storage_inter_cluster.yaml`: with clustered days, a store carries a fill between the days of the whole time series and swings within its clustered day. A patch is not a spec, so it prints as the declarations it writes, in the spec it lands on. + + +```python +ms.override( + ms.merge(base), + ['variants/storage_inter_cluster.yaml'], +) +``` + +```yaml title="variants/storage_inter_cluster.yaml" +dimensions: + clusters: + description: Calliope's `clusters` — the representative days a clustered time series is made of + dtype: int + datesteps: + description: Calliope's `datesteps` — the days of the whole time series, in order + dtype: datetime + +relations: + timestep_cluster: + description: "`timestep_cluster` — the cluster a time step belongs to" + key: timesteps + values: clusters + lookup_datestep_cluster: + description: "`lookup_datestep_cluster` — the cluster a day stands for" + key: datesteps + values: clusters + lookup_datestep_last_cluster_timestep: + description: "`lookup_datestep_last_cluster_timestep` — the last time step of the cluster a day stands for" + key: datesteps + values: timesteps + +parameters: + storage_retention: + description: >- + `1 - storage_loss` — the share of what a store holds that it keeps for + an hour, data prep. Between days it is raised to 24, so it does not + vary over time steps here + dims: [nodes, techs] + +variables: + storage: + description: >- + `storage` — what a store holds within a clustered day, relative to what + it carries between days. It may go below zero, as long as the sum does + not + bounds: { lower: null } + storage_inter_cluster: + description: "`storage_inter_cluster` — what a store carries from one day of the whole time series to the next" + dims: [nodes, techs, datesteps] + where: include_storage OR base_tech == 'storage' + bounds: { lower: 0 } + absence: zero + storage_intra_cluster_max: + description: "`storage_intra_cluster_max` — the most a store holds within a clustered day" + dims: [nodes, techs, clusters] + where: include_storage OR base_tech == 'storage' + storage_intra_cluster_min: + description: "`storage_intra_cluster_min` — the least a store holds within a clustered day" + dims: [nodes, techs, clusters] + where: include_storage OR base_tech == 'storage' + +expressions: + storage_previous_step: + description: >- + `$storage_previous_step` under inter-cluster storage — what a store + carries into a time step: its initial fill at the first step of a store + that is not cyclic, nothing at the first step of a clustered day, and + what is left of the step before everywhere else + cases: + initial: + when: position(timesteps) == 0 AND NOT cyclic_storage + expression: storage_initial * storage_cap + cluster_start: + when: lookup_cluster_last_timestep AND NOT (position(timesteps) == 0 AND NOT cyclic_storage) + expression: "0" + storage_inter_previous_step: + description: >- + `$storage_previous_step` of `balance_storage_inter` — what a store + carries into a day: its initial fill on the first day of a store that + is not cyclic, and what is left of the day before everywhere else. + Calliope reads the initial fill as a share, not times the capacity, as + here + dims: [nodes, techs, datesteps] + cases: + initial: + when: position(datesteps) == 0 AND NOT cyclic_storage + expression: storage_initial + otherwise: storage_retention ** 24 * shift(storage_inter_cluster, along=datesteps, offset=1, edge='wrap') + storage_intra: + description: >- + `$storage_intra` of `balance_storage_inter` — what the clustered day of + the day before left at its last step, and nothing on the first day of + a store that is not cyclic + dims: [nodes, techs, datesteps] + cases: + initial: + when: position(datesteps) == 0 AND NOT cyclic_storage + expression: "0" + otherwise: >- + shift(at(storage, by=lookup_datestep_last_cluster_timestep, over=timesteps, into=datesteps), + along=datesteps, offset=1, edge='wrap') + +constraints: + storage_max: null + set_storage_initial: + description: >- + `set_storage_initial` under inter-cluster storage — a cyclic store with + an initial fill carries it between days at the end, after a day's loss + dims: [nodes, techs, datesteps] + where: position(datesteps) == -1 AND storage_inter_cluster AND storage_initial AND cyclic_storage + expression: storage_inter_cluster * storage_retention ** 24 == storage_initial * storage_cap + storage_intra_max: + description: "`storage_intra_max` — a store holds at most its most within its clustered day" + dims: [nodes, techs, timesteps] + where: include_storage OR base_tech == 'storage' + expression: storage <= at(storage_intra_cluster_max, by=timestep_cluster, over=clusters, into=timesteps) + storage_intra_min: + description: "`storage_intra_min` — a store holds at least its least within its clustered day" + dims: [nodes, techs, timesteps] + where: include_storage OR base_tech == 'storage' + expression: storage >= at(storage_intra_cluster_min, by=timestep_cluster, over=clusters, into=timesteps) + storage_inter_max: + description: "`storage_inter_max` — what a store carries between days plus the most of its day is at most its capacity" + dims: [nodes, techs, datesteps] + where: include_storage OR base_tech == 'storage' + expression: >- + storage_inter_cluster + at(storage_intra_cluster_max, by=lookup_datestep_cluster, over=clusters, into=datesteps) + <= storage_cap + storage_inter_min: + description: "`storage_inter_min` — what a store carries between days, after a day's loss, plus the least of its day is not below zero" + dims: [nodes, techs, datesteps] + where: include_storage OR base_tech == 'storage' + expression: >- + storage_inter_cluster * storage_retention ** 24 + + at(storage_intra_cluster_min, by=lookup_datestep_cluster, over=clusters, into=datesteps) >= 0 + balance_storage_inter: + description: >- + `balance_storage_inter` — what a store carries into a day is what it + carried into the day before, after a day's loss, plus what that day's + cluster left + dims: [nodes, techs, datesteps] + where: include_storage OR base_tech == 'storage' + expression: storage_inter_cluster == storage_inter_previous_step + storage_intra + +assumptions: + cyclic_storage_needs_inter_cluster: null +``` + +**`storage`** + +```math +\mathit{storage}_{n,i,t} \in \mathbb{R} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T} \,:\, \mathrm{include\_storage}_{n,i} \vee \mathrm{base\_tech}_{i} = \text{'}\mathrm{storage}\text{'} +``` + +**`storage_inter_cluster`** + +```math +\mathit{storage}^{\mathrm{inter,cluster}}_{n,i,d} \ge 0 \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ d \in \mathcal{D} \,:\, \mathrm{include\_storage}_{n,i} \vee \mathrm{base\_tech}_{i} = \text{'}\mathrm{storage}\text{'} +``` + +**`storage_intra_cluster_max`** + +```math +\mathit{storage}^{\mathrm{intra,cluster,max}}_{n,i,l} \in \mathbb{R} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ l \in \mathcal{L} \,:\, \mathrm{include\_storage}_{n,i} \vee \mathrm{base\_tech}_{i} = \text{'}\mathrm{storage}\text{'} +``` + +**`storage_intra_cluster_min`** + +```math +\mathit{storage}^{\mathrm{intra,cluster,min}}_{n,i,l} \in \mathbb{R} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ l \in \mathcal{L} \,:\, \mathrm{include\_storage}_{n,i} \vee \mathrm{base\_tech}_{i} = \text{'}\mathrm{storage}\text{'} +``` + +**`storage_previous_step`** + +```math +\mathit{storage}^{\mathrm{previous,step}}_{n,i,t} = \begin{cases} \mathrm{storage}^{\mathrm{initial}}_{n,i} \cdot \mathit{storage}^{\mathrm{cap}}_{n,i} & \text{if } \mathrm{pos}(t) = 0 \wedge \neg \mathrm{cyclic\_storage}_{n,i} \\ 0 & \text{if } \mathrm{lookup\_cluster\_last\_timestep}(t) \text{ is defined} \wedge \neg \left( \mathrm{pos}(t) = 0 \wedge \neg \mathrm{cyclic\_storage}_{n,i} \right) \\ \mathrm{storage}^{\mathrm{retention}}_{n,i}^{\mathrm{timestep\_resolution}_{t \ominus 1}} \cdot \mathit{storage}_{n,i,t \ominus 1} & \text{otherwise} \end{cases} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T} +``` + +**`storage_inter_previous_step`** + +```math +\mathit{storage}^{\mathrm{inter,previous,step}}_{n,i,d} = \begin{cases} \mathrm{storage}^{\mathrm{initial}}_{n,i} & \text{if } \mathrm{pos}(d) = 0 \wedge \neg \mathrm{cyclic\_storage}_{n,i} \\ \mathrm{storage}^{\mathrm{retention}}_{n,i}^{24} \cdot \mathit{storage}^{\mathrm{inter,cluster}}_{n,i,d \ominus 1} & \text{otherwise} \end{cases} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ d \in \mathcal{D} +``` + +**`storage_intra`** + +```math +\mathit{storage}^{\mathrm{intra}}_{n,i,d} = \begin{cases} 0 & \text{if } \mathrm{pos}(d) = 0 \wedge \neg \mathrm{cyclic\_storage}_{n,i} \\ \mathit{storage}_{n,i,\mathrm{lookup\_datestep\_last\_cluster\_timestep}(d \ominus 1)} & \text{otherwise} \end{cases} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ d \in \mathcal{D} +``` + +**`set_storage_initial`** + +```math +\mathit{storage}^{\mathrm{inter,cluster}}_{n,i,d} \cdot \mathrm{storage}^{\mathrm{retention}}_{n,i}^{24} = \mathrm{storage}^{\mathrm{initial}}_{n,i} \cdot \mathit{storage}^{\mathrm{cap}}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ d \in \mathcal{D} \,:\, \mathrm{pos}(d) = \lvert \mathcal{D} \rvert - 1 \wedge \mathit{storage}^{\mathrm{inter,cluster}}_{n,i,d} \text{ exists} \wedge \mathrm{storage}^{\mathrm{initial}}_{n,i} \text{ is defined} \wedge \mathrm{cyclic\_storage}_{n,i} +``` + +**`storage_intra_max`** + +```math +\mathit{storage}_{n,i,t} \le \mathit{storage}^{\mathrm{intra,cluster,max}}_{n,i,\mathrm{timestep\_cluster}(t)} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T} \,:\, \mathrm{include\_storage}_{n,i} \vee \mathrm{base\_tech}_{i} = \text{'}\mathrm{storage}\text{'} +``` + +**`storage_intra_min`** + +```math +\mathit{storage}_{n,i,t} \ge \mathit{storage}^{\mathrm{intra,cluster,min}}_{n,i,\mathrm{timestep\_cluster}(t)} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T} \,:\, \mathrm{include\_storage}_{n,i} \vee \mathrm{base\_tech}_{i} = \text{'}\mathrm{storage}\text{'} +``` + +**`storage_inter_max`** + +```math +\mathit{storage}^{\mathrm{inter,cluster}}_{n,i,d} + \mathit{storage}^{\mathrm{intra,cluster,max}}_{n,i,\mathrm{lookup\_datestep\_cluster}(d)} \le \mathit{storage}^{\mathrm{cap}}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ d \in \mathcal{D} \,:\, \mathrm{include\_storage}_{n,i} \vee \mathrm{base\_tech}_{i} = \text{'}\mathrm{storage}\text{'} +``` + +**`storage_inter_min`** + +```math +\mathit{storage}^{\mathrm{inter,cluster}}_{n,i,d} \cdot \mathrm{storage}^{\mathrm{retention}}_{n,i}^{24} + \mathit{storage}^{\mathrm{intra,cluster,min}}_{n,i,\mathrm{lookup\_datestep\_cluster}(d)} \ge 0 \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ d \in \mathcal{D} \,:\, \mathrm{include\_storage}_{n,i} \vee \mathrm{base\_tech}_{i} = \text{'}\mathrm{storage}\text{'} +``` + +**`balance_storage_inter`** + +```math +\mathit{storage}^{\mathrm{inter,cluster}}_{n,i,d} = \mathit{storage}^{\mathrm{inter,previous,step}}_{n,i,d} + \mathit{storage}^{\mathrm{intra}}_{n,i,d} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ d \in \mathcal{D} \,:\, \mathrm{include\_storage}_{n,i} \vee \mathrm{base\_tech}_{i} = \text{'}\mathrm{storage}\text{'} +``` + +Removed: `storage_max`, `cyclic_storage_needs_inter_cluster`. + diff --git a/docs/examples/calliope/variants/urban_scale_chp.md b/docs/examples/calliope/variants/urban_scale_chp.md new file mode 100644 index 00000000..e97dc52e --- /dev/null +++ b/docs/examples/calliope/variants/urban_scale_chp.md @@ -0,0 +1,32 @@ + + +# The urban-scale CHP patch + +A patch of [Calliope in fragments](../index.md). What the urban-scale example changes in the base: `balance_conversion` holds for every conversion technology but `chp`. Its rows are [the urban-scale fragment](../extensions/urban_scale_chp.md). A patch is not a spec, so it prints as the declarations it writes, in the spec it lands on. + + +```python +ms.override( + ms.merge(base + ['extensions/urban_scale_chp.yaml']), + ['variants/urban_scale_chp.yaml'], +) +``` + +```yaml title="variants/urban_scale_chp.yaml" +constraints: + balance_conversion: + description: >- + `balance_conversion` for every conversion technology but `chp` — it + puts out, before its losses, what it takes in after them + where: base_tech == 'conversion' AND NOT include_storage AND NOT techs == chp +``` + +**`balance_conversion`** + +```math +\sum_{c \in \mathcal{C}} \mathit{flow\_out\_inc\_eff}_{n,i,c,t} = \sum_{c \in \mathcal{C}} \mathit{flow\_in\_inc\_eff}_{n,i,c,t} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T} \,:\, \mathrm{base\_tech}_{i} = \text{'}\mathrm{conversion}\text{'} \wedge \neg \mathrm{include\_storage}_{n,i} \wedge \neg \left( i = \text{'}\mathrm{chp}\text{'} \right) +``` + diff --git a/docs/examples/index.md b/docs/examples/index.md index 7a53f66d..9b2c8f4a 100644 --- a/docs/examples/index.md +++ b/docs/examples/index.md @@ -21,5 +21,7 @@ Every spec is a file under `examples/` in the repository. The PyPSA parity pages, from [PyPSA in one file](pypsa.md) on, are a proof of concept. They sit in the Development section, and [PyPSA in 24 files](pypsa/index.md) is the same spec composed from fragments. +[Calliope in fragments](calliope/index.md) is the second: all of Calliope's +math, its base merged from fragments, and its modes laid over as patches. [Typeset the math](../reference/typeset.md) prints your own. diff --git a/examples/calliope/area.yaml b/examples/calliope/area.yaml new file mode 100644 index 00000000..f7e94268 --- /dev/null +++ b/examples/calliope/area.yaml @@ -0,0 +1,81 @@ +# SPDX-FileCopyrightText: mathspec Contributors +# +# SPDX-License-Identifier: MIT + +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + costs: + description: Calliope's `costs` — cost classes, such as monetary and CO2 + +parameters: + area_use_min: + description: "`area_use_min` — least area use. Calliope's default is 0, and data prep fills it" + dims: [nodes, techs] + area_use_max: + description: "`area_use_max` — most area use. Calliope's default is `.inf`, and data prep fills it" + dims: [nodes, techs] + area_use_per_flow_cap: + description: "`area_use_per_flow_cap` — area use per unit of flow capacity; given only where set" + dims: [nodes, techs] + available_area: + description: "`available_area` — the area every technology at a node may use; given only where set" + dims: [nodes] + cost_area_use: + description: "`cost_area_use` — the cost of one unit of area use" + dims: [nodes, techs, costs] + +variables: + area_use: + description: >- + `area_use` — the area a technology uses. Calliope builds it where + `area_use_min` is given at all; the least area use is data here, so + it is built where that is above zero + dims: [nodes, techs] + where: area_use_min > 0 OR area_use_max OR area_use_per_flow_cap OR sink_unit == per_area OR source_unit == per_area + bounds: { lower: area_use_min, upper: area_use_max } + absence: zero + +expressions: + cost_investment_area_use: + description: "`cost_investment_area_use` — the investment cost of area use" + expression: cost_area_use * area_use + +given: + parameters: + flow_cap_max: { dims: [nodes, techs] } + sink_unit: { dims: [nodes, techs], dtype: str } + source_unit: { dims: [nodes, techs], dtype: str } + variables: + flow_cap: { dims: [nodes, techs, carriers] } + expressions: + cost_investment: { dims: [nodes, techs, costs], term: cost_investment_area_use } + +constraints: + force_zero_area_use: + description: "`force_zero_area_use` — a technology with no flow capacity uses no area" + dims: [nodes, techs] + where: area_use AND flow_cap_max == 0 + expression: area_use == 0 + area_use_per_flow_capacity: + description: "`area_use_per_flow_capacity` — area use follows flow capacity, where set" + dims: [nodes, techs, carriers] + where: flow_cap AND area_use AND area_use_per_flow_cap + expression: area_use == flow_cap * area_use_per_flow_cap + area_use_capacity_per_loc: + description: >- + `area_use_capacity_per_loc` — the technologies at a node use at most + its available area. Calliope's `where: area_use` over a node reads as + any technology there using area + dims: [nodes] + where: count(area_use, over=techs) >= 1 AND available_area + expression: sum(area_use, over=techs) <= available_area + +assumptions: + unbounded_area_use_cost: + description: Calliope's `unbounded_area_use_cost` — a negative area cost needs a finite maximum + holds: NOT cost_area_use < 0 OR area_use_max diff --git a/examples/calliope/balance.yaml b/examples/calliope/balance.yaml new file mode 100644 index 00000000..238f3695 --- /dev/null +++ b/examples/calliope/balance.yaml @@ -0,0 +1,43 @@ +# SPDX-FileCopyrightText: mathspec Contributors +# +# SPDX-License-Identifier: MIT + +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + +given: + parameters: + carrier_in: + description: whether a technology consumes a carrier at a node + dims: [nodes, techs, carriers] + dtype: bool + carrier_out: + description: whether a technology produces a carrier at a node + dims: [nodes, techs, carriers] + dtype: bool + +expressions: + carrier_flow: + description: >- + what every technology and every other file puts into a node's carrier, + less what it takes out + dims: [nodes, carriers, timesteps] + empty: true + +constraints: + system_balance: + description: >- + `system_balance` — at every node, in every time step, a carrier's + production equals its consumption. Built where a technology at the + node produces or consumes the carrier + dims: [nodes, carriers, timesteps] + where: count(carrier_in, over=techs) >= 1 OR count(carrier_out, over=techs) >= 1 + expression: carrier_flow == 0 diff --git a/examples/calliope/conversion.yaml b/examples/calliope/conversion.yaml new file mode 100644 index 00000000..e201f5a1 --- /dev/null +++ b/examples/calliope/conversion.yaml @@ -0,0 +1,29 @@ +# SPDX-FileCopyrightText: mathspec Contributors +# +# SPDX-License-Identifier: MIT + +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + +given: + parameters: + base_tech: { dims: [techs], dtype: str } + include_storage: { dims: [nodes, techs], dtype: bool } + expressions: + flow_out_inc_eff: { dims: [nodes, techs, carriers, timesteps] } + flow_in_inc_eff: { dims: [nodes, techs, carriers, timesteps] } + +constraints: + balance_conversion: + description: "`balance_conversion` — a conversion technology puts out, before its losses, what it takes in after them" + dims: [nodes, techs, timesteps] + where: base_tech == 'conversion' AND NOT include_storage + expression: sum(flow_out_inc_eff, over=carriers) == sum(flow_in_inc_eff, over=carriers) diff --git a/examples/calliope/cost.yaml b/examples/calliope/cost.yaml new file mode 100644 index 00000000..ded28243 --- /dev/null +++ b/examples/calliope/cost.yaml @@ -0,0 +1,95 @@ +# SPDX-FileCopyrightText: mathspec Contributors +# +# SPDX-License-Identifier: MIT + +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + costs: + description: Calliope's `costs` — cost classes, such as monetary and CO2 + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + +parameters: + cost_om_annual_investment_fraction: + description: "`cost_om_annual_investment_fraction` — the annual cost of operation, as a share of the investment cost" + dims: [nodes, techs, costs] + cost_depreciation_rate: + description: >- + `cost_depreciation_rate` — the share of the investment cost a year + carries; given only where set, and derived from the lifetime and the + interest rate elsewhere + dims: [nodes, techs, costs] + cost_interest_rate: + description: "`cost_interest_rate` — the interest rate an investment is annualised at" + dims: [nodes, techs, costs] + lifetime: + description: >- + `lifetime` — the years a technology lasts. Calliope's default is + `.inf`, and data prep fills it + dims: [nodes, techs] + cost_annuity_factor: + description: >- + the annuity factor `r (1 + r) ** lifetime / ((1 + r) ** lifetime - 1)` + of the interest rate `r`, data prep. mathspec refuses a sum as the + base of `**` and as a divisor, over parameters too + dims: [nodes, techs, costs] + +given: + parameters: + timestep_resolution: { dims: [timesteps] } + timestep_weights: { dims: [timesteps] } + objective_cost_weights: { dims: [costs] } + expressions: + system_cost: { dims: [], term: cost_of_techs } + +expressions: + cost_investment: + description: >- + `cost_investment` — the investment cost of a technology: flow, storage + and source capacity, and area use. Each file that builds a capacity + adds its own cost + dims: [nodes, techs, costs] + empty: true + cost_operation_variable: + description: >- + `cost_operation_variable` — the operating cost of a technology in a + time step. Each file that builds a flow adds its own cost + dims: [nodes, techs, costs, timesteps] + empty: true + cost_operation_fixed: + description: >- + `cost_operation_fixed` — the fixed annual operating cost of a + technology: its share of the investment cost here, and what each file + adds per unit of capacity + dims: [nodes, techs, costs] + expression: annualisation_weight * cost_investment * cost_om_annual_investment_fraction + annualisation_weight: + description: "`$annualisation_weight` — the share of a year the modelled time steps stand for" + expression: sum(timestep_resolution * timestep_weights, over=timesteps) / 8760 + depreciation_rate: + description: >- + `$depreciation_rate` of `cost_investment_annualised` — the share of the + investment cost a year carries: as given, one over the lifetime with + no interest, and the annuity factor with some + dims: [nodes, techs, costs] + cases: + given: + when: cost_depreciation_rate + expression: cost_depreciation_rate + no_interest: + when: NOT cost_depreciation_rate AND (NOT cost_interest_rate OR cost_interest_rate == 0) + expression: 1 / lifetime + otherwise: cost_annuity_factor + cost_investment_annualised: + description: "`cost_investment_annualised` — the investment cost, as a year's share scaled to the modelled time" + expression: annualisation_weight * depreciation_rate * cost_investment + cost: + description: "`cost` — the total cost of a technology: investment, variable and fixed operation" + expression: cost_investment_annualised + sum(cost_operation_variable, over=timesteps) + cost_operation_fixed + cost_of_techs: + description: "`sum(sum(cost, over=[nodes, techs]) * objective_cost_weights, over=costs)` of `min_cost_optimisation`" + expression: sum(sum(sum(cost, over=nodes), over=techs) * objective_cost_weights) diff --git a/examples/calliope/demand.yaml b/examples/calliope/demand.yaml new file mode 100644 index 00000000..da76df02 --- /dev/null +++ b/examples/calliope/demand.yaml @@ -0,0 +1,89 @@ +# SPDX-FileCopyrightText: mathspec Contributors +# +# SPDX-License-Identifier: MIT + +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + +parameters: + sink_use_min: + description: "`sink_use_min` — least sink use in a time step, per unit of `sink_unit`" + dims: [nodes, techs, timesteps] + sink_use_max: + description: "`sink_use_max` — most sink use in a time step, per unit of `sink_unit`; given only where set" + dims: [nodes, techs, timesteps] + sink_use_equals: + description: "`sink_use_equals` — the sink use required in a time step, such as a demand profile; given only where set" + dims: [nodes, techs, timesteps] + sink_unit: + description: >- + `sink_unit` — what the sink is per: `absolute`, `per_area` of area + use, or `per_cap` of flow capacity. Calliope's default is + `absolute`, which is what a technology with no row reads as + dims: [nodes, techs] + dtype: str + +expressions: + flow_cap_in: + description: "`where(flow_cap, carrier_in)` — the flow capacity of the carriers a technology consumes" + dims: [nodes, techs, carriers] + cases: + consumed: + when: carrier_in + expression: flow_cap + otherwise: 0 + sink_scaler: + description: "`$sink_scaler` — what the sink parameters are per: area use, flow capacity, or one" + dims: [nodes, techs] + cases: + per_area: + when: sink_unit == per_area + expression: area_use + per_cap: + when: sink_unit == per_cap + expression: sum(flow_cap_in, over=carriers) + otherwise: 1 + +given: + parameters: + base_tech: { dims: [techs], dtype: str } + carrier_in: { dims: [nodes, techs, carriers], dtype: bool } + variables: + flow_cap: { dims: [nodes, techs, carriers] } + area_use: { dims: [nodes, techs] } + expressions: + flow_in_inc_eff: { dims: [nodes, techs, carriers, timesteps] } + +constraints: + balance_demand_equals: + description: "`balance_demand` where `sink_use_equals` is set — a demand technology takes in what its sink requires" + dims: [nodes, techs, carriers, timesteps] + where: carrier_in AND base_tech == 'demand' AND sink_use_equals + expression: flow_in_inc_eff == sink_use_equals * sink_scaler + balance_demand_max: + description: "`balance_demand` where only `sink_use_max` is set — a demand technology takes in at most what its sink allows" + dims: [nodes, techs, carriers, timesteps] + where: carrier_in AND base_tech == 'demand' AND NOT sink_use_equals AND sink_use_max + expression: flow_in_inc_eff <= sink_use_max * sink_scaler + balance_demand_min_use: + description: "`balance_demand_min_use` — a demand technology takes in at least its least sink use" + dims: [nodes, techs, carriers, timesteps] + where: carrier_in AND sink_use_min AND NOT sink_use_equals AND base_tech == 'demand' + expression: flow_in_inc_eff >= sink_use_min * sink_scaler + +assumptions: + finite_sink_use: + description: Calliope's `finite_source_use`, for the sink — a required use is finite + holds: NOT sink_use_equals == inf + sink_unit_one_of: + description: Calliope's `one_of` on `sink_unit` + holds: sink_unit == absolute OR sink_unit == per_area OR sink_unit == per_cap + where: sink_unit diff --git a/examples/calliope/export.yaml b/examples/calliope/export.yaml new file mode 100644 index 00000000..8cc30ea8 --- /dev/null +++ b/examples/calliope/export.yaml @@ -0,0 +1,65 @@ +# SPDX-FileCopyrightText: mathspec Contributors +# +# SPDX-License-Identifier: MIT + +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + costs: + description: Calliope's `costs` — cost classes, such as monetary and CO2 + +parameters: + carrier_export: + description: "`carrier_export` — whether a technology may export a carrier it produces out of the system" + dims: [nodes, techs, carriers] + dtype: bool + export_min: + description: "`export_min` — least export. Calliope's default is 0, and data prep fills it" + dims: [nodes, techs, carriers] + export_max: + description: "`export_max` — most export. Calliope's default is `.inf`, and data prep fills it" + dims: [nodes, techs, carriers] + cost_export: + description: "`cost_export` — the cost of one unit of export, usually negative" + dims: [nodes, techs, costs, timesteps] + +variables: + flow_export: + description: "`flow_export` — what a technology exports out of the system in a time step" + dims: [nodes, techs, carriers, timesteps] + where: carrier_export + bounds: { lower: export_min, upper: export_max } + absence: zero + +expressions: + export_carrier_flow: -sum(flow_export, over=techs) + export_cost_operation_variable: timestep_weights * sum(cost_export * flow_export, over=carriers) + +given: + parameters: + carrier_out: { dims: [nodes, techs, carriers], dtype: bool } + timestep_weights: { dims: [timesteps] } + variables: + flow_out: { dims: [nodes, techs, carriers, timesteps] } + expressions: + carrier_flow: { dims: [nodes, carriers, timesteps], term: export_carrier_flow } + cost_operation_variable: { dims: [nodes, techs, costs, timesteps], term: export_cost_operation_variable } + +constraints: + export_balance: + description: "`export_balance` — a technology exports at most what it puts out" + dims: [nodes, techs, carriers, timesteps] + where: flow_export + expression: flow_out >= flow_export + +assumptions: + export_only_for_outflows: + description: Calliope's `export_only_for_outflows` — an exported carrier is one the technology produces + holds: NOT carrier_export OR count(carrier_out, over=nodes) >= 1 diff --git a/examples/calliope/extensions/annual_energy_balance.yaml b/examples/calliope/extensions/annual_energy_balance.yaml new file mode 100644 index 00000000..ab39148e --- /dev/null +++ b/examples/calliope/extensions/annual_energy_balance.yaml @@ -0,0 +1,85 @@ +# SPDX-FileCopyrightText: mathspec Contributors +# +# SPDX-License-Identifier: MIT + +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + +parameters: + annual_flow_max: + description: "`annual_flow_max` — the most a technology puts out over the whole time; given only where set" + dims: [techs] + annual_flow_max_group: + description: >- + `annual_flow_max` as the group row reads it — the most the group puts + out over the whole time. Calliope reads one parameter at three shapes + and lets the data choose; a parameter here has one, so the group's + limit is a number of its own + dims: [] + annual_source_max: + description: "`annual_source_max` — the most a technology takes from its source over the whole time; given only where set" + dims: [techs] + annual_sink_max: + description: "`annual_sink_max` — the most a technology puts into its sink over the whole time; given only where set" + dims: [techs] + flow_max_group: + description: "`flow_max_group` — whether a technology is in the group the group limit holds for" + dims: [techs] + dtype: bool + +expressions: + flow_out_of_group: + description: "`flow_out[techs=$techs]` — outflow of the technologies in the group" + dims: [nodes, techs, carriers, timesteps] + cases: + in_group: + when: flow_max_group + expression: flow_out + otherwise: 0 + +given: + parameters: + base_tech: { dims: [techs], dtype: str } + variables: + flow_out: { dims: [nodes, techs, carriers, timesteps] } + flow_in: { dims: [nodes, techs, carriers, timesteps] } + source_use: { dims: [nodes, techs, timesteps] } + +constraints: + annual_energy_balance_per_tech_and_node: + description: "`annual_energy_balance_per_tech_and_node` — a technology at a node puts out at most its annual limit" + dims: [nodes, techs] + where: annual_flow_max + expression: sum(sum(flow_out, over=carriers), over=timesteps) <= annual_flow_max + annual_energy_balance_global_per_tech: + description: "`annual_energy_balance_global_per_tech` — a technology puts out at most its annual limit over every node" + dims: [techs] + where: annual_flow_max + expression: sum(sum(sum(flow_out, over=nodes), over=carriers), over=timesteps) <= annual_flow_max + annual_energy_balance_global_multi_tech: + description: "`annual_energy_balance_global_multi_tech` — the group of technologies puts out at most its annual limit over every node" + dims: [] + where: annual_flow_max_group + expression: sum(flow_out_of_group) <= annual_flow_max_group + annual_energy_balance_total_source_availability: + description: >- + `annual_energy_balance_total_source_availability` — a technology takes + at most its annual limit from its source. Calliope's `where: + source_use` over a technology reads as the technology being a supply + one, which is where `source_use` is built + dims: [techs] + where: base_tech == 'supply' AND annual_source_max + expression: sum(sum(source_use, over=nodes), over=timesteps) <= annual_source_max + annual_energy_balance_total_sink_availability: + description: "`annual_energy_balance_total_sink_availability` — a demand technology takes in at most its annual limit" + dims: [techs] + where: base_tech == 'demand' AND annual_sink_max + expression: sum(sum(sum(flow_in, over=nodes), over=carriers), over=timesteps) <= annual_sink_max diff --git a/examples/calliope/extensions/chp_htp.yaml b/examples/calliope/extensions/chp_htp.yaml new file mode 100644 index 00000000..361cc151 --- /dev/null +++ b/examples/calliope/extensions/chp_htp.yaml @@ -0,0 +1,122 @@ +# SPDX-FileCopyrightText: mathspec Contributors +# +# SPDX-License-Identifier: MIT + +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + +parameters: + turbine_type: + description: "`turbine_type` — `extraction` or `backpressure`: the kind of turbine a combined heat and power plant has" + dims: [nodes, techs] + dtype: str + power_loss_factor: + description: "`power_loss_factor` — `cv`, the power an extraction turbine loses per unit of heat. Calliope's default is 1, and data prep fills it" + dims: [nodes, techs] + power_to_heat_ratio: + description: "`power_to_heat_ratio` — `cb`, the backpressure ratio. Calliope's default is 1, and data prep fills it" + dims: [nodes, techs] + boiler_eff: + description: "`boiler_eff` — the efficiency of the boiler fuel may be diverted to; given only where set" + dims: [nodes, techs] + +expressions: + chp_electricity_out: + description: "`flow_out[carriers=electricity]`" + dims: [nodes, techs, carriers, timesteps] + cases: + electricity: + when: carriers == electricity + expression: flow_out + otherwise: 0 + chp_heat_out: + description: "`flow_out[carriers=heat]`" + dims: [nodes, techs, carriers, timesteps] + cases: + heat: + when: carriers == heat + expression: flow_out + otherwise: 0 + chp_electricity_out_eff: + description: "`flow_out_eff[carriers=electricity]`" + dims: [nodes, techs, carriers, timesteps] + cases: + electricity: + when: carriers == electricity + expression: flow_out_eff + otherwise: 0 + chp_electricity_out_inc_eff: + description: "`flow_out_inc_eff[carriers=electricity]`" + dims: [nodes, techs, carriers, timesteps] + cases: + electricity: + when: carriers == electricity + expression: flow_out_inc_eff + otherwise: 0 + +given: + parameters: + base_tech: { dims: [techs], dtype: str } + include_storage: { dims: [nodes, techs], dtype: bool } + flow_out_eff: { dims: [nodes, techs, carriers, timesteps] } + variables: + flow_out: { dims: [nodes, techs, carriers, timesteps] } + expressions: + flow_out_inc_eff: { dims: [nodes, techs, carriers, timesteps] } + flow_in_inc_eff: { dims: [nodes, techs, carriers, timesteps] } + +constraints: + balance_conversion_backpressure: + description: >- + `balance_conversion` for a backpressure plant with no boiler — the + plant puts out, before losses, as much electricity as it takes in fuel + after them. The patch keeps the base row off these plants + dims: [nodes, techs, timesteps] + where: base_tech == 'conversion' AND NOT include_storage AND turbine_type == backpressure AND NOT boiler_eff + expression: sum(chp_electricity_out_inc_eff, over=carriers) == sum(flow_in_inc_eff, over=carriers) + chp_extraction_line: + description: "`chp_extraction_line` — an extraction plant puts out at most the electricity its fuel gives, less what the heat costs" + dims: [nodes, techs, timesteps] + where: turbine_type == extraction + expression: >- + sum(chp_electricity_out, over=carriers) + <= sum(flow_in_inc_eff, over=carriers) * sum(chp_electricity_out_eff, over=carriers) + - sum(chp_heat_out, over=carriers) * power_loss_factor + chp_backpressure_line_min: + description: "`chp_backpressure_line_min` — an extraction plant puts out at least the backpressure ratio of electricity per unit of heat" + dims: [nodes, techs, timesteps] + where: turbine_type == extraction + expression: sum(chp_electricity_out, over=carriers) >= sum(chp_heat_out, over=carriers) * power_to_heat_ratio + chp_backpressure_line_max: + description: "`chp_backpressure_line_max` — a backpressure plant with a boiler puts out at most the backpressure ratio of electricity per unit of heat" + dims: [nodes, techs, timesteps] + where: turbine_type == backpressure AND boiler_eff + expression: sum(chp_electricity_out, over=carriers) <= sum(chp_heat_out, over=carriers) * power_to_heat_ratio + chp_divert_fuel_to_boiler: + description: "`chp_divert_fuel_to_boiler` — a backpressure plant with a boiler puts out at most the heat its fuel gives through turbine and boiler" + dims: [nodes, techs, timesteps] + where: turbine_type == backpressure AND boiler_eff + expression: >- + sum(chp_heat_out, over=carriers) + <= sum(flow_in_inc_eff, over=carriers) * boiler_eff + - sum(chp_electricity_out, over=carriers) + * (boiler_eff / sum(chp_electricity_out_eff, over=carriers) - 1 / power_to_heat_ratio) + chp_backpressure_line_equals: + description: "`chp_backpressure_line_equals` — a backpressure plant with no boiler puts out the backpressure ratio of electricity per unit of heat" + dims: [nodes, techs, timesteps] + where: turbine_type == backpressure AND NOT boiler_eff + expression: sum(chp_electricity_out, over=carriers) == sum(chp_heat_out, over=carriers) * power_to_heat_ratio + +assumptions: + turbine_type_one_of: + description: Calliope's `one_of` on `turbine_type` + holds: turbine_type == extraction OR turbine_type == backpressure + where: turbine_type diff --git a/examples/calliope/extensions/demand_share_per_timestep_decision.yaml b/examples/calliope/extensions/demand_share_per_timestep_decision.yaml new file mode 100644 index 00000000..259801e6 --- /dev/null +++ b/examples/calliope/extensions/demand_share_per_timestep_decision.yaml @@ -0,0 +1,96 @@ +# SPDX-FileCopyrightText: mathspec Contributors +# +# SPDX-License-Identifier: MIT + +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + +relations: + decide_demand_share: + description: >- + `decide_demand_share` — the demand technology whose inflow a technology + meets a share of. Calliope reads it with `select_from_lookup_arrays`, + which is a read through the relation + key: techs + values: { demand: techs } + demand_share_carrier: + description: >- + `demand_share_carrier` — the carrier a share of demand is counted in. + Calliope slices `flow_out` by it, which is a test of the pair + key: [techs, carriers] + +parameters: + demand_share_relaxation: + description: "`demand_share_relaxation` — how far the share may stray from the one decided, as a fraction" + dims: [nodes, techs] + demand_share_limit: + description: "`demand_share_limit` — the share of demand the technologies meet together; given only where set" + dims: [nodes] + +variables: + demand_share_per_timestep_decision: + description: "`demand_share_per_timestep_decision` — the share of demand a technology meets, the same in every time step" + dims: [nodes, techs] + where: decide_demand_share + bounds: { lower: 0 } + absence: zero + +expressions: + demand_share_flow_out: + description: "`flow_out[carriers=$carrier]` — a technology's outflow of the carrier its share is counted in" + dims: [nodes, techs, carriers, timesteps] + cases: + share_carrier: + when: demand_share_carrier + expression: flow_out + otherwise: 0 + demand_share_sink: + description: "`select_from_lookup_arrays(sink_use_equals, techs=decide_demand_share)` — the demand a technology meets a share of" + expression: at(sink_use_equals, by=decide_demand_share, over=demand, into=techs) + +given: + parameters: + sink_use_equals: { dims: [nodes, techs, timesteps] } + variables: + flow_out: { dims: [nodes, techs, carriers, timesteps] } + +constraints: + demand_share_per_timestep_decision_main_min: + description: "`demand_share_per_timestep_decision_main_min` — a technology puts out at least its decided share of demand, less the relaxation" + dims: [nodes, techs, timesteps] + where: demand_share_per_timestep_decision + expression: >- + sum(demand_share_flow_out, over=carriers) + >= (1 - demand_share_relaxation) * demand_share_sink * demand_share_per_timestep_decision + demand_share_per_timestep_decision_main_max: + description: "`demand_share_per_timestep_decision_main_max` — a technology puts out at most its decided share of demand, plus the relaxation" + dims: [nodes, techs, timesteps] + where: demand_share_per_timestep_decision + expression: >- + sum(demand_share_flow_out, over=carriers) + <= (1 + demand_share_relaxation) * demand_share_sink * demand_share_per_timestep_decision + demand_share_per_timestep_decision_sum: + description: >- + `demand_share_per_timestep_decision_sum` — the decided shares at a node + add up to the limit. Calliope's `where: demand_share_per_timestep_decision` + over a node reads as any technology there deciding a share. Calliope + builds the row in every time step, and it is the same in each; a row + repeated along a dimension it does not read is refused, so it is one + row per node + dims: [nodes] + where: count(demand_share_per_timestep_decision, over=techs) >= 1 AND demand_share_limit + expression: sum(demand_share_per_timestep_decision, over=techs) == demand_share_limit + +assumptions: + demand_share_is_fraction: + description: Calliope's `demand_share_is_fraction` — the demand share limit is a fraction + holds: demand_share_limit >= 0 AND demand_share_limit <= 1 + where: demand_share_limit diff --git a/examples/calliope/extensions/fuel_dist.yaml b/examples/calliope/extensions/fuel_dist.yaml new file mode 100644 index 00000000..079812ec --- /dev/null +++ b/examples/calliope/extensions/fuel_dist.yaml @@ -0,0 +1,79 @@ +# SPDX-FileCopyrightText: mathspec Contributors +# +# SPDX-License-Identifier: MIT + +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + carriers: + description: Calliope's `carriers` — energy and commodity carriers + costs: + description: Calliope's `costs` — cost classes, such as monetary and CO2 + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + +parameters: + allow_fuel_distribution: + description: "`allow_fuel_distribution` — whether a node takes part in distributing a carrier" + dims: [nodes, carriers] + dtype: bool + fuel_import_max: + description: "`fuel_import_max` — the most of a carrier a node imports in a time step; given only where set" + dims: [nodes, carriers] + fuel_export_max: + description: "`fuel_export_max` — the most of a carrier a node exports in a time step; given only where set" + dims: [nodes, carriers] + cost_fuel_distribution: + description: "`cost_fuel_distribution` — the cost of importing one unit of a carrier, and the revenue of exporting it" + dims: [nodes, carriers, costs] + +variables: + fuel_distributor: + description: >- + `fuel_distributor` — what a node imports of a carrier, with no network + behind it; an export is negative + dims: [nodes, carriers, timesteps] + where: allow_fuel_distribution + absence: zero + +expressions: + fuel_dist_carrier_flow: + description: "`+ fuel_distributor` — the term Calliope writes into `system_balance`, by restating it whole" + expression: fuel_distributor + cost_var_fuel_distribution: + description: "`cost_var_fuel_distribution` — the cost of importing, and the revenue of exporting, a carrier" + expression: timestep_weights * fuel_distributor * cost_fuel_distribution + fuel_dist_system_cost: + description: >- + `sum(cost_var_fuel_distribution, …) * objective_cost_weights` — the term + Calliope writes into the objective, by restating it whole + expression: sum(cost_var_fuel_distribution * objective_cost_weights) + +given: + parameters: + timestep_weights: { dims: [timesteps] } + objective_cost_weights: { dims: [costs] } + expressions: + carrier_flow: { dims: [nodes, carriers, timesteps], term: fuel_dist_carrier_flow } + system_cost: { dims: [], term: fuel_dist_system_cost } + +constraints: + restrict_total_imports_and_exports: + description: >- + `restrict_total_imports_and_exports` — what the nodes import of a + carrier is what they export. Calliope's `where: fuel_distributor` over + a carrier reads as any node distributing it + dims: [carriers, timesteps] + where: count(fuel_distributor, over=nodes) >= 1 + expression: sum(fuel_distributor, over=nodes) == 0 + restrict_nodal_imports: + description: "`restrict_nodal_imports` — a node imports at most its limit" + dims: [nodes, carriers, timesteps] + where: fuel_distributor AND fuel_import_max + expression: fuel_distributor <= fuel_import_max + restrict_nodal_exports: + description: "`restrict_nodal_exports` — a node exports at most its limit" + dims: [nodes, carriers, timesteps] + where: fuel_distributor AND fuel_export_max + expression: -1 * fuel_distributor <= fuel_export_max diff --git a/examples/calliope/extensions/max_time_varying.yaml b/examples/calliope/extensions/max_time_varying.yaml new file mode 100644 index 00000000..c973f75e --- /dev/null +++ b/examples/calliope/extensions/max_time_varying.yaml @@ -0,0 +1,33 @@ +# SPDX-FileCopyrightText: mathspec Contributors +# +# SPDX-License-Identifier: MIT + +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + +parameters: + flow_cap_max_relative_per_ts: + description: "`flow_cap_max_relative_per_ts` — the share of its flow capacity a technology may put out in a time step; given only where set" + dims: [nodes, techs, timesteps] + +given: + parameters: + flow_out_parasitic_eff: { dims: [nodes, techs, carriers, timesteps] } + variables: + flow_out: { dims: [nodes, techs, carriers, timesteps] } + flow_cap: { dims: [nodes, techs, carriers] } + +constraints: + max_time_varying_flow_cap: + description: "`max_time_varying_flow_cap` — outflow is at most a share of the flow capacity that varies in time" + dims: [nodes, techs, carriers, timesteps] + where: flow_out AND flow_cap_max_relative_per_ts + expression: flow_out <= flow_cap_max_relative_per_ts * flow_cap * flow_out_parasitic_eff diff --git a/examples/calliope/extensions/milp.yaml b/examples/calliope/extensions/milp.yaml new file mode 100644 index 00000000..36cd9b75 --- /dev/null +++ b/examples/calliope/extensions/milp.yaml @@ -0,0 +1,278 @@ +# SPDX-FileCopyrightText: mathspec Contributors +# +# SPDX-License-Identifier: MIT + +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + costs: + description: Calliope's `costs` — cost classes, such as monetary and CO2 + +parameters: + cap_method: + description: >- + `cap_method` — `continuous` or `integer`: whether a technology's + capacity is bought in whole units. Calliope's default is + `continuous`, which is what a technology with no row reads as + dims: [nodes, techs] + dtype: str + integer_dispatch: + description: "`integer_dispatch` — whether a unit-bought technology runs in whole units" + dims: [nodes, techs] + dtype: bool + force_async_flow: + description: "`force_async_flow` — whether a technology may not take in and put out in one time step" + dims: [nodes, techs] + dtype: bool + flow_cap_per_unit: + description: "`flow_cap_per_unit` — the flow capacity of one unit; given only where set" + dims: [nodes, techs] + storage_cap_per_unit: + description: "`storage_cap_per_unit` — the storage capacity of one unit; given only where set" + dims: [nodes, techs] + purchased_units_min: + description: "`purchased_units_min` — least units bought. Calliope's default is 0, and data prep fills it" + dims: [nodes, techs] + purchased_units_max: + description: "`purchased_units_max` — most units bought. Calliope's default is `.inf`, and data prep fills it" + dims: [nodes, techs] + purchased_units_min_systemwide: + description: "`purchased_units_min_systemwide` — least units of a technology bought over every node" + dims: [techs] + purchased_units_max_systemwide: + description: "`purchased_units_max_systemwide` — most units of a technology bought over every node; given only where set" + dims: [techs] + cost_purchase: + description: "`cost_purchase` — the cost of one unit bought" + dims: [nodes, techs, costs] + cost_purchase_per_distance: + description: "`cost_purchase_per_distance` — the cost of one unit of a link bought, per unit of distance" + dims: [nodes, techs, costs] + +variables: + purchased_units: + description: "`purchased_units` — how many units of a technology are bought" + dims: [nodes, techs] + where: cap_method == integer + domain: integer + bounds: { lower: purchased_units_min, upper: purchased_units_max } + absence: zero + operating_units: + description: "`operating_units` — how many bought units run in a time step" + dims: [nodes, techs, timesteps] + where: integer_dispatch AND cap_method == integer + domain: integer + bounds: { lower: 0 } + absence: zero + async_flow_switch: + description: "`async_flow_switch` — whether a technology puts out, rather than takes in, in a time step" + dims: [nodes, techs, timesteps] + where: force_async_flow + domain: binary + absence: zero + available_flow_cap: + description: "`available_flow_cap` — the flow capacity in a time step: the whole of it where the technology runs, none where it does not" + dims: [nodes, techs, carriers, timesteps] + where: flow_cap AND integer_dispatch AND flow_cap_max AND NOT flow_cap_per_unit + bounds: { lower: 0 } + absence: zero + +expressions: + cost_investment_purchase: + description: "`cost_investment_purchase` — the investment cost of the units bought; a link's cost is split between its two ends" + dims: [nodes, techs, costs] + cases: + transmission: + when: base_tech == 'transmission' + expression: (cost_purchase + cost_purchase_per_distance * distance) * purchased_units * 0.5 + otherwise: cost_purchase * purchased_units + +given: + parameters: + base_tech: { dims: [techs], dtype: str } + distance: { dims: [techs] } + bigM: { dims: [] } + timestep_resolution: { dims: [timesteps] } + timestep_weights: { dims: [timesteps] } + flow_cap_min: { dims: [nodes, techs] } + flow_cap_max: { dims: [nodes, techs] } + flow_cap_min_systemwide: { dims: [techs, carriers] } + flow_out_min_relative: { dims: [nodes, techs, timesteps] } + flow_out_parasitic_eff: { dims: [nodes, techs, carriers, timesteps] } + storage_cap_min: { dims: [nodes, techs] } + storage_cap_max: { dims: [nodes, techs] } + area_use_min: { dims: [nodes, techs] } + source_cap_min: { dims: [nodes, techs] } + variables: + flow_cap: { dims: [nodes, techs, carriers] } + flow_out: { dims: [nodes, techs, carriers, timesteps] } + flow_in: { dims: [nodes, techs, carriers, timesteps] } + storage: { dims: [nodes, techs, timesteps] } + storage_cap: { dims: [nodes, techs] } + area_use: { dims: [nodes, techs] } + source_cap: { dims: [nodes, techs] } + expressions: + cost_investment: { dims: [nodes, techs, costs], term: cost_investment_purchase } + +constraints: + unit_commitment_milp: + description: "`unit_commitment_milp` — at most the units bought run" + dims: [nodes, techs, timesteps] + where: operating_units AND purchased_units + expression: operating_units <= purchased_units + flow_out_max_milp: + description: "`flow_out_max_milp` — outflow is at most what the running units can put out" + dims: [nodes, techs, carriers, timesteps] + where: flow_out AND operating_units AND flow_cap_per_unit + expression: flow_out <= operating_units * timestep_resolution * flow_cap_per_unit * flow_out_parasitic_eff + flow_in_max_milp: + description: "`flow_in_max_milp` — inflow is at most what the running units can take in" + dims: [nodes, techs, carriers, timesteps] + where: flow_in AND operating_units AND flow_cap_per_unit + expression: flow_in <= operating_units * timestep_resolution * flow_cap_per_unit + flow_out_min_milp_per_unit: + description: "`flow_out_min_milp` where `flow_cap_per_unit` is set — outflow is at least the running units' least share" + dims: [nodes, techs, carriers, timesteps] + where: flow_out AND operating_units AND flow_out_min_relative AND flow_cap_per_unit + expression: flow_out >= operating_units * timestep_resolution * flow_cap_per_unit * flow_out_min_relative + flow_out_min_milp_available: + description: "`flow_out_min_milp` where the available flow capacity is built — outflow is at least its least share of it" + dims: [nodes, techs, carriers, timesteps] + where: flow_out AND operating_units AND flow_out_min_relative AND available_flow_cap + expression: flow_out >= available_flow_cap * timestep_resolution * flow_out_min_relative + storage_capacity_units_milp: + description: "`storage_capacity_units_milp` — storage capacity is the units bought times the capacity of one" + dims: [nodes, techs] + where: storage_cap AND purchased_units AND storage_cap_per_unit + expression: storage_cap == purchased_units * storage_cap_per_unit + flow_capacity_units_milp: + description: "`flow_capacity_units_milp` — flow capacity is the units bought times the capacity of one" + dims: [nodes, techs, carriers] + where: flow_cap AND purchased_units AND flow_cap_per_unit + expression: flow_cap == purchased_units * flow_cap_per_unit + flow_capacity_max_purchase_milp: + description: "`flow_capacity_max_purchase_milp` where `flow_cap_max` is set — no flow capacity unless a unit is bought" + dims: [nodes, techs, carriers] + where: flow_cap AND purchased_units AND flow_cap_max + expression: flow_cap <= flow_cap_max * purchased_units + flow_capacity_max_purchase_milp_big_m: + description: "`flow_capacity_max_purchase_milp` where `flow_cap_max` is not set — the same, with `bigM` for the maximum" + dims: [nodes, techs, carriers] + where: flow_cap AND purchased_units AND NOT flow_cap_max + expression: flow_cap <= bigM * purchased_units + storage_capacity_max_purchase_milp: + description: "`storage_capacity_max_purchase_milp` — no storage capacity unless a unit is bought" + dims: [nodes, techs] + where: purchased_units AND storage_cap_max + expression: storage_cap <= storage_cap_max * purchased_units + unit_capacity_max_systemwide_milp: + description: "`unit_capacity_max_systemwide_milp` — the units of a technology bought over every node are at most its system-wide maximum" + dims: [techs] + where: count(purchased_units, over=nodes) >= 1 AND purchased_units_max_systemwide + expression: sum(purchased_units, over=nodes) <= purchased_units_max_systemwide + unit_capacity_min_systemwide_milp: + description: >- + `unit_capacity_min_systemwide_milp` — the units of a technology bought + over every node are at least its system-wide minimum. Calliope builds + it where the system-wide maximum is set, as here + dims: [techs] + where: count(purchased_units, over=nodes) >= 1 AND purchased_units_max_systemwide + expression: sum(purchased_units, over=nodes) >= purchased_units_min_systemwide + async_flow_in_milp: + description: "`async_flow_in_milp` — no inflow in a time step the switch gives to outflow" + dims: [nodes, techs, timesteps] + where: async_flow_switch + expression: sum(flow_in, over=carriers) <= (1 - async_flow_switch) * bigM + async_flow_out_milp: + description: "`async_flow_out_milp` — no outflow in a time step the switch gives to inflow" + dims: [nodes, techs, timesteps] + where: async_flow_switch + expression: sum(flow_out, over=carriers) <= async_flow_switch * bigM + available_flow_cap_continuous: + description: "`available_flow_cap_continuous` — the available flow capacity is at most the flow capacity" + dims: [nodes, techs, carriers, timesteps] + where: available_flow_cap + expression: available_flow_cap <= flow_cap + available_flow_cap_binary: + description: "`available_flow_cap_binary` — the available flow capacity is zero where no unit runs" + dims: [nodes, techs, carriers, timesteps] + where: available_flow_cap + expression: available_flow_cap <= flow_cap_max * operating_units + available_flow_cap_max_binary_continuous_switch: + description: "`available_flow_cap_max_binary_continuous_switch` — the available flow capacity is the whole flow capacity where the units run" + dims: [nodes, techs, carriers, timesteps] + where: available_flow_cap + expression: available_flow_cap >= flow_cap + (operating_units - purchased_units) * flow_cap_max + flow_capacity_minimum: + description: "`flow_capacity_minimum` where no unit is bought — flow capacity is at least its least" + dims: [nodes, techs, carriers] + where: flow_cap AND flow_cap_min AND NOT purchased_units + expression: flow_cap >= flow_cap_min + flow_capacity_minimum_purchased: + description: "`flow_capacity_minimum` where units are bought — flow capacity is at least its least, if a unit is bought" + dims: [nodes, techs, carriers] + where: flow_cap AND flow_cap_min AND purchased_units + expression: flow_cap >= flow_cap_min * purchased_units + storage_capacity_minimum: + description: "`storage_capacity_minimum` where no unit is bought — storage capacity is at least its least" + dims: [nodes, techs] + where: storage_cap_min AND NOT purchased_units + expression: storage_cap >= storage_cap_min + storage_capacity_minimum_purchased: + description: "`storage_capacity_minimum` where units are bought — storage capacity is at least its least, if a unit is bought" + dims: [nodes, techs] + where: storage_cap_min AND purchased_units + expression: storage_cap >= storage_cap_min * purchased_units + area_use_minimum: + description: "`area_use_minimum` where no unit is bought — area use is at least its least" + dims: [nodes, techs] + where: area_use_min AND NOT purchased_units + expression: area_use >= area_use_min + area_use_minimum_purchased: + description: "`area_use_minimum` where units are bought — area use is at least its least, if a unit is bought" + dims: [nodes, techs] + where: area_use_min AND purchased_units + expression: area_use >= area_use_min * purchased_units + source_capacity_minimum: + description: "`source_capacity_minimum` where no unit is bought — source capacity is at least its least" + dims: [nodes, techs] + where: base_tech == 'supply' AND source_cap_min AND NOT purchased_units + expression: source_cap >= source_cap_min + source_capacity_minimum_purchased: + description: "`source_capacity_minimum` where units are bought — source capacity is at least its least, if a unit is bought" + dims: [nodes, techs] + where: base_tech == 'supply' AND source_cap_min AND purchased_units + expression: source_cap >= source_cap_min * purchased_units + flow_capacity_systemwide_min_purchased: + description: >- + `flow_capacity_systemwide_min` where units are bought — the flow + capacity over every node is at least the system-wide minimum times the + units bought. The patch narrows the base row to where none are + dims: [techs, carriers] + where: count(flow_cap, over=nodes) >= 1 AND flow_cap_min_systemwide AND count(purchased_units, over=nodes) >= 1 + expression: sum(flow_cap, over=nodes) >= flow_cap_min_systemwide * sum(purchased_units, over=nodes) + +assumptions: + distance_only_for_transmission_milp: + description: Calliope's `distance_only_for_transmission_milp` — only a link sets a per-distance purchase cost + holds: base_tech == 'transmission' OR NOT cost_purchase_per_distance + conflicting_flow_caps: + description: Calliope's `conflicting_flow_caps` — a technology sets a capacity per unit or a capacity range, not both + holds: NOT ((flow_cap_max OR flow_cap_min) AND flow_cap_per_unit) + unit_commitment_only_for_units: + description: Calliope's `unit_commitment_only_for_units` — integer dispatch needs integer units + holds: NOT integer_dispatch OR cap_method == integer + conflicting_storage_caps: + description: Calliope's `conflicting_storage_caps` — a technology sets a storage capacity per unit or a range, not both + holds: NOT ((storage_cap_max OR storage_cap_min) AND storage_cap_per_unit) + cap_method_one_of: + description: Calliope's `one_of` on `cap_method` + holds: cap_method == continuous OR cap_method == integer + where: cap_method diff --git a/examples/calliope/extensions/monthly_peak_flow_charge.yaml b/examples/calliope/extensions/monthly_peak_flow_charge.yaml new file mode 100644 index 00000000..8982b818 --- /dev/null +++ b/examples/calliope/extensions/monthly_peak_flow_charge.yaml @@ -0,0 +1,66 @@ +# SPDX-FileCopyrightText: mathspec Contributors +# +# SPDX-License-Identifier: MIT + +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + costs: + description: Calliope's `costs` — cost classes, such as monetary and CO2 + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + months: + description: Calliope's `months` — the months of the year + dtype: int + +relations: + lookup_month: + description: >- + `lookup_month` — the month a time step falls in. Calliope ships it as a + boolean table over time step and month, and builds its row over both; + as a relation the row is one per time step + key: timesteps + values: months + +parameters: + monthly_peak_mode: + description: "`monthly_peak_mode` — whether a technology's peak outflow in a month is priced" + dims: [nodes, techs, carriers] + dtype: bool + cost_month_peak: + description: "`cost_month_peak` — the cost of one unit of peak outflow in a month" + dims: [nodes, techs, costs] + +variables: + flow_peak_month: + description: "`flow_peak_month` — a technology's peak outflow in a month" + dims: [nodes, techs, carriers, months] + where: carrier_out AND monthly_peak_mode + bounds: { lower: 0, upper: flow_cap_max } + absence: zero + +expressions: + cost_month_peak_charge: + description: "`sum(cost_month_peak * flow_peak_month, over=[carriers, months])` — the term Calliope writes into `cost_operation_fixed`, by restating it whole" + expression: sum(sum(cost_month_peak * flow_peak_month, over=carriers), over=months) + +given: + parameters: + carrier_out: { dims: [nodes, techs, carriers], dtype: bool } + flow_cap_max: { dims: [nodes, techs] } + variables: + flow_out: { dims: [nodes, techs, carriers, timesteps] } + expressions: + cost_operation_fixed: { dims: [nodes, techs, costs], term: cost_month_peak_charge } + +constraints: + set_peak_month_flow: + description: "`set_peak_month_flow` — the peak outflow in a month is at least the outflow in each of its time steps" + dims: [nodes, techs, carriers, timesteps] + where: at(flow_peak_month, by=lookup_month, over=months, into=timesteps) + expression: flow_out <= at(flow_peak_month, by=lookup_month, over=months, into=timesteps) diff --git a/examples/calliope/extensions/net_import_share.yaml b/examples/calliope/extensions/net_import_share.yaml new file mode 100644 index 00000000..dc2be0ce --- /dev/null +++ b/examples/calliope/extensions/net_import_share.yaml @@ -0,0 +1,97 @@ +# SPDX-FileCopyrightText: mathspec Contributors +# +# SPDX-License-Identifier: MIT + +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + +parameters: + net_import_share: + description: >- + `net_import_share` — the share of a node's flows that imports may make + up. Calliope's default is 1, and data prep fills it. Calliope reads it + per node and in a row over a group of nodes; a parameter here has one + shape, so it is one number + dims: [] + +expressions: + flow_out_transmission_techs: + description: "`flow_out_transmission_techs` — the outflow of transmission technologies, that is, imports" + dims: [nodes, techs, carriers, timesteps] + cases: + transmission: + when: carrier_out AND base_tech == 'transmission' + expression: flow_out + otherwise: 0 + electricity_imports: + description: "`flow_out_transmission_techs[carriers=electricity]`" + dims: [nodes, techs, carriers, timesteps] + cases: + electricity: + when: carriers == electricity + expression: flow_out_transmission_techs + otherwise: 0 + electricity_balance: + description: "`$total_energy_balance` — the outflow of electricity at a node, less its inflow" + dims: [nodes, techs, carriers, timesteps] + cases: + electricity: + when: carriers == electricity + expression: flow_out - flow_in + otherwise: 0 + node_group_heat_imports: + description: "`flow_out_transmission_techs[nodes=$node_group, carriers=$carrier]` — heat imports at nodes `a` and `c`" + dims: [nodes, techs, carriers, timesteps] + cases: + group: + when: (nodes == 'a' OR nodes == 'c') AND carriers == heat + expression: flow_out_transmission_techs + otherwise: 0 + node_group_heat_balance: + description: "`$total_energy_balance` of the node group — the outflow of heat at nodes `a` and `c`, less its inflow" + dims: [nodes, techs, carriers, timesteps] + cases: + group: + when: (nodes == 'a' OR nodes == 'c') AND carriers == heat + expression: flow_out - flow_in + otherwise: 0 + +given: + parameters: + base_tech: { dims: [techs], dtype: str } + carrier_out: { dims: [nodes, techs, carriers], dtype: bool } + variables: + flow_out: { dims: [nodes, techs, carriers, timesteps] } + flow_in: { dims: [nodes, techs, carriers, timesteps] } + +constraints: + net_import_share_max: + description: >- + `net_import_share_max` — electricity imports at a node are at most + their share of its electricity balance in each time step. Calliope's + `where: any(flow_out_transmission_techs, over=techs)` reads as a link + at the node putting out any carrier + dims: [nodes, timesteps] + where: count(count(carrier_out, over=carriers) >= 1 AND base_tech == 'transmission', over=techs) >= 1 + expression: >- + net_import_share * sum(sum(electricity_imports, over=techs), over=carriers) + <= sum(sum(electricity_balance, over=techs), over=carriers) + net_annual_import_share_max: + description: "`net_annual_import_share_max` — electricity imports at a node are at most their share of its electricity balance over the year" + dims: [nodes] + where: count(count(carrier_out, over=carriers) >= 1 AND base_tech == 'transmission', over=techs) >= 1 + expression: >- + net_import_share * sum(sum(sum(electricity_imports, over=techs), over=carriers), over=timesteps) + <= sum(sum(sum(electricity_balance, over=techs), over=carriers), over=timesteps) + net_annual_import_share_max_node_group: + description: "`net_annual_import_share_max_node_group` — heat imports at nodes `a` and `c` are at most their share of the group's heat balance over the year" + dims: [] + expression: net_import_share * sum(node_group_heat_imports) <= sum(node_group_heat_balance) diff --git a/examples/calliope/extensions/piecewise_linear_costs.yaml b/examples/calliope/extensions/piecewise_linear_costs.yaml new file mode 100644 index 00000000..ae1dd86d --- /dev/null +++ b/examples/calliope/extensions/piecewise_linear_costs.yaml @@ -0,0 +1,55 @@ +# SPDX-FileCopyrightText: mathspec Contributors +# +# SPDX-License-Identifier: MIT + +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + costs: + description: Calliope's `costs` — cost classes, such as monetary and CO2 + pieces: + description: Calliope's `pieces` — the lines a piecewise curve is the upper envelope of + dtype: int + +parameters: + cost_flow_cap_piecewise_slopes: + description: "`cost_flow_cap_piecewise_slopes` — the slope of each line of a convex investment cost curve" + dims: [nodes, techs, costs, pieces] + cost_flow_cap_piecewise_intercept: + description: "`cost_flow_cap_piecewise_intercept` — the intercept of each line of a convex investment cost curve" + dims: [nodes, techs, costs, pieces] + +variables: + piecewise_cost_investment: + description: "`piecewise_cost_investment` — an investment cost that grows faster the more capacity is built" + dims: [nodes, techs, costs] + where: >- + count(cost_flow_cap_piecewise_slopes, over=pieces) >= 1 + AND count(cost_flow_cap_piecewise_intercept, over=pieces) >= 1 AND purchased_units + bounds: { lower: 0 } + absence: zero + +given: + variables: + flow_cap: { dims: [nodes, techs, carriers] } + purchased_units: { dims: [nodes, techs] } + expressions: + cost_investment: { dims: [nodes, techs, costs], term: piecewise_cost_investment_term } + +expressions: + piecewise_cost_investment_term: + description: "`+ piecewise_cost_investment` — the term Calliope writes into `cost_investment`, by restating it whole" + expression: piecewise_cost_investment + +constraints: + piecewise_costs: + description: "`piecewise_costs` — the investment cost is at least every line of the curve, so at least the curve" + dims: [nodes, techs, costs, pieces] + where: piecewise_cost_investment + expression: >- + piecewise_cost_investment >= sum(cost_flow_cap_piecewise_slopes * flow_cap, over=carriers) + + cost_flow_cap_piecewise_intercept * purchased_units diff --git a/examples/calliope/extensions/piecewise_linear_efficiency.yaml b/examples/calliope/extensions/piecewise_linear_efficiency.yaml new file mode 100644 index 00000000..1c8089f7 --- /dev/null +++ b/examples/calliope/extensions/piecewise_linear_efficiency.yaml @@ -0,0 +1,43 @@ +# SPDX-FileCopyrightText: mathspec Contributors +# +# SPDX-License-Identifier: MIT + +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + pieces: + description: Calliope's `pieces` — the lines a piecewise curve is the upper envelope of + dtype: int + +parameters: + flow_eff_piecewise_slopes: + description: "`flow_eff_piecewise_slopes` — the slope of each line of a convex inflow curve" + dims: [nodes, techs, pieces] + flow_eff_piecewise_intercept: + description: "`flow_eff_piecewise_intercept` — the intercept of each line of a convex inflow curve" + dims: [nodes, techs, pieces] + +given: + variables: + flow_out: { dims: [nodes, techs, carriers, timesteps] } + flow_in: { dims: [nodes, techs, carriers, timesteps] } + available_flow_cap: { dims: [nodes, techs, carriers, timesteps] } + +constraints: + piecewise_efficiency: + description: >- + `piecewise_efficiency` — inflow is at least every line of the curve of + outflow, so at least the curve. Calliope's `where: available_flow_cap` + over a technology reads as the technology having it for some carrier + dims: [nodes, techs, timesteps, pieces] + where: flow_eff_piecewise_slopes AND flow_eff_piecewise_intercept AND count(available_flow_cap, over=carriers) >= 1 + expression: >- + sum(flow_in, over=carriers) >= flow_eff_piecewise_slopes * sum(flow_out, over=carriers) + + flow_eff_piecewise_intercept * sum(available_flow_cap, over=carriers) diff --git a/examples/calliope/extensions/share_all_timesteps.yaml b/examples/calliope/extensions/share_all_timesteps.yaml new file mode 100644 index 00000000..0f597358 --- /dev/null +++ b/examples/calliope/extensions/share_all_timesteps.yaml @@ -0,0 +1,77 @@ +# SPDX-FileCopyrightText: mathspec Contributors +# +# SPDX-License-Identifier: MIT + +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + +relations: + demand_share_tech: + description: >- + `demand_share_tech` — the demand technology whose inflow a technology + meets a share of. Calliope slices `flow_in` by it; a read through the + relation is that slice + key: techs + values: { demand: techs } + supply_share_carrier: + description: >- + `supply_share_carrier` — the carrier a technology's share of outflow is + counted in. Calliope slices `flow_out` by it, which is a test of the + pair + key: [techs, carriers] + +parameters: + demand_share_equals: + description: "`demand_share_equals` — the share of a demand technology's inflow a technology meets; given only where set" + dims: [nodes, techs] + supply_share_equals: + description: "`supply_share_equals` — the share of a node's outflow of a carrier a technology puts out; given only where set" + dims: [nodes, techs] + +expressions: + supply_share_flow_out: + description: "`flow_out[carriers=$carrier]` — a technology's outflow of its share carrier" + dims: [nodes, techs, carriers, timesteps] + cases: + share_carrier: + when: supply_share_carrier + expression: flow_out + otherwise: 0 + supply_share_all_flow_out: + description: "`sum(flow_out[carriers=$carrier], over=techs)` — every technology's outflow of a technology's share carrier" + dims: [nodes, techs, carriers, timesteps] + cases: + share_carrier: + when: supply_share_carrier + expression: sum(flow_out, over=techs) + otherwise: 0 + +given: + variables: + flow_out: { dims: [nodes, techs, carriers, timesteps] } + flow_in: { dims: [nodes, techs, carriers, timesteps] } + +constraints: + demand_share_equals_per_tech: + description: "`demand_share_equals_per_tech` — a technology puts out its share of a demand technology's inflow over the whole time" + dims: [nodes, techs] + where: demand_share_equals + expression: >- + sum(sum(flow_out, over=timesteps), over=carriers) + == sum(sum(at(flow_in, by=demand_share_tech, over=demand, into=techs), over=timesteps), over=carriers) + * demand_share_equals + supply_share_equals_per_tech: + description: "`supply_share_equals_per_tech` — a technology puts out its share of a node's outflow of a carrier over the whole time" + dims: [nodes, techs] + where: supply_share_equals + expression: >- + sum(sum(supply_share_flow_out, over=carriers), over=timesteps) + == sum(sum(supply_share_all_flow_out, over=carriers), over=timesteps) * supply_share_equals diff --git a/examples/calliope/extensions/share_per_timestep.yaml b/examples/calliope/extensions/share_per_timestep.yaml new file mode 100644 index 00000000..1e72525e --- /dev/null +++ b/examples/calliope/extensions/share_per_timestep.yaml @@ -0,0 +1,81 @@ +# SPDX-FileCopyrightText: mathspec Contributors +# +# SPDX-License-Identifier: MIT + +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + +relations: + demand_share_tech: + description: >- + `demand_share_tech` — the demand technology whose inflow a technology + meets a share of. Calliope slices `flow_in` by it; a read through the + relation is that slice + key: techs + values: { demand: techs } + supply_share_carrier: + description: >- + `supply_share_carrier` — the carrier a technology's share of outflow is + counted in. Calliope slices `flow_out` by it, which is a test of the + pair + key: [techs, carriers] + +parameters: + demand_share_per_timestep_equals: + description: "`demand_share_per_timestep_equals` — the share of a demand technology's inflow a technology meets in each time step; given only where set" + dims: [nodes, techs, timesteps] + supply_share_per_timestep_equals: + description: "`supply_share_per_timestep_equals` — the share of a node's outflow of a carrier a technology puts out in each time step; given only where set" + dims: [nodes, techs, timesteps] + +expressions: + supply_share_timestep_flow_out: + description: "`flow_out[carriers=$carrier]` — a technology's outflow of its share carrier" + dims: [nodes, techs, carriers, timesteps] + cases: + share_carrier: + when: supply_share_carrier + expression: flow_out + otherwise: 0 + supply_share_timestep_all_flow_out: + description: "`sum(flow_out[carriers=$carrier], over=techs)` — every technology's outflow of a technology's share carrier" + dims: [nodes, techs, carriers, timesteps] + cases: + share_carrier: + when: supply_share_carrier + expression: sum(flow_out, over=techs) + otherwise: 0 + +given: + variables: + flow_out: { dims: [nodes, techs, carriers, timesteps] } + flow_in: { dims: [nodes, techs, carriers, timesteps] } + +constraints: + demand_share_per_timestep_equals_per_tech: + description: "`demand_share_per_timestep_equals_per_tech` — a technology puts out its share of a demand technology's inflow in each time step" + dims: [nodes, techs, timesteps] + where: demand_share_per_timestep_equals + expression: >- + sum(flow_out, over=carriers) + == sum(at(flow_in, by=demand_share_tech, over=demand, into=techs), over=carriers) + * demand_share_per_timestep_equals + supply_share_per_timestep_equals_per_tech: + description: >- + `supply_share_per_timestep_equals_per_tech` — a technology puts out its + share of a node's outflow of a carrier in each time step. Calliope's + row keeps the carrier dimension of the slice; the slice here is summed + over the one carrier it keeps + dims: [nodes, techs, timesteps] + where: supply_share_per_timestep_equals + expression: >- + sum(supply_share_timestep_flow_out, over=carriers) + == sum(supply_share_timestep_all_flow_out, over=carriers) * supply_share_per_timestep_equals diff --git a/examples/calliope/extensions/sos2_piecewise_linear_costs.yaml b/examples/calliope/extensions/sos2_piecewise_linear_costs.yaml new file mode 100644 index 00000000..0e7b5ec7 --- /dev/null +++ b/examples/calliope/extensions/sos2_piecewise_linear_costs.yaml @@ -0,0 +1,68 @@ +# SPDX-FileCopyrightText: mathspec Contributors +# +# SPDX-License-Identifier: MIT + +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + costs: + description: Calliope's `costs` — cost classes, such as monetary and CO2 + breakpoints: + description: Calliope's `breakpoints` — the corners of a piecewise-linear curve, in order + dtype: int + +parameters: + piecewise_cost_investment_x: + description: "`piecewise_cost_investment_x` — the flow capacity at each breakpoint" + dims: [techs, breakpoints] + piecewise_cost_investment_y: + description: "`piecewise_cost_investment_y` — the investment cost at each breakpoint" + dims: [techs, costs, breakpoints] + +variables: + piecewise_cost_investment: + description: "`piecewise_cost_investment` — an investment cost that grows more slowly the more capacity is built" + dims: [nodes, techs, carriers, costs] + where: count(piecewise_cost_investment_x, over=breakpoints) >= 1 AND count(piecewise_cost_investment_y, over=breakpoints) >= 1 + bounds: { lower: 0 } + absence: zero + piecewise_flow_cap: + description: >- + the flow capacity, where the technology has a cost curve. A + `piecewise:` block takes no `where:`, so its link rows would pin + `flow_cap` to the curve at every technology; a link over this copy is + built only where the copy is + dims: [nodes, techs, carriers, costs] + where: count(piecewise_cost_investment_x, over=breakpoints) >= 1 AND count(piecewise_cost_investment_y, over=breakpoints) >= 1 + +constraints: + piecewise_flow_cap_is_flow_cap: + description: the copy of the flow capacity the curve reads is the flow capacity + dims: [nodes, techs, carriers, costs] + where: piecewise_flow_cap + expression: piecewise_flow_cap == flow_cap + +piecewise: + sos2_piecewise_costs: + description: "`sos2_piecewise_costs` — the investment cost lies on the curve through the breakpoints, stated as an SOS2 set" + over: breakpoints + method: sos2 + points: piecewise_cost_investment_x + links: + - [piecewise_flow_cap, piecewise_cost_investment_x] + - [piecewise_cost_investment, piecewise_cost_investment_y] + +expressions: + cost_investment_piecewise: + description: "`sum(piecewise_cost_investment, over=carriers)` — the term Calliope writes into `cost_investment`" + expression: sum(piecewise_cost_investment, over=carriers) + +given: + variables: + flow_cap: { dims: [nodes, techs, carriers] } + expressions: + cost_investment: { dims: [nodes, techs, costs], term: cost_investment_piecewise } diff --git a/examples/calliope/extensions/uptime_downtime_limits.yaml b/examples/calliope/extensions/uptime_downtime_limits.yaml new file mode 100644 index 00000000..ddc3de39 --- /dev/null +++ b/examples/calliope/extensions/uptime_downtime_limits.yaml @@ -0,0 +1,69 @@ +# SPDX-FileCopyrightText: mathspec Contributors +# +# SPDX-License-Identifier: MIT + +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + +parameters: + capacity_factor_min: + description: "`capacity_factor_min` — the least capacity factor a technology reaches over the whole time" + dims: [nodes, techs] + capacity_factor_max: + description: "`capacity_factor_max` — the most capacity factor a technology reaches over the whole time; given only where set" + dims: [nodes, techs] + uptime_limit: + description: "`uptime_limit` — the most time steps a technology runs in, weighted; given only where set" + dims: [nodes, techs] + downtime_periods: + description: "`downtime_periods` — whether a technology is down for maintenance in a time step" + dims: [nodes, techs, timesteps] + dtype: bool + +expressions: + total_time: + description: "`$total_time` — the hours the modelled time steps stand for" + expression: sum(timestep_resolution * timestep_weights, over=timesteps) + +given: + parameters: + carrier_out: { dims: [nodes, techs, carriers], dtype: bool } + timestep_resolution: { dims: [timesteps] } + timestep_weights: { dims: [timesteps] } + variables: + flow_out: { dims: [nodes, techs, carriers, timesteps] } + flow_cap: { dims: [nodes, techs, carriers] } + operating_units: { dims: [nodes, techs, timesteps] } + +constraints: + annual_capacity_factor_min: + description: "`annual_capacity_factor_min` — a technology's outflow over the whole time is at least its least capacity factor" + dims: [nodes, techs, carriers] + where: carrier_out AND capacity_factor_min + expression: sum(flow_out * timestep_weights, over=timesteps) >= flow_cap * capacity_factor_min * total_time + annual_capacity_factor_max: + description: "`annual_capacity_factor_max` — a technology's outflow over the whole time is at most its most capacity factor" + dims: [nodes, techs, carriers] + where: carrier_out AND capacity_factor_max + expression: sum(flow_out * timestep_weights, over=timesteps) <= flow_cap * capacity_factor_max * total_time + downtime_period: + description: "`downtime_period` — a technology puts out nothing in a time step it is down" + dims: [nodes, techs, timesteps] + where: downtime_periods + expression: sum(flow_out, over=carriers) == 0 + downtime_period_decision: + description: >- + `downtime_period_decision` — a unit-bought technology runs in at most + its limit of time steps. Calliope's `where: operating_units` over a + technology reads as the technology running in whole units at all + dims: [nodes, techs] + where: count(operating_units, over=timesteps) >= 1 AND uptime_limit + expression: sum(operating_units * timestep_weights, over=timesteps) <= uptime_limit diff --git a/examples/calliope/extensions/urban_scale_chp.yaml b/examples/calliope/extensions/urban_scale_chp.yaml new file mode 100644 index 00000000..7fe02187 --- /dev/null +++ b/examples/calliope/extensions/urban_scale_chp.yaml @@ -0,0 +1,70 @@ +# SPDX-FileCopyrightText: mathspec Contributors +# +# SPDX-License-Identifier: MIT + +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + +parameters: + heat_to_power_ratio: + description: "`heat_to_power_ratio` — the heat a combined heat and power plant puts out per unit of electricity. Calliope's default is 1, and data prep fills it" + dims: [nodes, techs] + +expressions: + urban_electricity_out: + description: "`flow_out[carriers=electricity]`" + dims: [nodes, techs, carriers, timesteps] + cases: + electricity: + when: carriers == electricity + expression: flow_out + otherwise: 0 + urban_heat_out: + description: "`flow_out[carriers=heat]`" + dims: [nodes, techs, carriers, timesteps] + cases: + heat: + when: carriers == heat + expression: flow_out + otherwise: 0 + urban_electricity_out_inc_eff: + description: "`flow_out_inc_eff[carriers=electricity]`" + dims: [nodes, techs, carriers, timesteps] + cases: + electricity: + when: carriers == electricity + expression: flow_out_inc_eff + otherwise: 0 + +given: + parameters: + base_tech: { dims: [techs], dtype: str } + include_storage: { dims: [nodes, techs], dtype: bool } + variables: + flow_out: { dims: [nodes, techs, carriers, timesteps] } + expressions: + flow_out_inc_eff: { dims: [nodes, techs, carriers, timesteps] } + flow_in_inc_eff: { dims: [nodes, techs, carriers, timesteps] } + +constraints: + link_chp_outputs: + description: "`link_chp_outputs` — the technology `chp` puts out heat in a fixed ratio to its electricity" + dims: [nodes, techs, timesteps] + where: techs == chp + expression: sum(urban_electricity_out, over=carriers) * heat_to_power_ratio == sum(urban_heat_out, over=carriers) + balance_conversion_chp: + description: >- + `balance_conversion` for the technology `chp` — it puts out, before + losses, as much electricity as it takes in fuel after them. The patch + keeps the base row off it + dims: [nodes, techs, timesteps] + where: base_tech == 'conversion' AND NOT include_storage AND techs == chp + expression: sum(urban_electricity_out_inc_eff, over=carriers) == sum(flow_in_inc_eff, over=carriers) diff --git a/examples/calliope/feasibility.yaml b/examples/calliope/feasibility.yaml new file mode 100644 index 00000000..211579df --- /dev/null +++ b/examples/calliope/feasibility.yaml @@ -0,0 +1,50 @@ +# SPDX-FileCopyrightText: mathspec Contributors +# +# SPDX-License-Identifier: MIT + +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + +variables: + unmet_demand: + description: >- + `unmet_demand` — a source of any carrier at any node, at a high price, + so a model that cannot meet its demand still solves. Calliope builds + it under `config.ensure_feasibility`; here it is this file + dims: [nodes, carriers, timesteps] + where: count(carrier_in, over=techs) >= 1 OR count(carrier_out, over=techs) >= 1 + bounds: { lower: 0 } + absence: zero + unused_supply: + description: "`unused_supply` — a sink of any carrier at any node, at a high price, the counterpart of `unmet_demand`" + dims: [nodes, carriers, timesteps] + where: count(carrier_in, over=techs) >= 1 OR count(carrier_out, over=techs) >= 1 + bounds: { upper: 0 } + absence: zero + +expressions: + feasibility_carrier_flow: unmet_demand + unused_supply + unmet_demand_penalty: + description: "`$unmet_demand` of `min_cost_optimisation` — what unmet demand and unused supply cost" + expression: sum(sum(sum(unmet_demand - unused_supply, over=carriers), over=nodes) * timestep_weights) * bigM + unmet_sum: + description: "`unmet_sum` — net unmet demand; reported" + expression: unmet_demand + unused_supply + +given: + parameters: + carrier_in: { dims: [nodes, techs, carriers], dtype: bool } + carrier_out: { dims: [nodes, techs, carriers], dtype: bool } + timestep_weights: { dims: [timesteps] } + bigM: { dims: [] } + expressions: + carrier_flow: { dims: [nodes, carriers, timesteps], term: feasibility_carrier_flow } + penalty: { dims: [], term: unmet_demand_penalty } diff --git a/examples/calliope/flows.yaml b/examples/calliope/flows.yaml new file mode 100644 index 00000000..a8b89262 --- /dev/null +++ b/examples/calliope/flows.yaml @@ -0,0 +1,259 @@ +# SPDX-FileCopyrightText: mathspec Contributors +# +# SPDX-License-Identifier: MIT + +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + costs: + description: Calliope's `costs` — cost classes, such as monetary and CO2 + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + +relations: + link_from: + description: >- + `link_from` — the node a transmission technology links from. Calliope + reads it as `map_dim(nodes, link_from)`, a mask over technology and + node, which is the relation's own row test + key: [techs, nodes] + link_to: + description: >- + `link_to` — the node a transmission technology links to, read as + `link_from` is + key: [techs, nodes] + +parameters: + base_tech: + description: >- + `base_tech` — the abstract class a technology derives from: demand, + supply, conversion, storage or transmission + dims: [techs] + dtype: str + carrier_in: + description: "`carrier_in` — whether a technology consumes a carrier at a node" + dims: [nodes, techs, carriers] + dtype: bool + carrier_out: + description: "`carrier_out` — whether a technology produces a carrier at a node" + dims: [nodes, techs, carriers] + dtype: bool + include_storage: + description: >- + `include_storage` — whether a technology that is not a storage one + carries a store all the same + dims: [nodes, techs] + dtype: bool + one_way: + description: "`one_way` — whether a transmission technology carries flow only from `link_from` to `link_to`" + dims: [techs] + dtype: bool + flow_cap_min: + description: >- + `flow_cap_min` — least flow capacity. Calliope's default is 0; a bound + has a row wherever the variable has one, so data prep fills it + dims: [nodes, techs] + flow_cap_max: + description: >- + `flow_cap_max` — most flow capacity. Calliope's default is `.inf`, + which data prep fills, and a `where` reads as not given + dims: [nodes, techs] + flow_cap_min_systemwide: + description: "`flow_cap_min_systemwide` — least flow capacity of a technology over every node; given only where set" + dims: [techs, carriers] + flow_cap_max_systemwide: + description: "`flow_cap_max_systemwide` — most flow capacity of a technology over every node; given only where set" + dims: [techs, carriers] + flow_out_min_relative: + description: "`flow_out_min_relative` — least outflow, per unit of flow capacity; given only where set" + dims: [nodes, techs, timesteps] + flow_out_eff: + description: "`flow_out_eff` — the share of flow that leaves a technology as outflow. Calliope's default is 1, and data prep fills it" + dims: [nodes, techs, carriers, timesteps] + flow_in_eff: + description: "`flow_in_eff` — the share of inflow that enters a technology. Calliope's default is 1, and data prep fills it" + dims: [nodes, techs, carriers, timesteps] + flow_out_parasitic_eff: + description: "`flow_out_parasitic_eff` — what is left after the plant's own use. Calliope's default is 1, and data prep fills it" + dims: [nodes, techs, carriers, timesteps] + flow_out_eff_per_distance: + description: "`flow_out_eff_per_distance` — the outflow efficiency of a link per unit of distance. Calliope's default is 1, and data prep fills it" + dims: [nodes, techs, carriers, timesteps] + flow_in_eff_per_distance: + description: "`flow_in_eff_per_distance` — the inflow efficiency of a link per unit of distance. Calliope's default is 1, and data prep fills it" + dims: [nodes, techs, carriers, timesteps] + distance: + description: >- + `distance` — the length of a transmission link. Calliope's default is + 1, which data prep fills, where it does not derive one from the + coordinates of the nodes + dims: [techs] + flow_ramping: + description: "`flow_ramping` — the most flow may change in an hour, per unit of flow capacity; given only where set" + dims: [nodes, techs] + cost_flow_cap: + description: "`cost_flow_cap` — the cost of one unit of flow capacity" + dims: [nodes, techs, costs] + cost_flow_cap_per_distance: + description: "`cost_flow_cap_per_distance` — the cost of one unit of flow capacity per unit of link distance" + dims: [nodes, techs, costs] + cost_flow_out: + description: "`cost_flow_out` — the cost of one unit of outflow" + dims: [nodes, techs, costs, timesteps] + cost_flow_in: + description: "`cost_flow_in` — the cost of one unit of inflow" + dims: [nodes, techs, costs, timesteps] + cost_om_annual: + description: "`cost_om_annual` — the annual cost of one unit of flow capacity" + dims: [nodes, techs, costs] + +variables: + flow_cap: + description: "`flow_cap` — the flow capacity of a technology, its nominal or nameplate capacity" + dims: [nodes, techs, carriers] + where: carrier_in OR carrier_out + bounds: { lower: flow_cap_min, upper: flow_cap_max } + absence: zero + flow_out: + description: >- + `flow_out` — the outflow of a technology in a time step. A one-way link + has none at the node it links from + dims: [nodes, techs, carriers, timesteps] + where: carrier_out AND NOT (one_way AND link_from) + bounds: { lower: 0 } + absence: zero + flow_in: + description: >- + `flow_in` — the inflow to a technology in a time step. A one-way link + has none at the node it links to + dims: [nodes, techs, carriers, timesteps] + where: carrier_in AND NOT (one_way AND link_to) + bounds: { lower: 0 } + absence: zero + +expressions: + flow_out_inc_eff: + description: "`flow_out_inc_eff` — outflow before the losses on the way out" + dims: [nodes, techs, carriers, timesteps] + cases: + transmission: + when: base_tech == 'transmission' + expression: flow_out / (flow_out_eff * flow_out_parasitic_eff * flow_out_eff_per_distance ** distance) + otherwise: flow_out / (flow_out_eff * flow_out_parasitic_eff) + flow_in_inc_eff: + description: "`flow_in_inc_eff` — inflow after the losses on the way in" + dims: [nodes, techs, carriers, timesteps] + cases: + transmission: + when: base_tech == 'transmission' + expression: flow_in * flow_in_eff * flow_in_eff_per_distance ** distance + otherwise: flow_in * flow_in_eff + ramping_flow: + description: >- + `$flow` of `ramping_up` and `ramping_down` — the flow a ramping limit + holds, per hour: outflow, inflow, or their difference where a + technology has both + dims: [nodes, techs, carriers, timesteps] + cases: + out: + when: carrier_out AND NOT carrier_in + expression: flow_out / timestep_resolution + in: + when: carrier_in AND NOT carrier_out + expression: flow_in / timestep_resolution + otherwise: (flow_out - flow_in) / timestep_resolution + cost_flow_cap_sum: + description: >- + `$cost_sum` of `cost_investment_flow_cap` — what one unit of flow + capacity costs; a link's cost is split between its two ends + dims: [nodes, techs, costs] + cases: + transmission: + when: base_tech == 'transmission' + expression: (cost_flow_cap + cost_flow_cap_per_distance * distance) * 0.5 + otherwise: cost_flow_cap + cost_investment_flow_cap: + description: "`cost_investment_flow_cap` — the investment cost of flow capacity" + expression: cost_flow_cap_sum * flow_cap + flows_carrier_flow: sum(flow_out, over=techs) - sum(flow_in, over=techs) + flows_cost_investment: sum(cost_investment_flow_cap, over=carriers) + flows_cost_operation_variable: >- + timestep_weights * (sum(cost_flow_out * flow_out, over=carriers) + sum(cost_flow_in * flow_in, over=carriers)) + flows_cost_operation_fixed: annualisation_weight * sum(cost_om_annual * flow_cap, over=carriers) + +given: + parameters: + timestep_resolution: { dims: [timesteps] } + timestep_weights: { dims: [timesteps] } + expressions: + annualisation_weight: + description: the share of a year the modelled time steps stand for + dims: [] + carrier_flow: { dims: [nodes, carriers, timesteps], term: flows_carrier_flow } + cost_investment: { dims: [nodes, techs, costs], term: flows_cost_investment } + cost_operation_variable: { dims: [nodes, techs, costs, timesteps], term: flows_cost_operation_variable } + cost_operation_fixed: { dims: [nodes, techs, costs], term: flows_cost_operation_fixed } + +constraints: + flow_out_max: + description: "`flow_out_max` — outflow is at most the flow capacity over the time step, less the plant's own use" + dims: [nodes, techs, carriers, timesteps] + where: carrier_out + expression: flow_out <= flow_cap * timestep_resolution * flow_out_parasitic_eff + flow_out_min: + description: "`flow_out_min` — outflow is at least its least share of the flow capacity" + dims: [nodes, techs, carriers, timesteps] + where: flow_cap AND flow_out_min_relative + expression: flow_out >= flow_cap * timestep_resolution * flow_out_min_relative + flow_in_max: + description: "`flow_in_max` — inflow is at most the flow capacity over the time step" + dims: [nodes, techs, carriers, timesteps] + where: carrier_in + expression: flow_in <= flow_cap * timestep_resolution + flow_capacity_systemwide_max: + description: "`flow_capacity_systemwide_max` — the flow capacity of a technology over every node is at most its system-wide maximum" + dims: [techs, carriers] + where: count(flow_cap, over=nodes) >= 1 AND flow_cap_max_systemwide + expression: sum(flow_cap, over=nodes) <= flow_cap_max_systemwide + flow_capacity_systemwide_min: + description: "`flow_capacity_systemwide_min` — the flow capacity of a technology over every node is at least its system-wide minimum" + dims: [techs, carriers] + where: count(flow_cap, over=nodes) >= 1 AND flow_cap_min_systemwide + expression: sum(flow_cap, over=nodes) >= flow_cap_min_systemwide + ramping_up: + description: "`ramping_up` — flow rises from one time step to the next by at most its ramping share of the flow capacity" + dims: [nodes, techs, carriers, timesteps] + where: (carrier_in OR carrier_out) AND flow_ramping AND position(timesteps) > 0 + expression: ramping_flow - shift(ramping_flow, along=timesteps, offset=1) <= flow_ramping * flow_cap + ramping_down: + description: "`ramping_down` — flow falls from one time step to the next by at most its ramping share of the flow capacity" + dims: [nodes, techs, carriers, timesteps] + where: (carrier_in OR carrier_out) AND flow_ramping AND position(timesteps) > 0 + expression: -1 * flow_ramping * flow_cap <= ramping_flow - shift(ramping_flow, along=timesteps, offset=1) + +assumptions: + must_have_base: + description: Calliope's `must_have_base` — every technology derives from an abstract class + holds: base_tech + base_tech_one_of: + description: Calliope's `one_of` on `base_tech` + holds: >- + base_tech == 'demand' OR base_tech == 'supply' OR base_tech == 'conversion' + OR base_tech == 'storage' OR base_tech == 'transmission' + distance_only_for_transmission: + description: >- + Calliope's `distance_only_for_transmission` — only a link sets a + distance or a per-distance value. Data prep fills the defaults, so a + technology that is not a link keeps them + holds: >- + distance == 1 AND flow_in_eff_per_distance == 1 + AND flow_out_eff_per_distance == 1 AND NOT cost_flow_cap_per_distance + where: NOT base_tech == 'transmission' + unbounded_flow_cap_cost: + description: Calliope's `unbounded_flow_cap_cost` — a negative flow capacity cost needs a finite maximum + holds: NOT cost_flow_cap < 0 OR flow_cap_max diff --git a/examples/calliope/reporting.yaml b/examples/calliope/reporting.yaml new file mode 100644 index 00000000..7efc96a7 --- /dev/null +++ b/examples/calliope/reporting.yaml @@ -0,0 +1,48 @@ +# SPDX-FileCopyrightText: mathspec Contributors +# +# SPDX-License-Identifier: MIT + +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + costs: + description: Calliope's `costs` — cost classes, such as monetary and CO2 + +given: + parameters: + timestep_resolution: { dims: [timesteps] } + timestep_weights: { dims: [timesteps] } + variables: + flow_cap: { dims: [nodes, techs, carriers] } + flow_out: { dims: [nodes, techs, carriers, timesteps] } + flow_export: { dims: [nodes, techs, carriers, timesteps] } + expressions: + cost: { dims: [nodes, techs, costs] } + +expressions: + capacity_factor: + description: "`capacity_factor` — the share of its flow capacity a technology puts out in a time step" + expression: flow_out / (flow_cap * timestep_resolution) + systemwide_capacity_factor: + description: "`systemwide_capacity_factor` — the share of its flow capacity a technology puts out over every node and time step" + expression: >- + sum(sum(flow_out * timestep_weights, over=nodes), over=timesteps) + / (sum(flow_cap, over=nodes) * sum(timestep_resolution * timestep_weights, over=timesteps)) + total_generation: + description: >- + `total_generation` — outflow over every node and time step. Calliope + weights only the export, as written here + expression: sum(sum(flow_out + flow_export * timestep_weights, over=nodes), over=timesteps) + systemwide_levelised_cost: + description: "`systemwide_levelised_cost` — a technology's cost per unit of what it generates, over every node" + expression: sum(cost, over=nodes) / total_generation + total_levelised_cost: + description: "`total_levelised_cost` — the system's cost per unit of a carrier generated" + expression: sum(sum(cost, over=nodes), over=techs) / sum(total_generation, over=techs) diff --git a/examples/calliope/settings.yaml b/examples/calliope/settings.yaml new file mode 100644 index 00000000..5530070d --- /dev/null +++ b/examples/calliope/settings.yaml @@ -0,0 +1,55 @@ +# SPDX-FileCopyrightText: mathspec Contributors +# +# SPDX-License-Identifier: MIT + +dimensions: + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + costs: + description: Calliope's `costs` — cost classes, such as monetary and CO2 + +parameters: + timestep_resolution: + description: >- + `timestep_resolution` — hours a time step lasts. Calliope's default is + 1, and data prep fills it + dims: [timesteps] + timestep_weights: + description: >- + `timestep_weights` — how many times a time step counts, as after + clustering. Calliope's default is 1, and data prep fills it + dims: [timesteps] + objective_cost_weights: + description: >- + `objective_cost_weights` — what one unit of a cost class weighs in the + objective. Calliope's default is 1, and data prep fills it + dims: [costs] + bigM: + description: >- + `bigM` — a number larger than any decision can take. Calliope's + default is 1e6, and data prep fills it + dims: [] + +expressions: + system_cost: + description: >- + the weighted cost of the system, over every cost class — Calliope's + `min_cost_optimisation` less its unmet-demand penalty. The cost file and + every file that prices something outside a technology add to it + dims: [] + empty: true + penalty: + description: >- + what the objective adds to the system cost to keep a model feasible — + Calliope's `$unmet_demand` sub-expression. It is zero, and a file that + keeps a model feasible adds to it + dims: [] + expression: "0" + +objective: + description: >- + `min_cost_optimisation` — the weighted cost of installing and operating + every technology, plus the penalty on unmet demand + sense: minimize + expression: system_cost + penalty diff --git a/examples/calliope/storage.yaml b/examples/calliope/storage.yaml new file mode 100644 index 00000000..bbc93ced --- /dev/null +++ b/examples/calliope/storage.yaml @@ -0,0 +1,167 @@ +# SPDX-FileCopyrightText: mathspec Contributors +# +# SPDX-License-Identifier: MIT + +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + costs: + description: Calliope's `costs` — cost classes, such as monetary and CO2 + +relations: + lookup_cluster_last_timestep: + description: >- + `lookup_cluster_last_timestep` — the last time step of the cluster a + time step stands for, at the first time step of each clustered day + key: timesteps + values: { last: timesteps } + +parameters: + storage_cap_min: + description: "`storage_cap_min` — least storage capacity. Calliope's default is 0, and data prep fills it" + dims: [nodes, techs] + storage_cap_max: + description: "`storage_cap_max` — most storage capacity. Calliope's default is `.inf`, and data prep fills it" + dims: [nodes, techs] + storage_discharge_depth: + description: "`storage_discharge_depth` — the least a store holds, as a share of its capacity" + dims: [nodes, techs, timesteps] + storage_initial: + description: "`storage_initial` — what a store holds at the start, as a share of its capacity; given only where set" + dims: [nodes, techs] + storage_retention: + description: >- + `1 - storage_loss` — the share of what a store holds that it keeps + for an hour, data prep. mathspec refuses a sum as the base of `**`, + over parameters too + dims: [nodes, techs, timesteps] + cyclic_storage: + description: >- + `cyclic_storage` — whether a store ends where it starts. Calliope's + default is true, and data prep fills it + dims: [nodes, techs] + dtype: bool + cluster_first_timestep: + description: "`cluster_first_timestep` — whether a time step is the first of its clustered day" + dims: [timesteps] + dtype: bool + flow_cap_per_storage_cap_min: + description: "`flow_cap_per_storage_cap_min` — least flow capacity per unit of storage capacity; given only where set" + dims: [nodes, techs] + flow_cap_per_storage_cap_max: + description: "`flow_cap_per_storage_cap_max` — most flow capacity per unit of storage capacity; given only where set" + dims: [nodes, techs] + cost_storage_cap: + description: "`cost_storage_cap` — the cost of one unit of storage capacity" + dims: [nodes, techs, costs] + +variables: + storage_cap: + description: "`storage_cap` — the most a technology can store" + dims: [nodes, techs] + where: include_storage OR base_tech == 'storage' + bounds: { lower: storage_cap_min, upper: storage_cap_max } + absence: zero + storage: + description: "`storage` — what a technology holds at the end of a time step" + dims: [nodes, techs, timesteps] + where: include_storage OR base_tech == 'storage' + bounds: { lower: 0 } + absence: zero + +expressions: + storage_previous_step: + description: >- + `$storage_previous_step` — what a store carries into a time step: + its initial fill at the first step of a store that is not cyclic, what + is left of the last step of its clustered day at the first step of a + cluster, and what is left of the step before everywhere else + dims: [nodes, techs, timesteps] + cases: + initial: + when: position(timesteps) == 0 AND NOT cyclic_storage + expression: storage_initial * storage_cap + cluster_start: + when: cluster_first_timestep AND NOT (position(timesteps) == 0 AND NOT cyclic_storage) + expression: >- + storage_retention ** at(timestep_resolution, by=lookup_cluster_last_timestep, over=last, into=timesteps) + * at(storage, by=lookup_cluster_last_timestep, over=last, into=timesteps) + otherwise: >- + storage_retention ** shift(timestep_resolution, along=timesteps, offset=1, edge='wrap') + * shift(storage, along=timesteps, offset=1, edge='wrap') + cost_investment_storage_cap: + description: "`cost_investment_storage_cap` — the investment cost of storage capacity" + expression: cost_storage_cap * storage_cap + +given: + parameters: + base_tech: { dims: [techs], dtype: str } + include_storage: { dims: [nodes, techs], dtype: bool } + timestep_resolution: { dims: [timesteps] } + variables: + flow_cap: { dims: [nodes, techs, carriers] } + expressions: + flow_out_inc_eff: { dims: [nodes, techs, carriers, timesteps] } + flow_in_inc_eff: { dims: [nodes, techs, carriers, timesteps] } + cost_investment: { dims: [nodes, techs, costs], term: cost_investment_storage_cap } + +constraints: + flow_capacity_per_storage_capacity_min: + description: "`flow_capacity_per_storage_capacity_min` — flow capacity is at least its least share of storage capacity" + dims: [nodes, techs, carriers] + where: flow_cap AND storage_cap AND flow_cap_per_storage_cap_min + expression: flow_cap >= storage_cap * flow_cap_per_storage_cap_min + flow_capacity_per_storage_capacity_max: + description: "`flow_capacity_per_storage_capacity_max` — flow capacity is at most its most share of storage capacity" + dims: [nodes, techs, carriers] + where: flow_cap AND storage_cap AND flow_cap_per_storage_cap_max + expression: flow_cap <= storage_cap * flow_cap_per_storage_cap_max + storage_max: + description: "`storage_max` — a store holds at most its capacity" + dims: [nodes, techs, timesteps] + where: storage + expression: storage <= storage_cap + storage_discharge_depth_limit: + description: "`storage_discharge_depth_limit` — a store holds at least its depth of discharge" + dims: [nodes, techs, timesteps] + where: storage AND storage_discharge_depth + expression: storage - storage_discharge_depth * storage_cap >= 0 + balance_storage: + description: >- + `balance_storage` — what a store holds at the end of a time step is + what it carried in, less what it put out before losses, plus what it + took in after them + dims: [nodes, techs, timesteps] + where: (include_storage OR base_tech == 'storage') AND NOT (base_tech == 'supply' OR base_tech == 'demand') + expression: >- + storage == storage_previous_step + - sum(flow_out_inc_eff, over=carriers) + sum(flow_in_inc_eff, over=carriers) + set_storage_initial: + description: >- + `set_storage_initial` — a cyclic store with an initial fill holds it + at the end, after the last step's loss. Calliope builds one row per + store and reads the last step; this builds that row at the last step + dims: [nodes, techs, timesteps] + where: position(timesteps) == -1 AND storage AND storage_initial AND cyclic_storage + expression: storage * storage_retention ** timestep_resolution == storage_initial * storage_cap + +assumptions: + unbounded_storage_cap_cost: + description: Calliope's `unbounded_storage_cap_cost` — a negative storage capacity cost needs a finite maximum + holds: NOT cost_storage_cap < 0 OR storage_cap_max + storage_initial_max: + description: Calliope's `storage_initial_max` — the initial fill is a share + holds: storage_initial >= 0 AND storage_initial <= 1 + where: storage_initial + cyclic_storage_needs_inter_cluster: + description: >- + Calliope's `cyclic_storage_needs_inter_cluster` — a cyclic store under + clustering needs the inter-cluster patch + holds: NOT (cyclic_storage AND lookup_cluster_last_timestep) diff --git a/examples/calliope/supply.yaml b/examples/calliope/supply.yaml new file mode 100644 index 00000000..a2027768 --- /dev/null +++ b/examples/calliope/supply.yaml @@ -0,0 +1,159 @@ +# SPDX-FileCopyrightText: mathspec Contributors +# +# SPDX-License-Identifier: MIT + +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + costs: + description: Calliope's `costs` — cost classes, such as monetary and CO2 + +parameters: + source_eff: + description: "`source_eff` — the share of the source a supply technology takes in. Calliope's default is 1, and data prep fills it" + dims: [nodes, techs, timesteps] + source_use_min: + description: "`source_use_min` — least source use in a time step, per unit of `source_unit`" + dims: [nodes, techs, timesteps] + source_use_max: + description: "`source_use_max` — most source use in a time step, per unit of `source_unit`; given only where set" + dims: [nodes, techs, timesteps] + source_use_equals: + description: "`source_use_equals` — the source use required in a time step, per unit of `source_unit`; given only where set" + dims: [nodes, techs, timesteps] + source_unit: + description: >- + `source_unit` — what the source is per: `absolute`, `per_area` of + area use, or `per_cap` of flow capacity. Calliope's default is + `absolute`, which is what a technology with no row reads as + dims: [nodes, techs] + dtype: str + source_cap_min: + description: "`source_cap_min` — least source capacity. Calliope's default is 0, and data prep fills it" + dims: [nodes, techs] + source_cap_max: + description: "`source_cap_max` — most source capacity. Calliope's default is `.inf`, and data prep fills it" + dims: [nodes, techs] + source_cap_equals_flow_cap: + description: "`source_cap_equals_flow_cap` — whether the source capacity equals the flow capacity" + dims: [nodes, techs] + dtype: bool + cost_source_use: + description: "`cost_source_use` — the cost of one unit of source use" + dims: [nodes, techs, costs, timesteps] + cost_source_cap: + description: "`cost_source_cap` — the cost of one unit of source capacity" + dims: [nodes, techs, costs] + +variables: + source_use: + description: "`source_use` — what a supply technology takes in from outside the system in a time step" + dims: [nodes, techs, timesteps] + where: base_tech == 'supply' + bounds: { lower: 0 } + absence: zero + source_cap: + description: "`source_cap` — the most a supply technology can take in from outside the system" + dims: [nodes, techs] + where: base_tech == 'supply' + bounds: { lower: source_cap_min, upper: source_cap_max } + absence: zero + +expressions: + flow_cap_out: + description: "`where(flow_cap, carrier_out)` — the flow capacity of the carriers a technology produces" + dims: [nodes, techs, carriers] + cases: + produced: + when: carrier_out + expression: flow_cap + otherwise: 0 + source_scaler: + description: "`$source_scaler` — what the source parameters are per: area use, flow capacity, or one" + dims: [nodes, techs] + cases: + per_area: + when: source_unit == per_area + expression: area_use + per_cap: + when: source_unit == per_cap + expression: sum(flow_cap_out, over=carriers) + otherwise: 1 + cost_investment_source_cap: + description: "`cost_investment_source_cap` — the investment cost of source capacity" + expression: cost_source_cap * source_cap + supply_cost_operation_variable: timestep_weights * cost_source_use * source_use + curtailment: + description: >- + `curtailment` — the share of the available source a supply technology + leaves unused in a time step; reported + expression: 1 - source_use / (source_use_max * source_scaler) + total_curtailment: + description: "`total_curtailment` — the share of the available source left unused over the whole time; reported" + expression: 1 - sum(source_use, over=timesteps) / sum(source_use_max * source_scaler, over=timesteps) + +given: + parameters: + base_tech: { dims: [techs], dtype: str } + carrier_out: { dims: [nodes, techs, carriers], dtype: bool } + include_storage: { dims: [nodes, techs], dtype: bool } + timestep_resolution: { dims: [timesteps] } + timestep_weights: { dims: [timesteps] } + variables: + flow_cap: { dims: [nodes, techs, carriers] } + area_use: { dims: [nodes, techs] } + expressions: + flow_out_inc_eff: { dims: [nodes, techs, carriers, timesteps] } + cost_investment: { dims: [nodes, techs, costs], term: cost_investment_source_cap } + cost_operation_variable: { dims: [nodes, techs, costs, timesteps], term: supply_cost_operation_variable } + +constraints: + source_max: + description: "`source_max` — source use is at most the source capacity over the time step" + dims: [nodes, techs, timesteps] + where: source_cap + expression: source_use <= timestep_resolution * source_cap + source_capacity_equals_flow_capacity: + description: "`source_capacity_equals_flow_capacity` — a supply technology's source capacity equals its flow capacity, where set" + dims: [nodes, techs, carriers] + where: flow_cap AND source_cap AND source_cap_equals_flow_cap + expression: source_cap == flow_cap + balance_supply_no_storage: + description: "`balance_supply_no_storage` — a supply technology with no store puts out what it takes from its source" + dims: [nodes, techs, carriers, timesteps] + where: carrier_out AND base_tech == 'supply' AND NOT include_storage + expression: flow_out_inc_eff == source_use * source_eff + source_availability_supply_equals: + description: "`source_availability_supply` where `source_use_equals` is set — source use is what is available" + dims: [nodes, techs, timesteps] + where: source_use AND source_use_equals + expression: source_use == source_use_equals * source_scaler + source_availability_supply_max: + description: "`source_availability_supply` where only `source_use_max` is set — source use is at most what is available" + dims: [nodes, techs, timesteps] + where: source_use AND NOT source_use_equals AND source_use_max + expression: source_use <= source_use_max * source_scaler + balance_supply_min_use: + description: "`balance_supply_min_use` — source use is at least its least use" + dims: [nodes, techs, timesteps] + where: source_use_min AND NOT source_use_equals AND base_tech == 'supply' + expression: source_use >= source_use_min * source_scaler + +assumptions: + unbounded_source_use_cost: + description: Calliope's `unbounded_source_use_cost` — a negative source capacity cost needs a finite maximum + holds: NOT cost_source_cap < 0 OR source_cap_max + finite_source_use: + description: Calliope's `finite_source_use`, for the source — a required use is finite + holds: NOT source_use_equals == inf + source_unit_one_of: + description: Calliope's `one_of` on `source_unit` + holds: source_unit == absolute OR source_unit == per_area OR source_unit == per_cap + where: source_unit diff --git a/examples/calliope/supply_storage.yaml b/examples/calliope/supply_storage.yaml new file mode 100644 index 00000000..bae741bc --- /dev/null +++ b/examples/calliope/supply_storage.yaml @@ -0,0 +1,35 @@ +# SPDX-FileCopyrightText: mathspec Contributors +# +# SPDX-License-Identifier: MIT + +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + +given: + parameters: + base_tech: { dims: [techs], dtype: str } + carrier_out: { dims: [nodes, techs, carriers], dtype: bool } + source_eff: { dims: [nodes, techs, timesteps] } + variables: + storage: { dims: [nodes, techs, timesteps] } + source_use: { dims: [nodes, techs, timesteps] } + expressions: + storage_previous_step: { dims: [nodes, techs, timesteps] } + flow_out_inc_eff: { dims: [nodes, techs, carriers, timesteps] } + +constraints: + balance_supply_with_storage: + description: >- + `balance_supply_with_storage` — a supply technology with a store puts + in what it takes from its source and draws out what it puts out + dims: [nodes, techs, carriers, timesteps] + where: carrier_out AND storage AND base_tech == 'supply' + expression: storage == storage_previous_step + source_use * source_eff - flow_out_inc_eff diff --git a/examples/calliope/transmission.yaml b/examples/calliope/transmission.yaml new file mode 100644 index 00000000..41456638 --- /dev/null +++ b/examples/calliope/transmission.yaml @@ -0,0 +1,69 @@ +# SPDX-FileCopyrightText: mathspec Contributors +# +# SPDX-License-Identifier: MIT + +dimensions: + nodes: + description: Calliope's `nodes` — the places technologies stand at + techs: + description: Calliope's `techs` — technologies + carriers: + description: Calliope's `carriers` — energy and commodity carriers + timesteps: + description: Calliope's `timesteps` — time steps, in order + dtype: datetime + +relations: + link_from: + description: >- + `link_from` — the node a transmission technology links from. Calliope + reads it as `map_dim(nodes, link_from)`, a mask over technology and + node, which is the relation's own row test + key: [techs, nodes] + link_to: + description: >- + `link_to` — the node a transmission technology links to, read as + `link_from` is + key: [techs, nodes] + +expressions: + flow_cap_from: + description: "`where(flow_cap, map_dim(nodes, link_from))` — a link's flow capacity at the node it links from" + dims: [nodes, techs, carriers] + cases: + from: + when: link_from + expression: flow_cap + otherwise: 0 + flow_cap_to: + description: "`where(flow_cap, map_dim(nodes, link_to))` — a link's flow capacity at the node it links to" + dims: [nodes, techs, carriers] + cases: + to: + when: link_to + expression: flow_cap + otherwise: 0 + +given: + parameters: + base_tech: { dims: [techs], dtype: str } + carrier_out: { dims: [nodes, techs, carriers], dtype: bool } + variables: + flow_cap: { dims: [nodes, techs, carriers] } + expressions: + flow_out_inc_eff: { dims: [nodes, techs, carriers, timesteps] } + flow_in_inc_eff: { dims: [nodes, techs, carriers, timesteps] } + +constraints: + balance_transmission: + description: "`balance_transmission` — a link puts out at one end, before losses, what it takes in at the other after them" + dims: [techs, timesteps] + where: base_tech == 'transmission' + expression: >- + sum(sum(flow_out_inc_eff, over=nodes), over=carriers) + == sum(sum(flow_in_inc_eff, over=nodes), over=carriers) + symmetric_transmission: + description: "`symmetric_transmission` — a link has the same flow capacity at both ends" + dims: [techs, carriers] + where: count(carrier_out, over=nodes) >= 1 AND base_tech == 'transmission' + expression: sum(flow_cap_from, over=nodes) == sum(flow_cap_to, over=nodes) diff --git a/examples/calliope/variants/chp_htp.yaml b/examples/calliope/variants/chp_htp.yaml new file mode 100644 index 00000000..696753bf --- /dev/null +++ b/examples/calliope/variants/chp_htp.yaml @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: mathspec Contributors +# +# SPDX-License-Identifier: MIT + +constraints: + balance_conversion: + description: >- + `balance_conversion` for a plant with no turbine type — a conversion + technology puts out, before its losses, what it takes in after them. + Extraction and backpressure plants have rows of their own + where: base_tech == 'conversion' AND NOT include_storage AND NOT turbine_type diff --git a/examples/calliope/variants/milp.yaml b/examples/calliope/variants/milp.yaml new file mode 100644 index 00000000..0e2a2374 --- /dev/null +++ b/examples/calliope/variants/milp.yaml @@ -0,0 +1,47 @@ +# SPDX-FileCopyrightText: mathspec Contributors +# +# SPDX-License-Identifier: MIT + +parameters: + flow_cap_min: + description: >- + `flow_cap_min` — least flow capacity, scaled by the units bought where + a technology buys units; given only where set, as no bound reads it + flow_cap_min_systemwide: + description: >- + `flow_cap_min_systemwide` — least flow capacity of a technology over + every node, scaled by the units bought where it buys units; given only + where set + flow_out_min_relative: + description: >- + `flow_out_min_relative` — least outflow, per unit of flow capacity. For + a continuous technology it holds in every time step; given only where + set + storage_cap_min: + description: "`storage_cap_min` — least storage capacity; given only where set, as no bound reads it" + area_use_min: + description: "`area_use_min` — least area use; given only where set, as no bound reads it" + source_cap_min: + description: "`source_cap_min` — least source capacity; given only where set, as no bound reads it" + +variables: + flow_cap: { bounds: { lower: 0 } } + area_use: { bounds: { lower: 0 } } + source_cap: { bounds: { lower: 0 } } + storage_cap: { bounds: { lower: 0 } } + +constraints: + flow_out_max: + description: "`flow_out_max` — a continuous technology's outflow is at most its flow capacity over the time step" + where: carrier_out AND NOT operating_units + flow_out_min: + description: "`flow_out_min` — a continuous technology's outflow is at least its least share of the flow capacity" + where: flow_cap AND flow_out_min_relative AND NOT operating_units + flow_in_max: + description: "`flow_in_max` — a continuous technology's inflow is at most its flow capacity over the time step" + where: carrier_in AND NOT operating_units + flow_capacity_systemwide_min: + description: >- + `flow_capacity_systemwide_min` where no unit is bought — the flow + capacity over every node is at least the system-wide minimum + where: count(flow_cap, over=nodes) >= 1 AND flow_cap_min_systemwide AND NOT count(purchased_units, over=nodes) >= 1 diff --git a/examples/calliope/variants/operate.yaml b/examples/calliope/variants/operate.yaml new file mode 100644 index 00000000..85ddf239 --- /dev/null +++ b/examples/calliope/variants/operate.yaml @@ -0,0 +1,57 @@ +# SPDX-FileCopyrightText: mathspec Contributors +# +# SPDX-License-Identifier: MIT + +parameters: + flow_cap: + description: "`flow_cap` — the flow capacity, fixed in operate mode. Calliope's default is `.inf`" + dims: [nodes, techs, carriers] + area_use: + description: "`area_use` — the area used, fixed in operate mode. Calliope's default is `.inf`" + dims: [nodes, techs] + source_cap: + description: "`source_cap` — the source capacity, fixed in operate mode. Calliope's default is `.inf`" + dims: [nodes, techs] + storage_cap: + description: "`storage_cap` — the storage capacity, fixed in operate mode. Calliope's default is `.inf`" + dims: [nodes, techs] + +variables: + flow_cap: null + area_use: null + source_cap: null + storage_cap: null + +constraints: + flow_capacity_per_storage_capacity_min: null + flow_capacity_per_storage_capacity_max: null + source_capacity_equals_flow_capacity: null + force_zero_area_use: null + area_use_per_flow_capacity: null + area_use_capacity_per_loc: null + flow_capacity_systemwide_max: null + flow_capacity_systemwide_min: null + symmetric_transmission: null + +expressions: + cost_investment: null + cost_investment_annualised: null + cost_investment_flow_cap: null + cost_investment_storage_cap: null + cost_investment_source_cap: null + cost_investment_area_use: null + cost_operation_fixed: null + cost_flow_cap_sum: null + depreciation_rate: null + flows_cost_investment: null + flows_cost_operation_fixed: null + flow_cap_from: null + flow_cap_to: null + cost: + description: "`cost` — the operating cost of a technology, over every time step" + expression: sum(cost_operation_variable, over=timesteps) + +assumptions: + operate_mode_cyclic_storage: + description: Calliope's `operate_mode_cyclic_storage` — a store in operate mode is not cyclic + holds: NOT (cyclic_storage AND (base_tech == 'storage' OR include_storage)) diff --git a/examples/calliope/variants/operate_milp.yaml b/examples/calliope/variants/operate_milp.yaml new file mode 100644 index 00000000..152efb00 --- /dev/null +++ b/examples/calliope/variants/operate_milp.yaml @@ -0,0 +1,32 @@ +# SPDX-FileCopyrightText: mathspec Contributors +# +# SPDX-License-Identifier: MIT + +parameters: + purchased_units: + description: "`purchased_units` — the units bought, fixed in operate mode. Calliope's default is `.inf`" + dims: [nodes, techs] + +variables: + purchased_units: null + +constraints: + storage_capacity_units_milp: null + flow_capacity_units_milp: null + unit_capacity_max_systemwide_milp: null + unit_capacity_min_systemwide_milp: null + flow_capacity_max_purchase_milp: null + flow_capacity_max_purchase_milp_big_m: null + storage_capacity_max_purchase_milp: null + flow_capacity_minimum: null + flow_capacity_minimum_purchased: null + storage_capacity_minimum: null + storage_capacity_minimum_purchased: null + area_use_minimum: null + area_use_minimum_purchased: null + source_capacity_minimum: null + source_capacity_minimum_purchased: null + flow_capacity_systemwide_min_purchased: null + +expressions: + cost_investment_purchase: null diff --git a/examples/calliope/variants/spores.yaml b/examples/calliope/variants/spores.yaml new file mode 100644 index 00000000..88cc5463 --- /dev/null +++ b/examples/calliope/variants/spores.yaml @@ -0,0 +1,39 @@ +# SPDX-FileCopyrightText: mathspec Contributors +# +# SPDX-License-Identifier: MIT + +parameters: + spores_baseline_cost: + description: >- + `spores_baseline_cost` — the least cost of the system, which a SPORES + iteration may exceed by its slack. Calliope's default is `.inf` + dims: [] + spores_slack: + description: "`spores_slack` — the share by which a SPORES iteration may exceed the least cost" + dims: [] + spores_score: + description: "`spores_score` — the score a technology at a node carries from the SPORES iterations before" + dims: [nodes, techs, carriers] + +constraints: + total_system_cost_max: + description: >- + `total_system_cost_max` — the cost the least-cost objective reads is at + most the least cost plus the slack. It reads the same sums the + objective did, so a file that adds a cost adds it here too + dims: [] + expression: system_cost + penalty <= spores_baseline_cost * (1 + spores_slack) + +expressions: + spores_score_cumulative: + description: "`spores_score_cumulative` — the SPORES score, reported with the results" + expression: spores_score + spores_baseline_cost_tracked: + description: "`spores_baseline_cost_tracked` — the SPORES baseline cost, reported with the results" + expression: spores_baseline_cost + +objective: + description: >- + `min_spores` — the SPORES score of the flow capacity built, plus the + penalty on unmet demand + expression: sum(flow_cap * spores_score) + penalty diff --git a/examples/calliope/variants/storage_inter_cluster.yaml b/examples/calliope/variants/storage_inter_cluster.yaml new file mode 100644 index 00000000..0dd123ef --- /dev/null +++ b/examples/calliope/variants/storage_inter_cluster.yaml @@ -0,0 +1,141 @@ +# SPDX-FileCopyrightText: mathspec Contributors +# +# SPDX-License-Identifier: MIT + +dimensions: + clusters: + description: Calliope's `clusters` — the representative days a clustered time series is made of + dtype: int + datesteps: + description: Calliope's `datesteps` — the days of the whole time series, in order + dtype: datetime + +relations: + timestep_cluster: + description: "`timestep_cluster` — the cluster a time step belongs to" + key: timesteps + values: clusters + lookup_datestep_cluster: + description: "`lookup_datestep_cluster` — the cluster a day stands for" + key: datesteps + values: clusters + lookup_datestep_last_cluster_timestep: + description: "`lookup_datestep_last_cluster_timestep` — the last time step of the cluster a day stands for" + key: datesteps + values: timesteps + +parameters: + storage_retention: + description: >- + `1 - storage_loss` — the share of what a store holds that it keeps for + an hour, data prep. Between days it is raised to 24, so it does not + vary over time steps here + dims: [nodes, techs] + +variables: + storage: + description: >- + `storage` — what a store holds within a clustered day, relative to what + it carries between days. It may go below zero, as long as the sum does + not + bounds: { lower: null } + storage_inter_cluster: + description: "`storage_inter_cluster` — what a store carries from one day of the whole time series to the next" + dims: [nodes, techs, datesteps] + where: include_storage OR base_tech == 'storage' + bounds: { lower: 0 } + absence: zero + storage_intra_cluster_max: + description: "`storage_intra_cluster_max` — the most a store holds within a clustered day" + dims: [nodes, techs, clusters] + where: include_storage OR base_tech == 'storage' + storage_intra_cluster_min: + description: "`storage_intra_cluster_min` — the least a store holds within a clustered day" + dims: [nodes, techs, clusters] + where: include_storage OR base_tech == 'storage' + +expressions: + storage_previous_step: + description: >- + `$storage_previous_step` under inter-cluster storage — what a store + carries into a time step: its initial fill at the first step of a store + that is not cyclic, nothing at the first step of a clustered day, and + what is left of the step before everywhere else + cases: + initial: + when: position(timesteps) == 0 AND NOT cyclic_storage + expression: storage_initial * storage_cap + cluster_start: + when: lookup_cluster_last_timestep AND NOT (position(timesteps) == 0 AND NOT cyclic_storage) + expression: "0" + storage_inter_previous_step: + description: >- + `$storage_previous_step` of `balance_storage_inter` — what a store + carries into a day: its initial fill on the first day of a store that + is not cyclic, and what is left of the day before everywhere else. + Calliope reads the initial fill as a share, not times the capacity, as + here + dims: [nodes, techs, datesteps] + cases: + initial: + when: position(datesteps) == 0 AND NOT cyclic_storage + expression: storage_initial + otherwise: storage_retention ** 24 * shift(storage_inter_cluster, along=datesteps, offset=1, edge='wrap') + storage_intra: + description: >- + `$storage_intra` of `balance_storage_inter` — what the clustered day of + the day before left at its last step, and nothing on the first day of + a store that is not cyclic + dims: [nodes, techs, datesteps] + cases: + initial: + when: position(datesteps) == 0 AND NOT cyclic_storage + expression: "0" + otherwise: >- + shift(at(storage, by=lookup_datestep_last_cluster_timestep, over=timesteps, into=datesteps), + along=datesteps, offset=1, edge='wrap') + +constraints: + storage_max: null + set_storage_initial: + description: >- + `set_storage_initial` under inter-cluster storage — a cyclic store with + an initial fill carries it between days at the end, after a day's loss + dims: [nodes, techs, datesteps] + where: position(datesteps) == -1 AND storage_inter_cluster AND storage_initial AND cyclic_storage + expression: storage_inter_cluster * storage_retention ** 24 == storage_initial * storage_cap + storage_intra_max: + description: "`storage_intra_max` — a store holds at most its most within its clustered day" + dims: [nodes, techs, timesteps] + where: include_storage OR base_tech == 'storage' + expression: storage <= at(storage_intra_cluster_max, by=timestep_cluster, over=clusters, into=timesteps) + storage_intra_min: + description: "`storage_intra_min` — a store holds at least its least within its clustered day" + dims: [nodes, techs, timesteps] + where: include_storage OR base_tech == 'storage' + expression: storage >= at(storage_intra_cluster_min, by=timestep_cluster, over=clusters, into=timesteps) + storage_inter_max: + description: "`storage_inter_max` — what a store carries between days plus the most of its day is at most its capacity" + dims: [nodes, techs, datesteps] + where: include_storage OR base_tech == 'storage' + expression: >- + storage_inter_cluster + at(storage_intra_cluster_max, by=lookup_datestep_cluster, over=clusters, into=datesteps) + <= storage_cap + storage_inter_min: + description: "`storage_inter_min` — what a store carries between days, after a day's loss, plus the least of its day is not below zero" + dims: [nodes, techs, datesteps] + where: include_storage OR base_tech == 'storage' + expression: >- + storage_inter_cluster * storage_retention ** 24 + + at(storage_intra_cluster_min, by=lookup_datestep_cluster, over=clusters, into=datesteps) >= 0 + balance_storage_inter: + description: >- + `balance_storage_inter` — what a store carries into a day is what it + carried into the day before, after a day's loss, plus what that day's + cluster left + dims: [nodes, techs, datesteps] + where: include_storage OR base_tech == 'storage' + expression: storage_inter_cluster == storage_inter_previous_step + storage_intra + +assumptions: + cyclic_storage_needs_inter_cluster: null diff --git a/examples/calliope/variants/urban_scale_chp.yaml b/examples/calliope/variants/urban_scale_chp.yaml new file mode 100644 index 00000000..6932c81c --- /dev/null +++ b/examples/calliope/variants/urban_scale_chp.yaml @@ -0,0 +1,10 @@ +# SPDX-FileCopyrightText: mathspec Contributors +# +# SPDX-License-Identifier: MIT + +constraints: + balance_conversion: + description: >- + `balance_conversion` for every conversion technology but `chp` — it + puts out, before its losses, what it takes in after them + where: base_tech == 'conversion' AND NOT include_storage AND NOT techs == chp diff --git a/examples/symbols/calliope.yaml b/examples/symbols/calliope.yaml new file mode 100644 index 00000000..319bb3e5 --- /dev/null +++ b/examples/symbols/calliope.yaml @@ -0,0 +1,20 @@ +# SPDX-FileCopyrightText: mathspec Contributors +# +# SPDX-License-Identifier: MIT + +# How `examples/calliope/` prints. The names are Calliope's and print as +# themselves, so a row reads beside Calliope's own math; only the dimensions +# take a letter, as Calliope's iterators take a word. +notation: latex +dimensions: + nodes: { index: n, set: '\mathcal{N}' } + techs: { index: i, set: '\mathcal{I}' } + carriers: { index: c, set: '\mathcal{C}' } + costs: { index: k, set: '\mathcal{K}' } + timesteps: { index: t, set: '\mathcal{T}' } + clusters: { index: l, set: '\mathcal{L}' } + datesteps: { index: d, set: '\mathcal{D}' } + months: { index: m, set: '\mathcal{M}' } + pieces: { index: p, set: '\mathcal{P}' } + breakpoints: { index: b, set: '\mathcal{B}' } +names: {} diff --git a/mkdocs.yml b/mkdocs.yml index 253a94a4..7d442464 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -81,7 +81,7 @@ nav: # writes a tool against `Spec` and `Program` (an engine such as specsolve, a # renderer, a checker), whoever changes mathspec itself, and the proofs of # concept: the notation page, which renders the typesetting test spec, and - # the PyPSA pages. The PyPSA pages stay in `docs/examples/`, where + # the PyPSA and Calliope pages. Those stay in `docs/examples/`, where # `tools/gallery.py` writes them. - Development: - Building on mathspec: @@ -121,6 +121,47 @@ nav: - Lines: examples/pypsa/line.md - Transformers: examples/pypsa/transformer.md - Loads: examples/pypsa/load.md + - Calliope in fragments: + - examples/calliope/index.md + - The port record: examples/calliope/port.md + - Settings: examples/calliope/settings.md + - The balance: examples/calliope/balance.md + - Flows: examples/calliope/flows.md + - The cost: examples/calliope/cost.md + - Supply: examples/calliope/supply.md + - Demand: examples/calliope/demand.md + - Conversion: examples/calliope/conversion.md + - Storage: examples/calliope/storage.md + - Supply with storage: examples/calliope/supply_storage.md + - Area: examples/calliope/area.md + - Transmission: examples/calliope/transmission.md + - Export: examples/calliope/export.md + - Feasibility: examples/calliope/feasibility.md + - Reporting: examples/calliope/reporting.md + - Extensions: + - MILP: examples/calliope/extensions/milp.md + - Annual energy balance: examples/calliope/extensions/annual_energy_balance.md + - CHP plants: examples/calliope/extensions/chp_htp.md + - Demand share as a decision: examples/calliope/extensions/demand_share_per_timestep_decision.md + - Fuel distribution: examples/calliope/extensions/fuel_dist.md + - Time-varying flow limit: examples/calliope/extensions/max_time_varying.md + - Monthly peak flow charge: examples/calliope/extensions/monthly_peak_flow_charge.md + - Net import share: examples/calliope/extensions/net_import_share.md + - Piecewise linear costs: examples/calliope/extensions/piecewise_linear_costs.md + - Piecewise linear efficiency: examples/calliope/extensions/piecewise_linear_efficiency.md + - Flow share over the whole time: examples/calliope/extensions/share_all_timesteps.md + - Flow share per time step: examples/calliope/extensions/share_per_timestep.md + - Piecewise linear costs with SOS2: examples/calliope/extensions/sos2_piecewise_linear_costs.md + - Uptime and downtime limits: examples/calliope/extensions/uptime_downtime_limits.md + - Urban-scale CHP: examples/calliope/extensions/urban_scale_chp.md + - Variants: + - The MILP patch: examples/calliope/variants/milp.md + - Operate mode: examples/calliope/variants/operate.md + - Operate mode, with MILP: examples/calliope/variants/operate_milp.md + - SPORES: examples/calliope/variants/spores.md + - Inter-cluster storage: examples/calliope/variants/storage_inter_cluster.md + - The CHP patch: examples/calliope/variants/chp_htp.md + - The urban-scale CHP patch: examples/calliope/variants/urban_scale_chp.md theme: palette: diff --git a/tests/test_calliope_example.py b/tests/test_calliope_example.py new file mode 100644 index 00000000..db851106 --- /dev/null +++ b/tests/test_calliope_example.py @@ -0,0 +1,145 @@ +# SPDX-FileCopyrightText: mathspec Contributors +# +# SPDX-License-Identifier: MIT + +"""The Calliope port under `examples/calliope/`, held to what its pages claim. + +The gallery test holds each page to its generator. What is left for here is +what the index and the port record state: every fragment stands alone, the +base and every extension compose with nothing left to provide, every variant +lands on the composition its page names, and a sum Calliope restates to add a +term gains the term instead. +""" + +from __future__ import annotations + +import re + +import pytest + +from mathspec import LanguageError, advice, merge, override, to_markdown, to_spec +from tests.fixtures import EXAMPLES +from tools import gallery + +CALLIOPE = EXAMPLES / 'calliope' +BASE = sorted(CALLIOPE.glob('*.yaml')) +EXTENSIONS = {path.stem: path for path in sorted((CALLIOPE / 'extensions').glob('*.yaml'))} +VARIANTS = {path.stem: path for path in sorted((CALLIOPE / 'variants').glob('*.yaml'))} + +#: The extensions that read the units the MILP fragment builds. +NEEDS_MILP = {'piecewise_linear_costs', 'piecewise_linear_efficiency', 'uptime_downtime_limits'} + +#: The extensions whose new rows replace a base row a variant of the same name narrows. +REWRITES_A_BASE_ROW = {'chp_htp', 'urban_scale_chp'} + + +def _composed(*extensions: str): + """The base, the MILP pair where an extension needs it, the extensions, and the variants of their rewrites.""" + milp = 'milp' in extensions or NEEDS_MILP & set(extensions) + names = [*(['milp'] if milp and 'milp' not in extensions else []), *extensions] + patches = [VARIANTS['milp']] if milp else [] + patches += [VARIANTS[name] for name in names if name in REWRITES_A_BASE_ROW] + merged = merge([*BASE, *(EXTENSIONS[name] for name in names)]) + return override(merged, patches) if patches else merged + + +@pytest.mark.parametrize('path', [*BASE, *EXTENSIONS.values()], ids=lambda path: path.stem) +def test_every_fragment_loads_and_prints_on_its_own(path): + assert to_markdown(to_spec(path)), f'{path.stem} rendered nothing' + + +def test_the_base_composes_with_nothing_left_to_provide(): + spec = merge(BASE) + assert not spec.given, 'every name a base fragment reads is one another base fragment declares' + assert not advice(spec), 'the composed base has no dimension out of use, no column to provide, no open variable' + + +@pytest.mark.parametrize('name', sorted(EXTENSIONS)) +def test_every_extension_composes_onto_the_base(name): + spec = _composed(name) + assert not spec.given, f'{name} reads only what the base, and the MILP fragment where it needs it, declare' + assert not advice(spec), f'{name} composed leaves nothing to advise on' + + +@pytest.mark.parametrize('name', sorted(VARIANTS)) +def test_every_variant_is_a_patch_rather_than_a_spec(name): + with pytest.raises(LanguageError): + to_spec(VARIANTS[name]) + + +@pytest.mark.parametrize('page', sorted(gallery.CALLIOPE_VARIANTS)) +def test_every_variant_lands_on_the_composition_its_page_names(page): + fragments, before = gallery.CALLIOPE_VARIANTS[page] + name = page.removeprefix('calliope/variants/').removesuffix('.md') + spec = override(merge(fragments), [*(VARIANTS[earlier] for earlier in before), VARIANTS[name]]) + assert not spec.given and not advice(spec), f'{name} leaves a whole spec with nothing to advise on' + + +def test_every_variant_has_a_page(): + paged = {page.removeprefix('calliope/variants/').removesuffix('.md') for page in gallery.CALLIOPE_VARIANTS} + assert paged == set(VARIANTS), 'a variant with no page is a patch nobody can read as math' + + +def test_the_whole_port_composes_in_one_spec(): + """Every extension but the two that Calliope also offers as alternatives, with every mode but operate.""" + alternatives = {'sos2_piecewise_linear_costs', 'urban_scale_chp'} + merged = merge([*BASE, *(path for name, path in EXTENSIONS.items() if name not in alternatives)]) + patches = ['milp', 'chp_htp', 'spores', 'storage_inter_cluster'] + spec = override(merged, [VARIANTS[name] for name in patches]) + assert not spec.given and not advice(spec), 'the port is one spec once composed' + + +def test_the_two_piecewise_costs_are_alternatives(): + with pytest.raises(LanguageError, match=r"both declare the variable 'piecewise_cost_investment'"): + merge( + [*BASE, EXTENSIONS['milp'], EXTENSIONS['piecewise_linear_costs'], EXTENSIONS['sos2_piecewise_linear_costs']] + ) + + +@pytest.mark.parametrize( + ('name', 'hub', 'term'), + [ + pytest.param('fuel_dist', 'carrier_flow', 'fuel_dist_carrier_flow', id='fuel-in-the-balance'), + pytest.param('fuel_dist', 'system_cost', 'fuel_dist_system_cost', id='fuel-in-the-objective'), + pytest.param('monthly_peak_flow_charge', 'cost_operation_fixed', 'cost_month_peak_charge', id='peak-charge'), + pytest.param('milp', 'cost_investment', 'cost_investment_purchase', id='purchase-cost'), + pytest.param( + 'piecewise_linear_costs', 'cost_investment', 'piecewise_cost_investment_term', id='piecewise-cost' + ), + ], +) +def test_a_sum_calliope_restates_gains_a_term_instead(name, hub, term): + """Calliope restates the whole block to add one term; here the base file stays as it is.""" + spec = _composed(name) + assert re.search(rf'\b{term}\b', spec.expressions[hub].expression), f'{hub} carries the term {name} adds' + + +def test_spores_caps_the_sums_the_objective_read(): + """Calliope restates its objective in the cap, and misses a cost an example adds to it; the cap reads the sums.""" + spec = override(_composed('fuel_dist'), [VARIANTS['spores']]) + cap = spec.constraints['total_system_cost_max'].expression + assert 'system_cost' in cap and 'penalty' in cap, 'the cap reads the two sums the least-cost objective read' + assert 'fuel_dist_system_cost' in spec.expressions['system_cost'].expression, 'so fuel distribution is capped too' + + +def test_operate_mode_turns_every_capacity_into_data(): + spec = override(_composed('milp'), [VARIANTS[name] for name in ('milp', 'operate', 'operate_milp')]) + capacities = {'flow_cap', 'area_use', 'source_cap', 'storage_cap', 'purchased_units'} + assert capacities <= set(spec.parameters), 'each capacity is a parameter of the same name' + assert not capacities & set(spec.variables), 'and no longer a decision' + + +def test_the_sos2_curve_links_a_copy_of_the_flow_capacity_masked_to_the_curve(): + """A `piecewise:` block has no `where:`, and a link over `flow_cap` pinned every technology with no curve to zero. + + The expanded link row `flow_cap == sum(lam * x)` is built at every + coordinate of the frame. Where a technology has no breakpoints its weights + do not exist, so the row reads `flow_cap == 0`. The link reads a copy of + `flow_cap` that exists only where the curve does, and the row goes with it. + """ + spec = to_spec(EXTENSIONS['sos2_piecewise_linear_costs']).expand() + link = spec.constraints['sos2_piecewise_costs_link0'].expression + assert link.startswith('(piecewise_flow_cap)'), 'the curve pins the masked copy, not the flow capacity itself' + assert 'piecewise_cost_investment_x' in spec.variables['piecewise_flow_cap'].where, ( + 'the copy exists only where the technology has breakpoints' + ) diff --git a/tools/gallery.py b/tools/gallery.py index 67d2df05..84e35309 100644 --- a/tools/gallery.py +++ b/tools/gallery.py @@ -17,6 +17,7 @@ import re import textwrap from functools import partial +from pathlib import Path from typing import TYPE_CHECKING, Any import yaml @@ -30,8 +31,6 @@ from tools.spec_math import OPERATORS, PROBES, _section, rendered_probe if TYPE_CHECKING: - from pathlib import Path - from mathspec.spec import Spec PAGES = ROOT / 'docs' / 'examples' @@ -43,6 +42,13 @@ #: PyPSA split prints in the one file's table, cut the same way. LIBRARY_SYMBOLS = ROOT / 'examples' / 'symbols' / 'library.yaml' PYPSA_SYMBOLS = ROOT / 'examples' / 'symbols' / 'pypsa.yaml' +#: Calliope's math: its base as one fragment per topic, each file beyond the +#: base that adds to it, and the patches its modes and its rewrites of a base +#: row are written as. Every page prints in one table, cut as above. +CALLIOPE = ROOT / 'examples' / 'calliope' +CALLIOPE_BASE = sorted(CALLIOPE.glob('*.yaml')) +CALLIOPE_EXTENSIONS = sorted((CALLIOPE / 'extensions').glob('*.yaml')) +CALLIOPE_SYMBOLS = ROOT / 'examples' / 'symbols' / 'calliope.yaml' BEGIN, END = '', '' #: Page -> the spec it shows. One spec per page, because a gallery of @@ -54,10 +60,33 @@ 'library/generator.md': LIBRARY / 'generator.yaml', 'library/load.md': LIBRARY / 'load.yaml', **{f'pypsa/{path.stem}.md': path for path in sorted(PYPSA.glob('*.yaml'))}, + **{f'calliope/{path.stem}.md': path for path in CALLIOPE_BASE}, + **{f'calliope/extensions/{path.stem}.md': path for path in CALLIOPE_EXTENSIONS}, } #: The index of the PyPSA split: its two tables are read off the fragments. SPLIT_INDEX = 'pypsa/index.md' +#: The index of the Calliope port: the same two tables, over the base and every extension. +CALLIOPE_INDEX = 'calliope/index.md' + + +def _calliope(*extensions: str) -> list[Path]: + """The base fragments, then the named extensions, in the order `merge` takes them.""" + return [*CALLIOPE_BASE, *(CALLIOPE / 'extensions' / f'{name}.yaml' for name in extensions)] + + +#: Page -> the fragments a Calliope patch is laid over, and the patches laid +#: before it. A patch is not a spec, so its page prints what it writes, as the +#: spec it lands on prints it. +CALLIOPE_VARIANTS: dict[str, tuple[list[Path], list[str]]] = { + 'calliope/variants/milp.md': (_calliope('milp'), []), + 'calliope/variants/operate.md': (_calliope(), []), + 'calliope/variants/operate_milp.md': (_calliope('milp'), ['milp', 'operate']), + 'calliope/variants/spores.md': (_calliope(), []), + 'calliope/variants/storage_inter_cluster.md': (_calliope(), []), + 'calliope/variants/chp_htp.md': (_calliope('chp_htp'), []), + 'calliope/variants/urban_scale_chp.md': (_calliope('urban_scale_chp'), []), +} #: Page -> the fragments whose composition it shows, and the patches laid over #: it. The spec is what `merge` returns, which no file in the tree holds, so @@ -119,26 +148,46 @@ def fragment_block(path: Path, table_path: Path) -> str: def split_index_block() -> str: - """The PyPSA split's two tables: each sum with the fragment that declares it and the terms, and each fragment. + """The PyPSA split's two tables, read off its fragments.""" + return sum_tables({path.stem: (path, f'{path.stem}.md') for path in sorted(PYPSA.glob('*.yaml'))}) + + +def calliope_index_block() -> str: + """The Calliope port's two tables, over the base fragments and then every extension.""" + return sum_tables( + { + **{path.stem: (path, f'{path.stem}.md') for path in CALLIOPE_BASE}, + **{path.stem: (path, f'extensions/{path.stem}.md') for path in CALLIOPE_EXTENSIONS}, + } + ) + + +def sum_tables(fragments: dict[str, tuple[Path, str]]) -> str: + """Each sum with the fragment that declares it and the terms, then each fragment. - Both are read off the fragments, so the index cannot name a term or an - owner the files no longer have. + Both are read off the fragments, so an index cannot name a term or an + owner the files no longer have. The owner of a sum is the fragment that + declares it under ``expressions:``, empty or with a body of its own. """ - specs = {path.stem: to_spec(path) for path in sorted(PYPSA.glob('*.yaml'))} - owners: dict[str, tuple[str, tuple[str, ...]]] = {} + specs = {name: to_spec(path) for name, (path, _) in fragments.items()} + href = {name: link for name, (_, link) in fragments.items()} terms: dict[str, dict[str, str]] = {} for name, spec in specs.items(): for hub, entry in spec.given.expressions.items(): if entry.term is not None: terms.setdefault(hub, {})[name] = entry.term - for hub, block in spec.expressions.items(): - if block.expression is None and not block.cases: - owners[hub] = (name, tuple(block.dims or ())) + owners = { + hub: (name, tuple(spec.expressions[hub].dims or ())) + for name, spec in specs.items() + for hub in terms + if hub in spec.expressions + } sums = ['| Sum | Over | Declared in | The terms, by the fragment that adds each |', '| --- | --- | --- | --- |'] for hub, by_fragment in sorted(terms.items(), key=lambda item: -len(item[1])): reader, dims = owners[hub] - cells = ', '.join(f'[`{term}`]({fragment}.md)' for fragment, term in sorted(by_fragment.items())) - sums.append(f'| `{hub}` | `{", ".join(dims)}` | [{reader}]({reader}.md) | {cells} |') + cells = ', '.join(f'[`{term}`]({href[fragment]})' for fragment, term in sorted(by_fragment.items())) + over = f'`{", ".join(dims)}`' if dims else 'nothing: one number' + sums.append(f'| `{hub}` | {over} | [{reader}]({href[reader]}) | {cells} |') files = [ '| Fragment | Parameters | Variables | Constraints | Reads | Adds to |', '| --- | --- | --- | --- | --- | --- |', @@ -148,12 +197,46 @@ def split_index_block() -> str: reads = len(given.parameters) + len(given.variables) + len(given.expressions) + len(given.constraints) adds = ', '.join(f'`{hub}`' for hub, entry in given.expressions.items() if entry.term is not None) files.append( - f'| [{name}]({name}.md) | {len(spec.parameters)} | {len(spec.variables)} | {len(spec.constraints)} ' + f'| [{name}]({href[name]}) | {len(spec.parameters)} | {len(spec.variables)} | {len(spec.constraints)} ' f'| {reads} | {adds} |' ) return '### The sums\n\n' + '\n'.join(sums) + '\n\n### The fragments\n\n' + '\n'.join(files) +def variant_block(page: str) -> str: + """A Calliope patch: the call that lays it, the file, then each declaration it writes as the patched spec prints it. + + A patch that removes a declaration names it in a list, since what is gone + has no line to print. + """ + fragments, before = CALLIOPE_VARIANTS[page] + path = CALLIOPE / 'variants' / f'{Path(page).stem}.yaml' + patches = [CALLIOPE / 'variants' / f'{name}.yaml' for name in before] + [path] + patched = override(merge(fragments), patches) + table = symbols_for(patched, CALLIOPE_SYMBOLS) + extensions = [f.stem for f in fragments if f.parent.name == 'extensions'] + listed = ', '.join(repr(f'extensions/{name}.yaml') for name in extensions) + merged = f'ms.merge(base + [{listed}])' if extensions else 'ms.merge(base)' + laid = ', '.join(repr(f'variants/{patch.name}') for patch in patches) + call = f'ms.override(\n {merged},\n [{laid}],\n)' + written = yaml.safe_load(path.read_text()) + parts = [f'```python\n{call}\n```', f'```yaml title="variants/{path.name}"\n{without_header(path)}\n```'] + removed = [] + for section in ('variables', 'expressions', 'constraints', 'assumptions'): + for name, entry in (written.get(section) or {}).items(): + if entry is None: + removed.append(f'`{name}`') + continue + line = typeset_declaration(patched, name, 'markdown', symbols=table, inline_expressions=False) + parts.append(f'**`{name}`**\n\n```math\n{line}\n```') + if written.get('objective'): + page_math = to_markdown(patched, symbols=table, numbered=False) + parts.append('**The objective**\n\n' + _section(page_math, 'Objective').removeprefix('#### Objective').strip()) + if removed: + parts.append(f'Removed: {", ".join(removed)}.') + return '\n\n'.join(parts) + + def composed_block(fragments: list[Path], patches: dict[str, Path]) -> str: """The spec `merge` returns for *fragments* as YAML, then its document as composed and under each patch. @@ -358,6 +441,12 @@ def block(page: str) -> str: return composed_block(*COMPOSED[page]) if page == SPLIT_INDEX: return split_index_block() + if page == CALLIOPE_INDEX: + return calliope_index_block() + if page in CALLIOPE_VARIANTS: + return variant_block(page) + if CALLIOPE in MODELS[page].parents: + return fragment_block(MODELS[page], CALLIOPE_SYMBOLS) if MODELS[page].parent == LIBRARY: return fragment_block(MODELS[page], LIBRARY_SYMBOLS) if MODELS[page].parent == PYPSA: @@ -373,7 +462,7 @@ def rendered(page: str, text: str) -> str: def pages() -> list[str]: - return [*MODELS, *COMPOSED, *DECLARED, 'operators.md', SPLIT_INDEX] + return [*MODELS, *COMPOSED, *DECLARED, 'operators.md', SPLIT_INDEX, CALLIOPE_INDEX, *CALLIOPE_VARIANTS] def main(argv: list[str] | None = None) -> int: diff --git a/tools/render_tex.py b/tools/render_tex.py index c882cea3..3cfa6032 100644 --- a/tools/render_tex.py +++ b/tools/render_tex.py @@ -23,9 +23,9 @@ CORPUS = ('examples/**/*.yaml', 'tests/typesetting/golden/*.yaml') #: Inside that glob and not specs: the symbol tables `sidecar_for` looks up, -#: and the patches a library's variants are written as, which `override` lays +#: and the patches the variants of a library and of Calliope are written as, which `override` lays #: over a spec rather than anything loading them on their own. -NOT_MODELS = ('examples/symbols', 'examples/library/variants') +NOT_MODELS = ('examples/symbols', 'examples/library/variants', 'examples/calliope/variants') def models() -> list[Path]: From 06ae75d9fdc94927e0d567d2c3446b11d2ea2f5d Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 28 Sep 2026 22:23:27 +0000 Subject: [PATCH 7/9] docs(changelog): name #770 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AvK6QNNFZHJcRzRf3sBbWe --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13b1d594..b7afee46 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(calliope): all of calliope's math is a set of fragments that merge, with its modes laid over them as patches ([#770](https://github.com/energy-models/mathspec/pull/770)) - 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(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)) From 82771c975b529b860a58397ffa3fbb53f201b82b Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 29 Sep 2026 05:21:23 +0000 Subject: [PATCH 8/9] docs(calliope): the ported math carries Calliope's copyright and Apache-2.0 licence Each file under examples/calliope/ names Calliope contributors beside this project, is under Apache-2.0 as Calliope's math is, and says it was changed from Calliope v0.7.0. The pages that print those files are CC-BY-4.0 AND Apache-2.0, and the index says whose math it is. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AvK6QNNFZHJcRzRf3sBbWe --- docs/examples/calliope/area.md | 3 ++- docs/examples/calliope/balance.md | 3 ++- docs/examples/calliope/conversion.md | 3 ++- docs/examples/calliope/cost.md | 3 ++- docs/examples/calliope/demand.md | 3 ++- docs/examples/calliope/export.md | 3 ++- docs/examples/calliope/extensions/annual_energy_balance.md | 3 ++- docs/examples/calliope/extensions/chp_htp.md | 3 ++- .../extensions/demand_share_per_timestep_decision.md | 3 ++- docs/examples/calliope/extensions/fuel_dist.md | 3 ++- docs/examples/calliope/extensions/max_time_varying.md | 3 ++- docs/examples/calliope/extensions/milp.md | 3 ++- .../calliope/extensions/monthly_peak_flow_charge.md | 3 ++- docs/examples/calliope/extensions/net_import_share.md | 3 ++- docs/examples/calliope/extensions/piecewise_linear_costs.md | 3 ++- .../calliope/extensions/piecewise_linear_efficiency.md | 3 ++- docs/examples/calliope/extensions/share_all_timesteps.md | 3 ++- docs/examples/calliope/extensions/share_per_timestep.md | 3 ++- .../calliope/extensions/sos2_piecewise_linear_costs.md | 3 ++- docs/examples/calliope/extensions/uptime_downtime_limits.md | 3 ++- docs/examples/calliope/extensions/urban_scale_chp.md | 3 ++- docs/examples/calliope/feasibility.md | 3 ++- docs/examples/calliope/flows.md | 3 ++- docs/examples/calliope/index.md | 5 +++++ docs/examples/calliope/reporting.md | 3 ++- docs/examples/calliope/settings.md | 3 ++- docs/examples/calliope/storage.md | 3 ++- docs/examples/calliope/supply.md | 3 ++- docs/examples/calliope/supply_storage.md | 3 ++- docs/examples/calliope/transmission.md | 3 ++- docs/examples/calliope/variants/chp_htp.md | 3 ++- docs/examples/calliope/variants/milp.md | 3 ++- docs/examples/calliope/variants/operate.md | 3 ++- docs/examples/calliope/variants/operate_milp.md | 3 ++- docs/examples/calliope/variants/spores.md | 3 ++- docs/examples/calliope/variants/storage_inter_cluster.md | 3 ++- docs/examples/calliope/variants/urban_scale_chp.md | 3 ++- examples/calliope/area.yaml | 6 +++++- examples/calliope/balance.yaml | 6 +++++- examples/calliope/conversion.yaml | 6 +++++- examples/calliope/cost.yaml | 6 +++++- examples/calliope/demand.yaml | 6 +++++- examples/calliope/export.yaml | 6 +++++- examples/calliope/extensions/annual_energy_balance.yaml | 6 +++++- examples/calliope/extensions/chp_htp.yaml | 6 +++++- .../extensions/demand_share_per_timestep_decision.yaml | 6 +++++- examples/calliope/extensions/fuel_dist.yaml | 6 +++++- examples/calliope/extensions/max_time_varying.yaml | 6 +++++- examples/calliope/extensions/milp.yaml | 6 +++++- examples/calliope/extensions/monthly_peak_flow_charge.yaml | 6 +++++- examples/calliope/extensions/net_import_share.yaml | 6 +++++- examples/calliope/extensions/piecewise_linear_costs.yaml | 6 +++++- .../calliope/extensions/piecewise_linear_efficiency.yaml | 6 +++++- examples/calliope/extensions/share_all_timesteps.yaml | 6 +++++- examples/calliope/extensions/share_per_timestep.yaml | 6 +++++- .../calliope/extensions/sos2_piecewise_linear_costs.yaml | 6 +++++- examples/calliope/extensions/uptime_downtime_limits.yaml | 6 +++++- examples/calliope/extensions/urban_scale_chp.yaml | 6 +++++- examples/calliope/feasibility.yaml | 6 +++++- examples/calliope/flows.yaml | 6 +++++- examples/calliope/reporting.yaml | 6 +++++- examples/calliope/settings.yaml | 6 +++++- examples/calliope/storage.yaml | 6 +++++- examples/calliope/supply.yaml | 6 +++++- examples/calliope/supply_storage.yaml | 6 +++++- examples/calliope/transmission.yaml | 6 +++++- examples/calliope/variants/chp_htp.yaml | 6 +++++- examples/calliope/variants/milp.yaml | 6 +++++- examples/calliope/variants/operate.yaml | 6 +++++- examples/calliope/variants/operate_milp.yaml | 6 +++++- examples/calliope/variants/spores.yaml | 6 +++++- examples/calliope/variants/storage_inter_cluster.yaml | 6 +++++- examples/calliope/variants/urban_scale_chp.yaml | 6 +++++- 73 files changed, 257 insertions(+), 72 deletions(-) diff --git a/docs/examples/calliope/area.md b/docs/examples/calliope/area.md index b3821296..8547fbaf 100644 --- a/docs/examples/calliope/area.md +++ b/docs/examples/calliope/area.md @@ -1,6 +1,7 @@ # Area diff --git a/docs/examples/calliope/balance.md b/docs/examples/calliope/balance.md index 809146d3..a014562c 100644 --- a/docs/examples/calliope/balance.md +++ b/docs/examples/calliope/balance.md @@ -1,6 +1,7 @@ # The balance diff --git a/docs/examples/calliope/conversion.md b/docs/examples/calliope/conversion.md index 9cac290d..31d2e322 100644 --- a/docs/examples/calliope/conversion.md +++ b/docs/examples/calliope/conversion.md @@ -1,6 +1,7 @@ # Conversion diff --git a/docs/examples/calliope/cost.md b/docs/examples/calliope/cost.md index 4eb06dc6..cae38b9a 100644 --- a/docs/examples/calliope/cost.md +++ b/docs/examples/calliope/cost.md @@ -1,6 +1,7 @@ # The cost diff --git a/docs/examples/calliope/demand.md b/docs/examples/calliope/demand.md index f8c3a165..4ac445f7 100644 --- a/docs/examples/calliope/demand.md +++ b/docs/examples/calliope/demand.md @@ -1,6 +1,7 @@ # Demand diff --git a/docs/examples/calliope/export.md b/docs/examples/calliope/export.md index fdeab018..04a17aab 100644 --- a/docs/examples/calliope/export.md +++ b/docs/examples/calliope/export.md @@ -1,6 +1,7 @@ # Export diff --git a/docs/examples/calliope/extensions/annual_energy_balance.md b/docs/examples/calliope/extensions/annual_energy_balance.md index 554b75ec..9c3e34fd 100644 --- a/docs/examples/calliope/extensions/annual_energy_balance.md +++ b/docs/examples/calliope/extensions/annual_energy_balance.md @@ -1,6 +1,7 @@ # Annual energy balance diff --git a/docs/examples/calliope/extensions/chp_htp.md b/docs/examples/calliope/extensions/chp_htp.md index eafc7b8e..a21d3746 100644 --- a/docs/examples/calliope/extensions/chp_htp.md +++ b/docs/examples/calliope/extensions/chp_htp.md @@ -1,6 +1,7 @@ # CHP plants diff --git a/docs/examples/calliope/extensions/demand_share_per_timestep_decision.md b/docs/examples/calliope/extensions/demand_share_per_timestep_decision.md index a5f7f4f4..4284fb3a 100644 --- a/docs/examples/calliope/extensions/demand_share_per_timestep_decision.md +++ b/docs/examples/calliope/extensions/demand_share_per_timestep_decision.md @@ -1,6 +1,7 @@ # Demand share as a decision diff --git a/docs/examples/calliope/extensions/fuel_dist.md b/docs/examples/calliope/extensions/fuel_dist.md index a1b1a1be..703f79e4 100644 --- a/docs/examples/calliope/extensions/fuel_dist.md +++ b/docs/examples/calliope/extensions/fuel_dist.md @@ -1,6 +1,7 @@ # Fuel distribution diff --git a/docs/examples/calliope/extensions/max_time_varying.md b/docs/examples/calliope/extensions/max_time_varying.md index 8a831db4..54e9897d 100644 --- a/docs/examples/calliope/extensions/max_time_varying.md +++ b/docs/examples/calliope/extensions/max_time_varying.md @@ -1,6 +1,7 @@ # Time-varying flow limit diff --git a/docs/examples/calliope/extensions/milp.md b/docs/examples/calliope/extensions/milp.md index 1c703828..8f77bc8b 100644 --- a/docs/examples/calliope/extensions/milp.md +++ b/docs/examples/calliope/extensions/milp.md @@ -1,6 +1,7 @@ # MILP diff --git a/docs/examples/calliope/extensions/monthly_peak_flow_charge.md b/docs/examples/calliope/extensions/monthly_peak_flow_charge.md index 31f546cb..cc729284 100644 --- a/docs/examples/calliope/extensions/monthly_peak_flow_charge.md +++ b/docs/examples/calliope/extensions/monthly_peak_flow_charge.md @@ -1,6 +1,7 @@ # Monthly peak flow charge diff --git a/docs/examples/calliope/extensions/net_import_share.md b/docs/examples/calliope/extensions/net_import_share.md index 0ce17f53..8bbad733 100644 --- a/docs/examples/calliope/extensions/net_import_share.md +++ b/docs/examples/calliope/extensions/net_import_share.md @@ -1,6 +1,7 @@ # Net import share diff --git a/docs/examples/calliope/extensions/piecewise_linear_costs.md b/docs/examples/calliope/extensions/piecewise_linear_costs.md index b4472b29..e3991760 100644 --- a/docs/examples/calliope/extensions/piecewise_linear_costs.md +++ b/docs/examples/calliope/extensions/piecewise_linear_costs.md @@ -1,6 +1,7 @@ # Piecewise linear costs diff --git a/docs/examples/calliope/extensions/piecewise_linear_efficiency.md b/docs/examples/calliope/extensions/piecewise_linear_efficiency.md index 7000baf9..93ddb157 100644 --- a/docs/examples/calliope/extensions/piecewise_linear_efficiency.md +++ b/docs/examples/calliope/extensions/piecewise_linear_efficiency.md @@ -1,6 +1,7 @@ # Piecewise linear efficiency diff --git a/docs/examples/calliope/extensions/share_all_timesteps.md b/docs/examples/calliope/extensions/share_all_timesteps.md index 0845ae1c..58506b20 100644 --- a/docs/examples/calliope/extensions/share_all_timesteps.md +++ b/docs/examples/calliope/extensions/share_all_timesteps.md @@ -1,6 +1,7 @@ # Flow share over the whole time diff --git a/docs/examples/calliope/extensions/share_per_timestep.md b/docs/examples/calliope/extensions/share_per_timestep.md index b42ed8e9..339c6e95 100644 --- a/docs/examples/calliope/extensions/share_per_timestep.md +++ b/docs/examples/calliope/extensions/share_per_timestep.md @@ -1,6 +1,7 @@ # Flow share per time step diff --git a/docs/examples/calliope/extensions/sos2_piecewise_linear_costs.md b/docs/examples/calliope/extensions/sos2_piecewise_linear_costs.md index 5a2c5fde..b2f8ca94 100644 --- a/docs/examples/calliope/extensions/sos2_piecewise_linear_costs.md +++ b/docs/examples/calliope/extensions/sos2_piecewise_linear_costs.md @@ -1,6 +1,7 @@ # Piecewise linear costs with SOS2 diff --git a/docs/examples/calliope/extensions/uptime_downtime_limits.md b/docs/examples/calliope/extensions/uptime_downtime_limits.md index 4366c635..a117ae59 100644 --- a/docs/examples/calliope/extensions/uptime_downtime_limits.md +++ b/docs/examples/calliope/extensions/uptime_downtime_limits.md @@ -1,6 +1,7 @@ # Uptime and downtime limits diff --git a/docs/examples/calliope/extensions/urban_scale_chp.md b/docs/examples/calliope/extensions/urban_scale_chp.md index 154d8e44..c31ef7f5 100644 --- a/docs/examples/calliope/extensions/urban_scale_chp.md +++ b/docs/examples/calliope/extensions/urban_scale_chp.md @@ -1,6 +1,7 @@ # Urban-scale CHP diff --git a/docs/examples/calliope/feasibility.md b/docs/examples/calliope/feasibility.md index 8fc74886..9bc6ce20 100644 --- a/docs/examples/calliope/feasibility.md +++ b/docs/examples/calliope/feasibility.md @@ -1,6 +1,7 @@ # Feasibility diff --git a/docs/examples/calliope/flows.md b/docs/examples/calliope/flows.md index 903ff42e..da4b2c85 100644 --- a/docs/examples/calliope/flows.md +++ b/docs/examples/calliope/flows.md @@ -1,6 +1,7 @@ # Flows diff --git a/docs/examples/calliope/index.md b/docs/examples/calliope/index.md index 4662f132..603c5287 100644 --- a/docs/examples/calliope/index.md +++ b/docs/examples/calliope/index.md @@ -12,6 +12,11 @@ is all of it, from Calliope `v0.7.0` (`src/calliope/math/` and [The port record](port.md) lists every Calliope block with how it is stated here, and what mathspec is missing where the port is not one block for one. +The math is Calliope's, copyright Calliope contributors, under the +[Apache License 2.0](https://github.com/calliope-project/calliope/blob/main/LICENSE). +The files under `examples/calliope/` are changed from it and are under the same +licence. [The port record](port.md) says what changed. + Calliope composes its math by overriding: a mode or an example restates a base block whole to change it. Here, three kinds of file do that work, and [the PyPSA split](../pypsa/index.md) uses the first two. diff --git a/docs/examples/calliope/reporting.md b/docs/examples/calliope/reporting.md index e8cc4d0a..7fe48696 100644 --- a/docs/examples/calliope/reporting.md +++ b/docs/examples/calliope/reporting.md @@ -1,6 +1,7 @@ # Reporting diff --git a/docs/examples/calliope/settings.md b/docs/examples/calliope/settings.md index bb7cb004..95b67ce4 100644 --- a/docs/examples/calliope/settings.md +++ b/docs/examples/calliope/settings.md @@ -1,6 +1,7 @@ # Settings diff --git a/docs/examples/calliope/storage.md b/docs/examples/calliope/storage.md index 8df957ed..a03d76e8 100644 --- a/docs/examples/calliope/storage.md +++ b/docs/examples/calliope/storage.md @@ -1,6 +1,7 @@ # Storage diff --git a/docs/examples/calliope/supply.md b/docs/examples/calliope/supply.md index 044dd559..4fbfdf99 100644 --- a/docs/examples/calliope/supply.md +++ b/docs/examples/calliope/supply.md @@ -1,6 +1,7 @@ # Supply diff --git a/docs/examples/calliope/supply_storage.md b/docs/examples/calliope/supply_storage.md index 89ec8c69..c69c3c63 100644 --- a/docs/examples/calliope/supply_storage.md +++ b/docs/examples/calliope/supply_storage.md @@ -1,6 +1,7 @@ # Supply with storage diff --git a/docs/examples/calliope/transmission.md b/docs/examples/calliope/transmission.md index 13764bc4..b13ec174 100644 --- a/docs/examples/calliope/transmission.md +++ b/docs/examples/calliope/transmission.md @@ -1,6 +1,7 @@ # Transmission diff --git a/docs/examples/calliope/variants/chp_htp.md b/docs/examples/calliope/variants/chp_htp.md index 42a7b3f9..d53e4f9f 100644 --- a/docs/examples/calliope/variants/chp_htp.md +++ b/docs/examples/calliope/variants/chp_htp.md @@ -1,6 +1,7 @@ # The CHP patch diff --git a/docs/examples/calliope/variants/milp.md b/docs/examples/calliope/variants/milp.md index 0ddddeb8..bf52b19d 100644 --- a/docs/examples/calliope/variants/milp.md +++ b/docs/examples/calliope/variants/milp.md @@ -1,6 +1,7 @@ # The MILP patch diff --git a/docs/examples/calliope/variants/operate.md b/docs/examples/calliope/variants/operate.md index 5c83e281..45573921 100644 --- a/docs/examples/calliope/variants/operate.md +++ b/docs/examples/calliope/variants/operate.md @@ -1,6 +1,7 @@ # Operate mode diff --git a/docs/examples/calliope/variants/operate_milp.md b/docs/examples/calliope/variants/operate_milp.md index baf3c1de..db2cb888 100644 --- a/docs/examples/calliope/variants/operate_milp.md +++ b/docs/examples/calliope/variants/operate_milp.md @@ -1,6 +1,7 @@ # Operate mode, with MILP diff --git a/docs/examples/calliope/variants/spores.md b/docs/examples/calliope/variants/spores.md index 990b46de..c17d1c5f 100644 --- a/docs/examples/calliope/variants/spores.md +++ b/docs/examples/calliope/variants/spores.md @@ -1,6 +1,7 @@ # SPORES diff --git a/docs/examples/calliope/variants/storage_inter_cluster.md b/docs/examples/calliope/variants/storage_inter_cluster.md index ae630f19..7e98a509 100644 --- a/docs/examples/calliope/variants/storage_inter_cluster.md +++ b/docs/examples/calliope/variants/storage_inter_cluster.md @@ -1,6 +1,7 @@ # Inter-cluster storage diff --git a/docs/examples/calliope/variants/urban_scale_chp.md b/docs/examples/calliope/variants/urban_scale_chp.md index e97dc52e..b33b0db4 100644 --- a/docs/examples/calliope/variants/urban_scale_chp.md +++ b/docs/examples/calliope/variants/urban_scale_chp.md @@ -1,6 +1,7 @@ # The urban-scale CHP patch diff --git a/examples/calliope/area.yaml b/examples/calliope/area.yaml index f7e94268..a89f5b7e 100644 --- a/examples/calliope/area.yaml +++ b/examples/calliope/area.yaml @@ -1,6 +1,10 @@ +# SPDX-FileCopyrightText: Calliope contributors # SPDX-FileCopyrightText: mathspec Contributors # -# SPDX-License-Identifier: MIT +# SPDX-License-Identifier: Apache-2.0 +# +# Ported from the math of Calliope v0.7.0, https://github.com/calliope-project/calliope, +# and changed to mathspec's language. docs/examples/calliope/port.md records each change. dimensions: nodes: diff --git a/examples/calliope/balance.yaml b/examples/calliope/balance.yaml index 238f3695..3e6bffd8 100644 --- a/examples/calliope/balance.yaml +++ b/examples/calliope/balance.yaml @@ -1,6 +1,10 @@ +# SPDX-FileCopyrightText: Calliope contributors # SPDX-FileCopyrightText: mathspec Contributors # -# SPDX-License-Identifier: MIT +# SPDX-License-Identifier: Apache-2.0 +# +# Ported from the math of Calliope v0.7.0, https://github.com/calliope-project/calliope, +# and changed to mathspec's language. docs/examples/calliope/port.md records each change. dimensions: nodes: diff --git a/examples/calliope/conversion.yaml b/examples/calliope/conversion.yaml index e201f5a1..c73050e3 100644 --- a/examples/calliope/conversion.yaml +++ b/examples/calliope/conversion.yaml @@ -1,6 +1,10 @@ +# SPDX-FileCopyrightText: Calliope contributors # SPDX-FileCopyrightText: mathspec Contributors # -# SPDX-License-Identifier: MIT +# SPDX-License-Identifier: Apache-2.0 +# +# Ported from the math of Calliope v0.7.0, https://github.com/calliope-project/calliope, +# and changed to mathspec's language. docs/examples/calliope/port.md records each change. dimensions: nodes: diff --git a/examples/calliope/cost.yaml b/examples/calliope/cost.yaml index ded28243..de4124d2 100644 --- a/examples/calliope/cost.yaml +++ b/examples/calliope/cost.yaml @@ -1,6 +1,10 @@ +# SPDX-FileCopyrightText: Calliope contributors # SPDX-FileCopyrightText: mathspec Contributors # -# SPDX-License-Identifier: MIT +# SPDX-License-Identifier: Apache-2.0 +# +# Ported from the math of Calliope v0.7.0, https://github.com/calliope-project/calliope, +# and changed to mathspec's language. docs/examples/calliope/port.md records each change. dimensions: nodes: diff --git a/examples/calliope/demand.yaml b/examples/calliope/demand.yaml index da76df02..ab8994bc 100644 --- a/examples/calliope/demand.yaml +++ b/examples/calliope/demand.yaml @@ -1,6 +1,10 @@ +# SPDX-FileCopyrightText: Calliope contributors # SPDX-FileCopyrightText: mathspec Contributors # -# SPDX-License-Identifier: MIT +# SPDX-License-Identifier: Apache-2.0 +# +# Ported from the math of Calliope v0.7.0, https://github.com/calliope-project/calliope, +# and changed to mathspec's language. docs/examples/calliope/port.md records each change. dimensions: nodes: diff --git a/examples/calliope/export.yaml b/examples/calliope/export.yaml index 8cc30ea8..792d26d7 100644 --- a/examples/calliope/export.yaml +++ b/examples/calliope/export.yaml @@ -1,6 +1,10 @@ +# SPDX-FileCopyrightText: Calliope contributors # SPDX-FileCopyrightText: mathspec Contributors # -# SPDX-License-Identifier: MIT +# SPDX-License-Identifier: Apache-2.0 +# +# Ported from the math of Calliope v0.7.0, https://github.com/calliope-project/calliope, +# and changed to mathspec's language. docs/examples/calliope/port.md records each change. dimensions: nodes: diff --git a/examples/calliope/extensions/annual_energy_balance.yaml b/examples/calliope/extensions/annual_energy_balance.yaml index ab39148e..c20611c2 100644 --- a/examples/calliope/extensions/annual_energy_balance.yaml +++ b/examples/calliope/extensions/annual_energy_balance.yaml @@ -1,6 +1,10 @@ +# SPDX-FileCopyrightText: Calliope contributors # SPDX-FileCopyrightText: mathspec Contributors # -# SPDX-License-Identifier: MIT +# SPDX-License-Identifier: Apache-2.0 +# +# Ported from the math of Calliope v0.7.0, https://github.com/calliope-project/calliope, +# and changed to mathspec's language. docs/examples/calliope/port.md records each change. dimensions: nodes: diff --git a/examples/calliope/extensions/chp_htp.yaml b/examples/calliope/extensions/chp_htp.yaml index 361cc151..d174b61f 100644 --- a/examples/calliope/extensions/chp_htp.yaml +++ b/examples/calliope/extensions/chp_htp.yaml @@ -1,6 +1,10 @@ +# SPDX-FileCopyrightText: Calliope contributors # SPDX-FileCopyrightText: mathspec Contributors # -# SPDX-License-Identifier: MIT +# SPDX-License-Identifier: Apache-2.0 +# +# Ported from the math of Calliope v0.7.0, https://github.com/calliope-project/calliope, +# and changed to mathspec's language. docs/examples/calliope/port.md records each change. dimensions: nodes: diff --git a/examples/calliope/extensions/demand_share_per_timestep_decision.yaml b/examples/calliope/extensions/demand_share_per_timestep_decision.yaml index 259801e6..fbcd2637 100644 --- a/examples/calliope/extensions/demand_share_per_timestep_decision.yaml +++ b/examples/calliope/extensions/demand_share_per_timestep_decision.yaml @@ -1,6 +1,10 @@ +# SPDX-FileCopyrightText: Calliope contributors # SPDX-FileCopyrightText: mathspec Contributors # -# SPDX-License-Identifier: MIT +# SPDX-License-Identifier: Apache-2.0 +# +# Ported from the math of Calliope v0.7.0, https://github.com/calliope-project/calliope, +# and changed to mathspec's language. docs/examples/calliope/port.md records each change. dimensions: nodes: diff --git a/examples/calliope/extensions/fuel_dist.yaml b/examples/calliope/extensions/fuel_dist.yaml index 079812ec..a9fca0e0 100644 --- a/examples/calliope/extensions/fuel_dist.yaml +++ b/examples/calliope/extensions/fuel_dist.yaml @@ -1,6 +1,10 @@ +# SPDX-FileCopyrightText: Calliope contributors # SPDX-FileCopyrightText: mathspec Contributors # -# SPDX-License-Identifier: MIT +# SPDX-License-Identifier: Apache-2.0 +# +# Ported from the math of Calliope v0.7.0, https://github.com/calliope-project/calliope, +# and changed to mathspec's language. docs/examples/calliope/port.md records each change. dimensions: nodes: diff --git a/examples/calliope/extensions/max_time_varying.yaml b/examples/calliope/extensions/max_time_varying.yaml index c973f75e..f4fa6150 100644 --- a/examples/calliope/extensions/max_time_varying.yaml +++ b/examples/calliope/extensions/max_time_varying.yaml @@ -1,6 +1,10 @@ +# SPDX-FileCopyrightText: Calliope contributors # SPDX-FileCopyrightText: mathspec Contributors # -# SPDX-License-Identifier: MIT +# SPDX-License-Identifier: Apache-2.0 +# +# Ported from the math of Calliope v0.7.0, https://github.com/calliope-project/calliope, +# and changed to mathspec's language. docs/examples/calliope/port.md records each change. dimensions: nodes: diff --git a/examples/calliope/extensions/milp.yaml b/examples/calliope/extensions/milp.yaml index 36cd9b75..79ec713a 100644 --- a/examples/calliope/extensions/milp.yaml +++ b/examples/calliope/extensions/milp.yaml @@ -1,6 +1,10 @@ +# SPDX-FileCopyrightText: Calliope contributors # SPDX-FileCopyrightText: mathspec Contributors # -# SPDX-License-Identifier: MIT +# SPDX-License-Identifier: Apache-2.0 +# +# Ported from the math of Calliope v0.7.0, https://github.com/calliope-project/calliope, +# and changed to mathspec's language. docs/examples/calliope/port.md records each change. dimensions: nodes: diff --git a/examples/calliope/extensions/monthly_peak_flow_charge.yaml b/examples/calliope/extensions/monthly_peak_flow_charge.yaml index 8982b818..97bbbaa6 100644 --- a/examples/calliope/extensions/monthly_peak_flow_charge.yaml +++ b/examples/calliope/extensions/monthly_peak_flow_charge.yaml @@ -1,6 +1,10 @@ +# SPDX-FileCopyrightText: Calliope contributors # SPDX-FileCopyrightText: mathspec Contributors # -# SPDX-License-Identifier: MIT +# SPDX-License-Identifier: Apache-2.0 +# +# Ported from the math of Calliope v0.7.0, https://github.com/calliope-project/calliope, +# and changed to mathspec's language. docs/examples/calliope/port.md records each change. dimensions: nodes: diff --git a/examples/calliope/extensions/net_import_share.yaml b/examples/calliope/extensions/net_import_share.yaml index dc2be0ce..f857317d 100644 --- a/examples/calliope/extensions/net_import_share.yaml +++ b/examples/calliope/extensions/net_import_share.yaml @@ -1,6 +1,10 @@ +# SPDX-FileCopyrightText: Calliope contributors # SPDX-FileCopyrightText: mathspec Contributors # -# SPDX-License-Identifier: MIT +# SPDX-License-Identifier: Apache-2.0 +# +# Ported from the math of Calliope v0.7.0, https://github.com/calliope-project/calliope, +# and changed to mathspec's language. docs/examples/calliope/port.md records each change. dimensions: nodes: diff --git a/examples/calliope/extensions/piecewise_linear_costs.yaml b/examples/calliope/extensions/piecewise_linear_costs.yaml index ae1dd86d..63215fef 100644 --- a/examples/calliope/extensions/piecewise_linear_costs.yaml +++ b/examples/calliope/extensions/piecewise_linear_costs.yaml @@ -1,6 +1,10 @@ +# SPDX-FileCopyrightText: Calliope contributors # SPDX-FileCopyrightText: mathspec Contributors # -# SPDX-License-Identifier: MIT +# SPDX-License-Identifier: Apache-2.0 +# +# Ported from the math of Calliope v0.7.0, https://github.com/calliope-project/calliope, +# and changed to mathspec's language. docs/examples/calliope/port.md records each change. dimensions: nodes: diff --git a/examples/calliope/extensions/piecewise_linear_efficiency.yaml b/examples/calliope/extensions/piecewise_linear_efficiency.yaml index 1c8089f7..261ef5b1 100644 --- a/examples/calliope/extensions/piecewise_linear_efficiency.yaml +++ b/examples/calliope/extensions/piecewise_linear_efficiency.yaml @@ -1,6 +1,10 @@ +# SPDX-FileCopyrightText: Calliope contributors # SPDX-FileCopyrightText: mathspec Contributors # -# SPDX-License-Identifier: MIT +# SPDX-License-Identifier: Apache-2.0 +# +# Ported from the math of Calliope v0.7.0, https://github.com/calliope-project/calliope, +# and changed to mathspec's language. docs/examples/calliope/port.md records each change. dimensions: nodes: diff --git a/examples/calliope/extensions/share_all_timesteps.yaml b/examples/calliope/extensions/share_all_timesteps.yaml index 0f597358..447ebb71 100644 --- a/examples/calliope/extensions/share_all_timesteps.yaml +++ b/examples/calliope/extensions/share_all_timesteps.yaml @@ -1,6 +1,10 @@ +# SPDX-FileCopyrightText: Calliope contributors # SPDX-FileCopyrightText: mathspec Contributors # -# SPDX-License-Identifier: MIT +# SPDX-License-Identifier: Apache-2.0 +# +# Ported from the math of Calliope v0.7.0, https://github.com/calliope-project/calliope, +# and changed to mathspec's language. docs/examples/calliope/port.md records each change. dimensions: nodes: diff --git a/examples/calliope/extensions/share_per_timestep.yaml b/examples/calliope/extensions/share_per_timestep.yaml index 1e72525e..b916826f 100644 --- a/examples/calliope/extensions/share_per_timestep.yaml +++ b/examples/calliope/extensions/share_per_timestep.yaml @@ -1,6 +1,10 @@ +# SPDX-FileCopyrightText: Calliope contributors # SPDX-FileCopyrightText: mathspec Contributors # -# SPDX-License-Identifier: MIT +# SPDX-License-Identifier: Apache-2.0 +# +# Ported from the math of Calliope v0.7.0, https://github.com/calliope-project/calliope, +# and changed to mathspec's language. docs/examples/calliope/port.md records each change. dimensions: nodes: diff --git a/examples/calliope/extensions/sos2_piecewise_linear_costs.yaml b/examples/calliope/extensions/sos2_piecewise_linear_costs.yaml index 0e7b5ec7..65bae56d 100644 --- a/examples/calliope/extensions/sos2_piecewise_linear_costs.yaml +++ b/examples/calliope/extensions/sos2_piecewise_linear_costs.yaml @@ -1,6 +1,10 @@ +# SPDX-FileCopyrightText: Calliope contributors # SPDX-FileCopyrightText: mathspec Contributors # -# SPDX-License-Identifier: MIT +# SPDX-License-Identifier: Apache-2.0 +# +# Ported from the math of Calliope v0.7.0, https://github.com/calliope-project/calliope, +# and changed to mathspec's language. docs/examples/calliope/port.md records each change. dimensions: nodes: diff --git a/examples/calliope/extensions/uptime_downtime_limits.yaml b/examples/calliope/extensions/uptime_downtime_limits.yaml index ddc3de39..c7f9eee4 100644 --- a/examples/calliope/extensions/uptime_downtime_limits.yaml +++ b/examples/calliope/extensions/uptime_downtime_limits.yaml @@ -1,6 +1,10 @@ +# SPDX-FileCopyrightText: Calliope contributors # SPDX-FileCopyrightText: mathspec Contributors # -# SPDX-License-Identifier: MIT +# SPDX-License-Identifier: Apache-2.0 +# +# Ported from the math of Calliope v0.7.0, https://github.com/calliope-project/calliope, +# and changed to mathspec's language. docs/examples/calliope/port.md records each change. dimensions: nodes: diff --git a/examples/calliope/extensions/urban_scale_chp.yaml b/examples/calliope/extensions/urban_scale_chp.yaml index 7fe02187..49a4a769 100644 --- a/examples/calliope/extensions/urban_scale_chp.yaml +++ b/examples/calliope/extensions/urban_scale_chp.yaml @@ -1,6 +1,10 @@ +# SPDX-FileCopyrightText: Calliope contributors # SPDX-FileCopyrightText: mathspec Contributors # -# SPDX-License-Identifier: MIT +# SPDX-License-Identifier: Apache-2.0 +# +# Ported from the math of Calliope v0.7.0, https://github.com/calliope-project/calliope, +# and changed to mathspec's language. docs/examples/calliope/port.md records each change. dimensions: nodes: diff --git a/examples/calliope/feasibility.yaml b/examples/calliope/feasibility.yaml index 211579df..8da98d16 100644 --- a/examples/calliope/feasibility.yaml +++ b/examples/calliope/feasibility.yaml @@ -1,6 +1,10 @@ +# SPDX-FileCopyrightText: Calliope contributors # SPDX-FileCopyrightText: mathspec Contributors # -# SPDX-License-Identifier: MIT +# SPDX-License-Identifier: Apache-2.0 +# +# Ported from the math of Calliope v0.7.0, https://github.com/calliope-project/calliope, +# and changed to mathspec's language. docs/examples/calliope/port.md records each change. dimensions: nodes: diff --git a/examples/calliope/flows.yaml b/examples/calliope/flows.yaml index a8b89262..6a7b0456 100644 --- a/examples/calliope/flows.yaml +++ b/examples/calliope/flows.yaml @@ -1,6 +1,10 @@ +# SPDX-FileCopyrightText: Calliope contributors # SPDX-FileCopyrightText: mathspec Contributors # -# SPDX-License-Identifier: MIT +# SPDX-License-Identifier: Apache-2.0 +# +# Ported from the math of Calliope v0.7.0, https://github.com/calliope-project/calliope, +# and changed to mathspec's language. docs/examples/calliope/port.md records each change. dimensions: nodes: diff --git a/examples/calliope/reporting.yaml b/examples/calliope/reporting.yaml index 7efc96a7..8da89eae 100644 --- a/examples/calliope/reporting.yaml +++ b/examples/calliope/reporting.yaml @@ -1,6 +1,10 @@ +# SPDX-FileCopyrightText: Calliope contributors # SPDX-FileCopyrightText: mathspec Contributors # -# SPDX-License-Identifier: MIT +# SPDX-License-Identifier: Apache-2.0 +# +# Ported from the math of Calliope v0.7.0, https://github.com/calliope-project/calliope, +# and changed to mathspec's language. docs/examples/calliope/port.md records each change. dimensions: nodes: diff --git a/examples/calliope/settings.yaml b/examples/calliope/settings.yaml index 5530070d..8da8adde 100644 --- a/examples/calliope/settings.yaml +++ b/examples/calliope/settings.yaml @@ -1,6 +1,10 @@ +# SPDX-FileCopyrightText: Calliope contributors # SPDX-FileCopyrightText: mathspec Contributors # -# SPDX-License-Identifier: MIT +# SPDX-License-Identifier: Apache-2.0 +# +# Ported from the math of Calliope v0.7.0, https://github.com/calliope-project/calliope, +# and changed to mathspec's language. docs/examples/calliope/port.md records each change. dimensions: timesteps: diff --git a/examples/calliope/storage.yaml b/examples/calliope/storage.yaml index bbc93ced..76853e97 100644 --- a/examples/calliope/storage.yaml +++ b/examples/calliope/storage.yaml @@ -1,6 +1,10 @@ +# SPDX-FileCopyrightText: Calliope contributors # SPDX-FileCopyrightText: mathspec Contributors # -# SPDX-License-Identifier: MIT +# SPDX-License-Identifier: Apache-2.0 +# +# Ported from the math of Calliope v0.7.0, https://github.com/calliope-project/calliope, +# and changed to mathspec's language. docs/examples/calliope/port.md records each change. dimensions: nodes: diff --git a/examples/calliope/supply.yaml b/examples/calliope/supply.yaml index a2027768..2b4fe912 100644 --- a/examples/calliope/supply.yaml +++ b/examples/calliope/supply.yaml @@ -1,6 +1,10 @@ +# SPDX-FileCopyrightText: Calliope contributors # SPDX-FileCopyrightText: mathspec Contributors # -# SPDX-License-Identifier: MIT +# SPDX-License-Identifier: Apache-2.0 +# +# Ported from the math of Calliope v0.7.0, https://github.com/calliope-project/calliope, +# and changed to mathspec's language. docs/examples/calliope/port.md records each change. dimensions: nodes: diff --git a/examples/calliope/supply_storage.yaml b/examples/calliope/supply_storage.yaml index bae741bc..71972b24 100644 --- a/examples/calliope/supply_storage.yaml +++ b/examples/calliope/supply_storage.yaml @@ -1,6 +1,10 @@ +# SPDX-FileCopyrightText: Calliope contributors # SPDX-FileCopyrightText: mathspec Contributors # -# SPDX-License-Identifier: MIT +# SPDX-License-Identifier: Apache-2.0 +# +# Ported from the math of Calliope v0.7.0, https://github.com/calliope-project/calliope, +# and changed to mathspec's language. docs/examples/calliope/port.md records each change. dimensions: nodes: diff --git a/examples/calliope/transmission.yaml b/examples/calliope/transmission.yaml index 41456638..76efa7f8 100644 --- a/examples/calliope/transmission.yaml +++ b/examples/calliope/transmission.yaml @@ -1,6 +1,10 @@ +# SPDX-FileCopyrightText: Calliope contributors # SPDX-FileCopyrightText: mathspec Contributors # -# SPDX-License-Identifier: MIT +# SPDX-License-Identifier: Apache-2.0 +# +# Ported from the math of Calliope v0.7.0, https://github.com/calliope-project/calliope, +# and changed to mathspec's language. docs/examples/calliope/port.md records each change. dimensions: nodes: diff --git a/examples/calliope/variants/chp_htp.yaml b/examples/calliope/variants/chp_htp.yaml index 696753bf..c81b13d8 100644 --- a/examples/calliope/variants/chp_htp.yaml +++ b/examples/calliope/variants/chp_htp.yaml @@ -1,6 +1,10 @@ +# SPDX-FileCopyrightText: Calliope contributors # SPDX-FileCopyrightText: mathspec Contributors # -# SPDX-License-Identifier: MIT +# SPDX-License-Identifier: Apache-2.0 +# +# Ported from the math of Calliope v0.7.0, https://github.com/calliope-project/calliope, +# and changed to mathspec's language. docs/examples/calliope/port.md records each change. constraints: balance_conversion: diff --git a/examples/calliope/variants/milp.yaml b/examples/calliope/variants/milp.yaml index 0e2a2374..7c806b93 100644 --- a/examples/calliope/variants/milp.yaml +++ b/examples/calliope/variants/milp.yaml @@ -1,6 +1,10 @@ +# SPDX-FileCopyrightText: Calliope contributors # SPDX-FileCopyrightText: mathspec Contributors # -# SPDX-License-Identifier: MIT +# SPDX-License-Identifier: Apache-2.0 +# +# Ported from the math of Calliope v0.7.0, https://github.com/calliope-project/calliope, +# and changed to mathspec's language. docs/examples/calliope/port.md records each change. parameters: flow_cap_min: diff --git a/examples/calliope/variants/operate.yaml b/examples/calliope/variants/operate.yaml index 85ddf239..52726725 100644 --- a/examples/calliope/variants/operate.yaml +++ b/examples/calliope/variants/operate.yaml @@ -1,6 +1,10 @@ +# SPDX-FileCopyrightText: Calliope contributors # SPDX-FileCopyrightText: mathspec Contributors # -# SPDX-License-Identifier: MIT +# SPDX-License-Identifier: Apache-2.0 +# +# Ported from the math of Calliope v0.7.0, https://github.com/calliope-project/calliope, +# and changed to mathspec's language. docs/examples/calliope/port.md records each change. parameters: flow_cap: diff --git a/examples/calliope/variants/operate_milp.yaml b/examples/calliope/variants/operate_milp.yaml index 152efb00..78cc8bae 100644 --- a/examples/calliope/variants/operate_milp.yaml +++ b/examples/calliope/variants/operate_milp.yaml @@ -1,6 +1,10 @@ +# SPDX-FileCopyrightText: Calliope contributors # SPDX-FileCopyrightText: mathspec Contributors # -# SPDX-License-Identifier: MIT +# SPDX-License-Identifier: Apache-2.0 +# +# Ported from the math of Calliope v0.7.0, https://github.com/calliope-project/calliope, +# and changed to mathspec's language. docs/examples/calliope/port.md records each change. parameters: purchased_units: diff --git a/examples/calliope/variants/spores.yaml b/examples/calliope/variants/spores.yaml index 88cc5463..82a19ce1 100644 --- a/examples/calliope/variants/spores.yaml +++ b/examples/calliope/variants/spores.yaml @@ -1,6 +1,10 @@ +# SPDX-FileCopyrightText: Calliope contributors # SPDX-FileCopyrightText: mathspec Contributors # -# SPDX-License-Identifier: MIT +# SPDX-License-Identifier: Apache-2.0 +# +# Ported from the math of Calliope v0.7.0, https://github.com/calliope-project/calliope, +# and changed to mathspec's language. docs/examples/calliope/port.md records each change. parameters: spores_baseline_cost: diff --git a/examples/calliope/variants/storage_inter_cluster.yaml b/examples/calliope/variants/storage_inter_cluster.yaml index 0dd123ef..d63c64a2 100644 --- a/examples/calliope/variants/storage_inter_cluster.yaml +++ b/examples/calliope/variants/storage_inter_cluster.yaml @@ -1,6 +1,10 @@ +# SPDX-FileCopyrightText: Calliope contributors # SPDX-FileCopyrightText: mathspec Contributors # -# SPDX-License-Identifier: MIT +# SPDX-License-Identifier: Apache-2.0 +# +# Ported from the math of Calliope v0.7.0, https://github.com/calliope-project/calliope, +# and changed to mathspec's language. docs/examples/calliope/port.md records each change. dimensions: clusters: diff --git a/examples/calliope/variants/urban_scale_chp.yaml b/examples/calliope/variants/urban_scale_chp.yaml index 6932c81c..b96bcafd 100644 --- a/examples/calliope/variants/urban_scale_chp.yaml +++ b/examples/calliope/variants/urban_scale_chp.yaml @@ -1,6 +1,10 @@ +# SPDX-FileCopyrightText: Calliope contributors # SPDX-FileCopyrightText: mathspec Contributors # -# SPDX-License-Identifier: MIT +# SPDX-License-Identifier: Apache-2.0 +# +# Ported from the math of Calliope v0.7.0, https://github.com/calliope-project/calliope, +# and changed to mathspec's language. docs/examples/calliope/port.md records each change. constraints: balance_conversion: From 44d71cff203f765cf1bfd62e325cd127c3953340 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 29 Sep 2026 06:54:35 +0000 Subject: [PATCH 9/9] docs(calliope): the port writes storage loss, the annuity factor and multi-dimension sums as Calliope does MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #757 admits a sum of parameters under ** and as a divisor, so storage_retention and cost_annuity_factor are gone: the storage files read (1 - storage_loss) ** …, and the depreciation rate states Calliope's annuity factor. #778 and #779 admit sum(over=[a, b]), so the 21 nested sums are one call each. port.md marks gaps 1 and 8 closed and keeps the numbering. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01N66KMDkSv1rZbhr8jqqJzJ --- docs/examples/calliope/cost.md | 17 +++----- .../extensions/annual_energy_balance.md | 16 ++++---- .../extensions/monthly_peak_flow_charge.md | 4 +- .../calliope/extensions/net_import_share.md | 12 +++--- .../extensions/share_all_timesteps.md | 12 +++--- docs/examples/calliope/feasibility.md | 4 +- docs/examples/calliope/index.md | 2 +- docs/examples/calliope/port.md | 39 ++++++++++--------- docs/examples/calliope/reporting.md | 12 +++--- docs/examples/calliope/storage.md | 19 ++++----- docs/examples/calliope/transmission.md | 6 +-- .../variants/storage_inter_cluster.md | 22 +++++------ examples/calliope/cost.yaml | 12 ++---- .../extensions/annual_energy_balance.yaml | 8 ++-- .../extensions/monthly_peak_flow_charge.yaml | 2 +- .../calliope/extensions/net_import_share.yaml | 8 ++-- .../extensions/share_all_timesteps.yaml | 8 ++-- examples/calliope/feasibility.yaml | 2 +- examples/calliope/reporting.yaml | 6 +-- examples/calliope/storage.yaml | 13 +++---- examples/calliope/transmission.yaml | 4 +- .../variants/storage_inter_cluster.yaml | 14 +++---- 22 files changed, 115 insertions(+), 127 deletions(-) diff --git a/docs/examples/calliope/cost.md b/docs/examples/calliope/cost.md index cae38b9a..612bab4d 100644 --- a/docs/examples/calliope/cost.md +++ b/docs/examples/calliope/cost.md @@ -39,12 +39,6 @@ parameters: `lifetime` — the years a technology lasts. Calliope's default is `.inf`, and data prep fills it dims: [nodes, techs] - cost_annuity_factor: - description: >- - the annuity factor `r (1 + r) ** lifetime / ((1 + r) ** lifetime - 1)` - of the interest rate `r`, data prep. mathspec refuses a sum as the - base of `**` and as a divisor, over parameters too - dims: [nodes, techs, costs] given: parameters: @@ -91,7 +85,9 @@ expressions: no_interest: when: NOT cost_depreciation_rate AND (NOT cost_interest_rate OR cost_interest_rate == 0) expression: 1 / lifetime - otherwise: cost_annuity_factor + otherwise: >- + cost_interest_rate * (1 + cost_interest_rate) ** lifetime + / ((1 + cost_interest_rate) ** lifetime - 1) cost_investment_annualised: description: "`cost_investment_annualised` — the investment cost, as a year's share scaled to the modelled time" expression: annualisation_weight * depreciation_rate * cost_investment @@ -100,7 +96,7 @@ expressions: expression: cost_investment_annualised + sum(cost_operation_variable, over=timesteps) + cost_operation_fixed cost_of_techs: description: "`sum(sum(cost, over=[nodes, techs]) * objective_cost_weights, over=costs)` of `min_cost_optimisation`" - expression: sum(sum(sum(cost, over=nodes), over=techs) * objective_cost_weights) + expression: sum(sum(cost, over=[nodes, techs]) * objective_cost_weights) ``` #### Sets @@ -120,7 +116,6 @@ expressions: | $`\mathrm{cost}^{\mathrm{depreciation,rate}}`$ | `cost_depreciation_rate` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K}`$ — `cost_depreciation_rate` — the share of the investment cost a year carries; given only where set, and derived from the lifetime and the interest rate elsewhere | | $`\mathrm{cost}^{\mathrm{interest,rate}}`$ | `cost_interest_rate` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K}`$ — `cost_interest_rate` — the interest rate an investment is annualised at | | $`\mathrm{lifetime}`$ | `lifetime` over $`\mathcal{N} \times \mathcal{I}`$ — `lifetime` — the years a technology lasts. Calliope's default is `.inf`, and data prep fills it | -| $`\mathrm{cost}^{\mathrm{annuity,factor}}`$ | `cost_annuity_factor` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{K}`$ — the annuity factor `r (1 + r) ** lifetime / ((1 + r) ** lifetime - 1)` of the interest rate `r`, data prep. mathspec refuses a sum as the base of `**` and as a divisor, over parameters too | #### Given @@ -161,7 +156,7 @@ expressions: **`depreciation_rate`** ```math -\mathrm{depreciation\_rate}_{n,i,k} = \begin{cases} \mathrm{cost}^{\mathrm{depreciation,rate}}_{n,i,k} & \text{if } \mathrm{cost}^{\mathrm{depreciation,rate}}_{n,i,k} \text{ is defined} \\ \frac{1}{\mathrm{lifetime}_{n,i}} & \text{if } \neg \left( \mathrm{cost}^{\mathrm{depreciation,rate}}_{n,i,k} \text{ is defined} \right) \wedge \left( \neg \left( \mathrm{cost}^{\mathrm{interest,rate}}_{n,i,k} \text{ is defined} \right) \vee \mathrm{cost}^{\mathrm{interest,rate}}_{n,i,k} = 0 \right) \\ \mathrm{cost}^{\mathrm{annuity,factor}}_{n,i,k} & \text{otherwise} \end{cases} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ k \in \mathcal{K} +\mathrm{depreciation\_rate}_{n,i,k} = \begin{cases} \mathrm{cost}^{\mathrm{depreciation,rate}}_{n,i,k} & \text{if } \mathrm{cost}^{\mathrm{depreciation,rate}}_{n,i,k} \text{ is defined} \\ \frac{1}{\mathrm{lifetime}_{n,i}} & \text{if } \neg \left( \mathrm{cost}^{\mathrm{depreciation,rate}}_{n,i,k} \text{ is defined} \right) \wedge \left( \neg \left( \mathrm{cost}^{\mathrm{interest,rate}}_{n,i,k} \text{ is defined} \right) \vee \mathrm{cost}^{\mathrm{interest,rate}}_{n,i,k} = 0 \right) \\ \frac{\mathrm{cost}^{\mathrm{interest,rate}}_{n,i,k} \cdot \left( 1 + \mathrm{cost}^{\mathrm{interest,rate}}_{n,i,k} \right)^{\mathrm{lifetime}_{n,i}}}{\left( 1 + \mathrm{cost}^{\mathrm{interest,rate}}_{n,i,k} \right)^{\mathrm{lifetime}_{n,i}} - 1} & \text{otherwise} \end{cases} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ k \in \mathcal{K} ``` **`cost_investment_annualised`** @@ -179,7 +174,7 @@ expressions: **`cost_of_techs`** ```math -\mathit{cost}^{\mathrm{of,techs}} = \sum_{k \in \mathcal{K}} \left( \sum_{i \in \mathcal{I}} \sum_{n \in \mathcal{N}} \mathit{cost}_{n,i,k} \right) \cdot \mathrm{objective\_cost\_weights}_{k} +\mathit{cost}^{\mathrm{of,techs}} = \sum_{k \in \mathcal{K}} \left( \sum_{n \in \mathcal{N},\ i \in \mathcal{I}} \mathit{cost}_{n,i,k} \right) \cdot \mathrm{objective\_cost\_weights}_{k} ``` **`cost_investment`** diff --git a/docs/examples/calliope/extensions/annual_energy_balance.md b/docs/examples/calliope/extensions/annual_energy_balance.md index 9c3e34fd..491bc0f0 100644 --- a/docs/examples/calliope/extensions/annual_energy_balance.md +++ b/docs/examples/calliope/extensions/annual_energy_balance.md @@ -66,12 +66,12 @@ constraints: description: "`annual_energy_balance_per_tech_and_node` — a technology at a node puts out at most its annual limit" dims: [nodes, techs] where: annual_flow_max - expression: sum(sum(flow_out, over=carriers), over=timesteps) <= annual_flow_max + expression: sum(flow_out, over=[carriers, timesteps]) <= annual_flow_max annual_energy_balance_global_per_tech: description: "`annual_energy_balance_global_per_tech` — a technology puts out at most its annual limit over every node" dims: [techs] where: annual_flow_max - expression: sum(sum(sum(flow_out, over=nodes), over=carriers), over=timesteps) <= annual_flow_max + expression: sum(flow_out, over=[nodes, carriers, timesteps]) <= annual_flow_max annual_energy_balance_global_multi_tech: description: "`annual_energy_balance_global_multi_tech` — the group of technologies puts out at most its annual limit over every node" dims: [] @@ -85,12 +85,12 @@ constraints: one, which is where `source_use` is built dims: [techs] where: base_tech == 'supply' AND annual_source_max - expression: sum(sum(source_use, over=nodes), over=timesteps) <= annual_source_max + expression: sum(source_use, over=[nodes, timesteps]) <= annual_source_max annual_energy_balance_total_sink_availability: description: "`annual_energy_balance_total_sink_availability` — a demand technology takes in at most its annual limit" dims: [techs] where: base_tech == 'demand' AND annual_sink_max - expression: sum(sum(sum(flow_in, over=nodes), over=carriers), over=timesteps) <= annual_sink_max + expression: sum(flow_in, over=[nodes, carriers, timesteps]) <= annual_sink_max ``` #### Sets @@ -132,13 +132,13 @@ constraints: **`annual_energy_balance_per_tech_and_node`** ```math -\sum_{t \in \mathcal{T}} \sum_{c \in \mathcal{C}} \mathit{flow\_out}_{n,i,c,t} \le \mathrm{annual\_flow\_max}_{i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I} \,:\, \mathrm{annual\_flow\_max}_{i} \text{ is defined} +\sum_{c \in \mathcal{C},\ t \in \mathcal{T}} \mathit{flow\_out}_{n,i,c,t} \le \mathrm{annual\_flow\_max}_{i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I} \,:\, \mathrm{annual\_flow\_max}_{i} \text{ is defined} ``` **`annual_energy_balance_global_per_tech`** ```math -\sum_{t \in \mathcal{T}} \sum_{c \in \mathcal{C}} \sum_{n \in \mathcal{N}} \mathit{flow\_out}_{n,i,c,t} \le \mathrm{annual\_flow\_max}_{i} \qquad \forall\, i \in \mathcal{I} \,:\, \mathrm{annual\_flow\_max}_{i} \text{ is defined} +\sum_{n \in \mathcal{N},\ c \in \mathcal{C},\ t \in \mathcal{T}} \mathit{flow\_out}_{n,i,c,t} \le \mathrm{annual\_flow\_max}_{i} \qquad \forall\, i \in \mathcal{I} \,:\, \mathrm{annual\_flow\_max}_{i} \text{ is defined} ``` **`annual_energy_balance_global_multi_tech`** @@ -150,13 +150,13 @@ constraints: **`annual_energy_balance_total_source_availability`** ```math -\sum_{t \in \mathcal{T}} \sum_{n \in \mathcal{N}} \mathit{source\_use}_{n,i,t} \le \mathrm{annual\_source\_max}_{i} \qquad \forall\, i \in \mathcal{I} \,:\, \mathrm{base\_tech}_{i} = \text{'}\mathrm{supply}\text{'} \wedge \mathrm{annual\_source\_max}_{i} \text{ is defined} +\sum_{n \in \mathcal{N},\ t \in \mathcal{T}} \mathit{source\_use}_{n,i,t} \le \mathrm{annual\_source\_max}_{i} \qquad \forall\, i \in \mathcal{I} \,:\, \mathrm{base\_tech}_{i} = \text{'}\mathrm{supply}\text{'} \wedge \mathrm{annual\_source\_max}_{i} \text{ is defined} ``` **`annual_energy_balance_total_sink_availability`** ```math -\sum_{t \in \mathcal{T}} \sum_{c \in \mathcal{C}} \sum_{n \in \mathcal{N}} \mathit{flow\_in}_{n,i,c,t} \le \mathrm{annual\_sink\_max}_{i} \qquad \forall\, i \in \mathcal{I} \,:\, \mathrm{base\_tech}_{i} = \text{'}\mathrm{demand}\text{'} \wedge \mathrm{annual\_sink\_max}_{i} \text{ is defined} +\sum_{n \in \mathcal{N},\ c \in \mathcal{C},\ t \in \mathcal{T}} \mathit{flow\_in}_{n,i,c,t} \le \mathrm{annual\_sink\_max}_{i} \qquad \forall\, i \in \mathcal{I} \,:\, \mathrm{base\_tech}_{i} = \text{'}\mathrm{demand}\text{'} \wedge \mathrm{annual\_sink\_max}_{i} \text{ is defined} ``` #### Definitions diff --git a/docs/examples/calliope/extensions/monthly_peak_flow_charge.md b/docs/examples/calliope/extensions/monthly_peak_flow_charge.md index cc729284..0e035199 100644 --- a/docs/examples/calliope/extensions/monthly_peak_flow_charge.md +++ b/docs/examples/calliope/extensions/monthly_peak_flow_charge.md @@ -55,7 +55,7 @@ variables: expressions: cost_month_peak_charge: description: "`sum(cost_month_peak * flow_peak_month, over=[carriers, months])` — the term Calliope writes into `cost_operation_fixed`, by restating it whole" - expression: sum(sum(cost_month_peak * flow_peak_month, over=carriers), over=months) + expression: sum(cost_month_peak * flow_peak_month, over=[carriers, months]) given: parameters: @@ -128,7 +128,7 @@ Upright is what the data supplies — a parameter such as $`\mathrm{monthly\_pea **`cost_month_peak_charge`** ```math -\mathit{cost\_month\_peak\_charge}_{n,i,k} = \sum_{m \in \mathcal{M}} \sum_{c \in \mathcal{C}} \mathrm{cost\_month\_peak}_{n,i,k} \cdot \mathit{flow\_peak\_month}_{n,i,c,m} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ k \in \mathcal{K} +\mathit{cost\_month\_peak\_charge}_{n,i,k} = \sum_{c \in \mathcal{C},\ m \in \mathcal{M}} \mathrm{cost\_month\_peak}_{n,i,k} \cdot \mathit{flow\_peak\_month}_{n,i,c,m} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ k \in \mathcal{K} ``` #### Variable domains diff --git a/docs/examples/calliope/extensions/net_import_share.md b/docs/examples/calliope/extensions/net_import_share.md index 8bbad733..f34935e2 100644 --- a/docs/examples/calliope/extensions/net_import_share.md +++ b/docs/examples/calliope/extensions/net_import_share.md @@ -90,15 +90,15 @@ constraints: dims: [nodes, timesteps] where: count(count(carrier_out, over=carriers) >= 1 AND base_tech == 'transmission', over=techs) >= 1 expression: >- - net_import_share * sum(sum(electricity_imports, over=techs), over=carriers) - <= sum(sum(electricity_balance, over=techs), over=carriers) + net_import_share * sum(electricity_imports, over=[techs, carriers]) + <= sum(electricity_balance, over=[techs, carriers]) net_annual_import_share_max: description: "`net_annual_import_share_max` — electricity imports at a node are at most their share of its electricity balance over the year" dims: [nodes] where: count(count(carrier_out, over=carriers) >= 1 AND base_tech == 'transmission', over=techs) >= 1 expression: >- - net_import_share * sum(sum(sum(electricity_imports, over=techs), over=carriers), over=timesteps) - <= sum(sum(sum(electricity_balance, over=techs), over=carriers), over=timesteps) + net_import_share * sum(electricity_imports, over=[techs, carriers, timesteps]) + <= sum(electricity_balance, over=[techs, carriers, timesteps]) net_annual_import_share_max_node_group: description: "`net_annual_import_share_max_node_group` — heat imports at nodes `a` and `c` are at most their share of the group's heat balance over the year" dims: [] @@ -144,13 +144,13 @@ constraints: **`net_import_share_max`** ```math -\mathrm{net\_import\_share} \cdot \left( \sum_{c \in \mathcal{C}} \sum_{i \in \mathcal{I}} \mathit{electricity\_imports}_{n,i,c,t} \right) \le \sum_{c \in \mathcal{C}} \sum_{i \in \mathcal{I}} \mathit{electricity\_balance}_{n,i,c,t} \qquad \forall\, n \in \mathcal{N},\ t \in \mathcal{T} \,:\, \lvert \{ i \in \mathcal{I} \,:\, \lvert \{ c \in \mathcal{C} \,:\, \mathrm{carrier\_out}_{n,i,c} \} \rvert \ge 1 \wedge \mathrm{base\_tech}_{i} = \text{'}\mathrm{transmission}\text{'} \} \rvert \ge 1 +\mathrm{net\_import\_share} \cdot \left( \sum_{i \in \mathcal{I},\ c \in \mathcal{C}} \mathit{electricity\_imports}_{n,i,c,t} \right) \le \sum_{i \in \mathcal{I},\ c \in \mathcal{C}} \mathit{electricity\_balance}_{n,i,c,t} \qquad \forall\, n \in \mathcal{N},\ t \in \mathcal{T} \,:\, \lvert \{ i \in \mathcal{I} \,:\, \lvert \{ c \in \mathcal{C} \,:\, \mathrm{carrier\_out}_{n,i,c} \} \rvert \ge 1 \wedge \mathrm{base\_tech}_{i} = \text{'}\mathrm{transmission}\text{'} \} \rvert \ge 1 ``` **`net_annual_import_share_max`** ```math -\mathrm{net\_import\_share} \cdot \left( \sum_{t \in \mathcal{T}} \sum_{c \in \mathcal{C}} \sum_{i \in \mathcal{I}} \mathit{electricity\_imports}_{n,i,c,t} \right) \le \sum_{t \in \mathcal{T}} \sum_{c \in \mathcal{C}} \sum_{i \in \mathcal{I}} \mathit{electricity\_balance}_{n,i,c,t} \qquad \forall\, n \in \mathcal{N} \,:\, \lvert \{ i \in \mathcal{I} \,:\, \lvert \{ c \in \mathcal{C} \,:\, \mathrm{carrier\_out}_{n,i,c} \} \rvert \ge 1 \wedge \mathrm{base\_tech}_{i} = \text{'}\mathrm{transmission}\text{'} \} \rvert \ge 1 +\mathrm{net\_import\_share} \cdot \left( \sum_{i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T}} \mathit{electricity\_imports}_{n,i,c,t} \right) \le \sum_{i \in \mathcal{I},\ c \in \mathcal{C},\ t \in \mathcal{T}} \mathit{electricity\_balance}_{n,i,c,t} \qquad \forall\, n \in \mathcal{N} \,:\, \lvert \{ i \in \mathcal{I} \,:\, \lvert \{ c \in \mathcal{C} \,:\, \mathrm{carrier\_out}_{n,i,c} \} \rvert \ge 1 \wedge \mathrm{base\_tech}_{i} = \text{'}\mathrm{transmission}\text{'} \} \rvert \ge 1 ``` **`net_annual_import_share_max_node_group`** diff --git a/docs/examples/calliope/extensions/share_all_timesteps.md b/docs/examples/calliope/extensions/share_all_timesteps.md index 58506b20..8ae7a719 100644 --- a/docs/examples/calliope/extensions/share_all_timesteps.md +++ b/docs/examples/calliope/extensions/share_all_timesteps.md @@ -73,16 +73,16 @@ constraints: dims: [nodes, techs] where: demand_share_equals expression: >- - sum(sum(flow_out, over=timesteps), over=carriers) - == sum(sum(at(flow_in, by=demand_share_tech, over=demand, into=techs), over=timesteps), over=carriers) + sum(flow_out, over=[timesteps, carriers]) + == sum(at(flow_in, by=demand_share_tech, over=demand, into=techs), over=[timesteps, carriers]) * demand_share_equals supply_share_equals_per_tech: description: "`supply_share_equals_per_tech` — a technology puts out its share of a node's outflow of a carrier over the whole time" dims: [nodes, techs] where: supply_share_equals expression: >- - sum(sum(supply_share_flow_out, over=carriers), over=timesteps) - == sum(sum(supply_share_all_flow_out, over=carriers), over=timesteps) * supply_share_equals + sum(supply_share_flow_out, over=[carriers, timesteps]) + == sum(supply_share_all_flow_out, over=[carriers, timesteps]) * supply_share_equals ``` #### Sets @@ -120,13 +120,13 @@ constraints: **`demand_share_equals_per_tech`** ```math -\sum_{c \in \mathcal{C}} \sum_{t \in \mathcal{T}} \mathit{flow\_out}_{n,i,c,t} = \left( \sum_{c \in \mathcal{C}} \sum_{t \in \mathcal{T}} \mathit{flow\_in}_{n,\mathrm{demand\_share\_tech}(i),c,t} \right) \cdot \mathrm{demand\_share\_equals}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I} \,:\, \mathrm{demand\_share\_equals}_{n,i} \text{ is defined} +\sum_{c \in \mathcal{C},\ t \in \mathcal{T}} \mathit{flow\_out}_{n,i,c,t} = \left( \sum_{c \in \mathcal{C},\ t \in \mathcal{T}} \mathit{flow\_in}_{n,\mathrm{demand\_share\_tech}(i),c,t} \right) \cdot \mathrm{demand\_share\_equals}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I} \,:\, \mathrm{demand\_share\_equals}_{n,i} \text{ is defined} ``` **`supply_share_equals_per_tech`** ```math -\sum_{t \in \mathcal{T}} \sum_{c \in \mathcal{C}} \mathit{supply\_share\_flow\_out}_{n,i,c,t} = \left( \sum_{t \in \mathcal{T}} \sum_{c \in \mathcal{C}} \mathit{supply\_share\_all\_flow\_out}_{n,i,c,t} \right) \cdot \mathrm{supply\_share\_equals}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I} \,:\, \mathrm{supply\_share\_equals}_{n,i} \text{ is defined} +\sum_{c \in \mathcal{C},\ t \in \mathcal{T}} \mathit{supply\_share\_flow\_out}_{n,i,c,t} = \left( \sum_{c \in \mathcal{C},\ t \in \mathcal{T}} \mathit{supply\_share\_all\_flow\_out}_{n,i,c,t} \right) \cdot \mathrm{supply\_share\_equals}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I} \,:\, \mathrm{supply\_share\_equals}_{n,i} \text{ is defined} ``` #### Definitions diff --git a/docs/examples/calliope/feasibility.md b/docs/examples/calliope/feasibility.md index 9bc6ce20..6d135044 100644 --- a/docs/examples/calliope/feasibility.md +++ b/docs/examples/calliope/feasibility.md @@ -42,7 +42,7 @@ expressions: feasibility_carrier_flow: unmet_demand + unused_supply unmet_demand_penalty: description: "`$unmet_demand` of `min_cost_optimisation` — what unmet demand and unused supply cost" - expression: sum(sum(sum(unmet_demand - unused_supply, over=carriers), over=nodes) * timestep_weights) * bigM + expression: sum(sum(unmet_demand - unused_supply, over=[carriers, nodes]) * timestep_weights) * bigM unmet_sum: description: "`unmet_sum` — net unmet demand; reported" expression: unmet_demand + unused_supply @@ -104,7 +104,7 @@ given: **`unmet_demand_penalty`** ```math -\mathit{unmet\_demand\_penalty} = \left( \sum_{t \in \mathcal{T}} \left( \sum_{n \in \mathcal{N}} \sum_{c \in \mathcal{C}} \left( \mathit{unmet\_demand}_{n,c,t} - \mathit{unused\_supply}_{n,c,t} \right) \right) \cdot \mathrm{timestep\_weights}_{t} \right) \cdot \mathrm{bigM} +\mathit{unmet\_demand\_penalty} = \left( \sum_{t \in \mathcal{T}} \left( \sum_{n \in \mathcal{N},\ c \in \mathcal{C}} \left( \mathit{unmet\_demand}_{n,c,t} - \mathit{unused\_supply}_{n,c,t} \right) \right) \cdot \mathrm{timestep\_weights}_{t} \right) \cdot \mathrm{bigM} ``` **`unmet_sum`** diff --git a/docs/examples/calliope/index.md b/docs/examples/calliope/index.md index 603c5287..575f7243 100644 --- a/docs/examples/calliope/index.md +++ b/docs/examples/calliope/index.md @@ -119,7 +119,7 @@ reads, and `merge` keeps what no file declares under `given:`. | [area](area.md) | 5 | 1 | 3 | 5 | `cost_investment` | | [balance](balance.md) | 0 | 0 | 1 | 2 | | | [conversion](conversion.md) | 0 | 0 | 1 | 4 | | -| [cost](cost.md) | 5 | 0 | 0 | 4 | `system_cost` | +| [cost](cost.md) | 4 | 0 | 0 | 4 | `system_cost` | | [demand](demand.md) | 4 | 0 | 3 | 5 | | | [export](export.md) | 4 | 1 | 1 | 5 | `carrier_flow`, `cost_operation_variable` | | [feasibility](feasibility.md) | 0 | 2 | 0 | 6 | `carrier_flow`, `penalty` | diff --git a/docs/examples/calliope/port.md b/docs/examples/calliope/port.md index cf7ce777..515e7b97 100644 --- a/docs/examples/calliope/port.md +++ b/docs/examples/calliope/port.md @@ -15,17 +15,19 @@ nothing, and the note says why. Parameters are not listed: each one Calliope declares is a parameter of the same name, in the file that reads it, with [the conventions](index.md#conventions) -for defaults and dimensions. The four exceptions are rows below. +for defaults and dimensions. The one exception is `storage_loss` in the +inter-cluster variant, a row below. ## What mathspec is missing Each gap below is a place the port is not one block for one. None stops a block from being stated. -1. **`**` and `/` over a sum of parameters.** mathspec refuses - `(1 - storage_loss) ** timestep_resolution` and Calliope's annuity factor, - whose base and divisor are sums, although no variable is in them. Data - prep supplies `storage_retention` and `cost_annuity_factor`. +1. **Closed: a power or a quotient over a sum of parameters.** mathspec + refused `(1 - storage_loss) ** timestep_resolution` and Calliope's annuity + factor, whose base and divisor are sums. + [#757](https://github.com/energy-models/mathspec/pull/757) admits them, and + the port writes both as Calliope does. 2. **A default that is not zero.** Calliope's `default:` is the value arithmetic reads and, at the same time, "not given" to a `where:`. A mathspec parameter has one reading of a missing row, `0`, so data prep @@ -57,8 +59,10 @@ link_from)`, and a slice by a per-technology carrier, compare a lookup with 7. **An empty sum that no file adds to.** An `empty: true` sum with no term is a column the spec reads and does not build, not zero. `penalty` has the body `0`, so a model with no feasibility file reads a zero penalty. -8. **`sum(over=[a, b])`.** Calliope sums over a list of dimensions. mathspec - takes one dimension per `sum`, so the port nests them. +8. **Closed: `sum(over=[a, b])`.** Calliope sums over a list of dimensions. + [#778](https://github.com/energy-models/mathspec/pull/778) admits the list, + and [#779](https://github.com/energy-models/mathspec/pull/779) admits it in + a macro, so the port writes each sum as one call. 9. **A warning.** An assumption holds or the data is refused. Calliope's three checks at `errors: warn` are out. @@ -79,7 +83,6 @@ integer ([#768](https://github.com/energy-models/mathspec/pull/768)). | lookup `lookup_cluster_last_timestep` | done | [storage](storage.md) | a relation from a time step onto a time step | | lookups `sink_unit`, `source_unit` | done | [demand](demand.md), [supply](supply.md) | `absolute`, the default, is what no row reads as | | lookups `latitude`, `longitude` | out | | read by no math; Calliope derives `distance` from them in data preparation | -| parameters `storage_loss`, `cost_interest_rate` with `lifetime` | prep | [storage](storage.md), [cost](cost.md) | `storage_retention` and `cost_annuity_factor`, gap 1 | | `flow_capacity_per_storage_capacity_min`, `_max` | done | [storage](storage.md) | | | `source_capacity_equals_flow_capacity` | done | [supply](supply.md) | | | `force_zero_area_use`, `area_use_per_flow_capacity` | done | [area](area.md) | | @@ -110,7 +113,7 @@ integer ([#768](https://github.com/energy-models/mathspec/pull/768)). | `cost_operation_variable` | split | [cost](cost.md) | a sum; flows, supply and export each add their cost. Calliope's two cases, supply or not, are one sum, since a supply technology has no inflow | | `cost_investment_flow_cap`, `_storage_cap`, `_source_cap`, `_area_use` | done | [flows](flows.md), [storage](storage.md), [supply](supply.md), [area](area.md) | each is a term of `cost_investment`, and the flow capacity one through `flows_cost_investment` | | `cost_investment` | split | [cost](cost.md) | a sum each capacity adds to | -| `cost_investment_annualised` | prep | [cost](cost.md) | the annuity factor, gap 1 | +| `cost_investment_annualised` | done | [cost](cost.md) | `$depreciation_rate` is a named expression with cases | | `cost_operation_fixed` | split | [cost](cost.md) | its body here, and `flows_cost_operation_fixed` added | | `cost` | done | [cost](cost.md) | | | postprocessed `capacity_factor`, `systemwide_capacity_factor`, `total_generation`, `systemwide_levelised_cost`, `total_levelised_cost` | done | [reporting](reporting.md) | reported; the `where:` is the absence of what they read | @@ -165,15 +168,15 @@ integer ([#768](https://github.com/energy-models/mathspec/pull/768)). ## `storage_inter_cluster.yaml` -| Calliope | status | here | note | -| ----------------------------------------------------------------------------------------------------------- | ------ | -------------------------------------------- | ----------------------------------------------------------------------------------------- | -| lookups `timestep_cluster`, `lookup_datestep_cluster`, `lookup_datestep_last_cluster_timestep` | done | [variant](variants/storage_inter_cluster.md) | relations | -| `storage_max`, `cyclic_storage_needs_inter_cluster` off | done | [variant](variants/storage_inter_cluster.md) | removals | -| `$storage_previous_step` of `balance_supply_with_storage` and `balance_storage` | done | [variant](variants/storage_inter_cluster.md) | one named expression, patched once | -| `set_storage_initial` | split | [variant](variants/storage_inter_cluster.md) | built at the last day, as in the base | -| `storage_intra_max`, `storage_intra_min`, `storage_inter_max`, `storage_inter_min`, `balance_storage_inter` | done | [variant](variants/storage_inter_cluster.md) | a slice by a lookup is `at` through the relation | -| variables | done | [variant](variants/storage_inter_cluster.md) | | -| `storage_loss` over time steps | prep | [variant](variants/storage_inter_cluster.md) | `storage_retention ** 24` reads it per day, so the variant declares it without time steps | +| Calliope | status | here | note | +| ----------------------------------------------------------------------------------------------------------- | ------ | -------------------------------------------- | ------------------------------------------------------------------------------------------ | +| lookups `timestep_cluster`, `lookup_datestep_cluster`, `lookup_datestep_last_cluster_timestep` | done | [variant](variants/storage_inter_cluster.md) | relations | +| `storage_max`, `cyclic_storage_needs_inter_cluster` off | done | [variant](variants/storage_inter_cluster.md) | removals | +| `$storage_previous_step` of `balance_supply_with_storage` and `balance_storage` | done | [variant](variants/storage_inter_cluster.md) | one named expression, patched once | +| `set_storage_initial` | split | [variant](variants/storage_inter_cluster.md) | built at the last day, as in the base | +| `storage_intra_max`, `storage_intra_min`, `storage_inter_max`, `storage_inter_min`, `balance_storage_inter` | done | [variant](variants/storage_inter_cluster.md) | a slice by a lookup is `at` through the relation | +| variables | done | [variant](variants/storage_inter_cluster.md) | | +| `storage_loss` over time steps | prep | [variant](variants/storage_inter_cluster.md) | `(1 - storage_loss) ** 24` reads it per day, so the variant declares it without time steps | ## The examples diff --git a/docs/examples/calliope/reporting.md b/docs/examples/calliope/reporting.md index 7fe48696..d9421c5f 100644 --- a/docs/examples/calliope/reporting.md +++ b/docs/examples/calliope/reporting.md @@ -41,19 +41,19 @@ expressions: systemwide_capacity_factor: description: "`systemwide_capacity_factor` — the share of its flow capacity a technology puts out over every node and time step" expression: >- - sum(sum(flow_out * timestep_weights, over=nodes), over=timesteps) + sum(flow_out * timestep_weights, over=[nodes, timesteps]) / (sum(flow_cap, over=nodes) * sum(timestep_resolution * timestep_weights, over=timesteps)) total_generation: description: >- `total_generation` — outflow over every node and time step. Calliope weights only the export, as written here - expression: sum(sum(flow_out + flow_export * timestep_weights, over=nodes), over=timesteps) + expression: sum(flow_out + flow_export * timestep_weights, over=[nodes, timesteps]) systemwide_levelised_cost: description: "`systemwide_levelised_cost` — a technology's cost per unit of what it generates, over every node" expression: sum(cost, over=nodes) / total_generation total_levelised_cost: description: "`total_levelised_cost` — the system's cost per unit of a carrier generated" - expression: sum(sum(cost, over=nodes), over=techs) / sum(total_generation, over=techs) + expression: sum(cost, over=[nodes, techs]) / sum(total_generation, over=techs) ``` #### Sets @@ -98,13 +98,13 @@ expressions: **`systemwide_capacity_factor`** ```math -\mathit{systemwide\_capacity\_factor}_{i,c} = \frac{\sum_{t \in \mathcal{T}} \sum_{n \in \mathcal{N}} \mathit{flow\_out}_{n,i,c,t} \cdot \mathrm{timestep\_weights}_{t}}{\left( \sum_{n \in \mathcal{N}} \mathit{flow\_cap}_{n,i,c} \right) \cdot \left( \sum_{t \in \mathcal{T}} \mathrm{timestep\_resolution}_{t} \cdot \mathrm{timestep\_weights}_{t} \right)} \qquad \forall\, i \in \mathcal{I},\ c \in \mathcal{C} +\mathit{systemwide\_capacity\_factor}_{i,c} = \frac{\sum_{n \in \mathcal{N},\ t \in \mathcal{T}} \mathit{flow\_out}_{n,i,c,t} \cdot \mathrm{timestep\_weights}_{t}}{\left( \sum_{n \in \mathcal{N}} \mathit{flow\_cap}_{n,i,c} \right) \cdot \left( \sum_{t \in \mathcal{T}} \mathrm{timestep\_resolution}_{t} \cdot \mathrm{timestep\_weights}_{t} \right)} \qquad \forall\, i \in \mathcal{I},\ c \in \mathcal{C} ``` **`total_generation`** ```math -\mathit{total\_generation}_{i,c} = \sum_{t \in \mathcal{T}} \sum_{n \in \mathcal{N}} \left( \mathit{flow\_out}_{n,i,c,t} + \mathit{flow\_export}_{n,i,c,t} \cdot \mathrm{timestep\_weights}_{t} \right) \qquad \forall\, i \in \mathcal{I},\ c \in \mathcal{C} +\mathit{total\_generation}_{i,c} = \sum_{n \in \mathcal{N},\ t \in \mathcal{T}} \left( \mathit{flow\_out}_{n,i,c,t} + \mathit{flow\_export}_{n,i,c,t} \cdot \mathrm{timestep\_weights}_{t} \right) \qquad \forall\, i \in \mathcal{I},\ c \in \mathcal{C} ``` **`systemwide_levelised_cost`** @@ -116,6 +116,6 @@ expressions: **`total_levelised_cost`** ```math -\mathit{total\_levelised\_cost}_{c,k} = \frac{\sum_{i \in \mathcal{I}} \sum_{n \in \mathcal{N}} \mathit{cost}_{n,i,k}}{\sum_{i \in \mathcal{I}} \mathit{total\_generation}_{i,c}} \qquad \forall\, c \in \mathcal{C},\ k \in \mathcal{K} +\mathit{total\_levelised\_cost}_{c,k} = \frac{\sum_{n \in \mathcal{N},\ i \in \mathcal{I}} \mathit{cost}_{n,i,k}}{\sum_{i \in \mathcal{I}} \mathit{total\_generation}_{i,c}} \qquad \forall\, c \in \mathcal{C},\ k \in \mathcal{K} ``` diff --git a/docs/examples/calliope/storage.md b/docs/examples/calliope/storage.md index a03d76e8..8c412246 100644 --- a/docs/examples/calliope/storage.md +++ b/docs/examples/calliope/storage.md @@ -44,11 +44,8 @@ parameters: storage_initial: description: "`storage_initial` — what a store holds at the start, as a share of its capacity; given only where set" dims: [nodes, techs] - storage_retention: - description: >- - `1 - storage_loss` — the share of what a store holds that it keeps - for an hour, data prep. mathspec refuses a sum as the base of `**`, - over parameters too + storage_loss: + description: "`storage_loss` — the share of what a store holds that it loses in an hour" dims: [nodes, techs, timesteps] cyclic_storage: description: >- @@ -99,10 +96,10 @@ expressions: cluster_start: when: cluster_first_timestep AND NOT (position(timesteps) == 0 AND NOT cyclic_storage) expression: >- - storage_retention ** at(timestep_resolution, by=lookup_cluster_last_timestep, over=last, into=timesteps) + (1 - storage_loss) ** at(timestep_resolution, by=lookup_cluster_last_timestep, over=last, into=timesteps) * at(storage, by=lookup_cluster_last_timestep, over=last, into=timesteps) otherwise: >- - storage_retention ** shift(timestep_resolution, along=timesteps, offset=1, edge='wrap') + (1 - storage_loss) ** shift(timestep_resolution, along=timesteps, offset=1, edge='wrap') * shift(storage, along=timesteps, offset=1, edge='wrap') cost_investment_storage_cap: description: "`cost_investment_storage_cap` — the investment cost of storage capacity" @@ -158,7 +155,7 @@ constraints: store and reads the last step; this builds that row at the last step dims: [nodes, techs, timesteps] where: position(timesteps) == -1 AND storage AND storage_initial AND cyclic_storage - expression: storage * storage_retention ** timestep_resolution == storage_initial * storage_cap + expression: storage * (1 - storage_loss) ** timestep_resolution == storage_initial * storage_cap assumptions: unbounded_storage_cap_cost: @@ -193,7 +190,7 @@ assumptions: | $`\mathrm{storage}^{\mathrm{cap,max}}`$ | `storage_cap_max` over $`\mathcal{N} \times \mathcal{I}`$ — `storage_cap_max` — most storage capacity. Calliope's default is `.inf`, and data prep fills it | | $`\mathrm{storage}^{\mathrm{discharge,depth}}`$ | `storage_discharge_depth` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{T}`$ — `storage_discharge_depth` — the least a store holds, as a share of its capacity | | $`\mathrm{storage}^{\mathrm{initial}}`$ | `storage_initial` over $`\mathcal{N} \times \mathcal{I}`$ — `storage_initial` — what a store holds at the start, as a share of its capacity; given only where set | -| $`\mathrm{storage}^{\mathrm{retention}}`$ | `storage_retention` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{T}`$ — `1 - storage_loss` — the share of what a store holds that it keeps for an hour, data prep. mathspec refuses a sum as the base of `**`, over parameters too | +| $`\mathrm{storage}^{\mathrm{loss}}`$ | `storage_loss` over $`\mathcal{N} \times \mathcal{I} \times \mathcal{T}`$ — `storage_loss` — the share of what a store holds that it loses in an hour | | $`\mathrm{cyclic\_storage}`$ | `cyclic_storage` over $`\mathcal{N} \times \mathcal{I}`$ — `cyclic_storage` — whether a store ends where it starts. Calliope's default is true, and data prep fills it | | $`\mathrm{cluster\_first\_timestep}`$ | `cluster_first_timestep` over $`\mathcal{T}`$ — `cluster_first_timestep` — whether a time step is the first of its clustered day | | $`\mathrm{flow\_cap\_per\_storage\_cap\_min}`$ | `flow_cap_per_storage_cap_min` over $`\mathcal{N} \times \mathcal{I}`$ — `flow_cap_per_storage_cap_min` — least flow capacity per unit of storage capacity; given only where set | @@ -269,7 +266,7 @@ $`\lvert \mathcal{T} \rvert`$ denotes the size of the set being counted along, a **`set_storage_initial`** ```math -\mathit{storage}_{n,i,t} \cdot \mathrm{storage}^{\mathrm{retention}}_{n,i,t}^{\mathrm{timestep\_resolution}_{t}} = \mathrm{storage}^{\mathrm{initial}}_{n,i} \cdot \mathit{storage}^{\mathrm{cap}}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T} \,:\, \mathrm{pos}(t) = \lvert \mathcal{T} \rvert - 1 \wedge \mathit{storage}_{n,i,t} \text{ exists} \wedge \mathrm{storage}^{\mathrm{initial}}_{n,i} \text{ is defined} \wedge \mathrm{cyclic\_storage}_{n,i} +\mathit{storage}_{n,i,t} \cdot \left( 1 - \mathrm{storage}^{\mathrm{loss}}_{n,i,t} \right)^{\mathrm{timestep\_resolution}_{t}} = \mathrm{storage}^{\mathrm{initial}}_{n,i} \cdot \mathit{storage}^{\mathrm{cap}}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T} \,:\, \mathrm{pos}(t) = \lvert \mathcal{T} \rvert - 1 \wedge \mathit{storage}_{n,i,t} \text{ exists} \wedge \mathrm{storage}^{\mathrm{initial}}_{n,i} \text{ is defined} \wedge \mathrm{cyclic\_storage}_{n,i} ``` #### Definitions @@ -277,7 +274,7 @@ $`\lvert \mathcal{T} \rvert`$ denotes the size of the set being counted along, a **`storage_previous_step`** ```math -\mathit{storage}^{\mathrm{previous,step}}_{n,i,t} = \begin{cases} \mathrm{storage}^{\mathrm{initial}}_{n,i} \cdot \mathit{storage}^{\mathrm{cap}}_{n,i} & \text{if } \mathrm{pos}(t) = 0 \wedge \neg \mathrm{cyclic\_storage}_{n,i} \\ \mathrm{storage}^{\mathrm{retention}}_{n,i,t}^{\mathrm{timestep\_resolution}_{\mathrm{lookup\_cluster\_last\_timestep}(t)}} \cdot \mathit{storage}_{n,i,\mathrm{lookup\_cluster\_last\_timestep}(t)} & \text{if } \mathrm{cluster\_first\_timestep}_{t} \wedge \neg \left( \mathrm{pos}(t) = 0 \wedge \neg \mathrm{cyclic\_storage}_{n,i} \right) \\ \mathrm{storage}^{\mathrm{retention}}_{n,i,t}^{\mathrm{timestep\_resolution}_{t \ominus 1}} \cdot \mathit{storage}_{n,i,t \ominus 1} & \text{otherwise} \end{cases} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T} +\mathit{storage}^{\mathrm{previous,step}}_{n,i,t} = \begin{cases} \mathrm{storage}^{\mathrm{initial}}_{n,i} \cdot \mathit{storage}^{\mathrm{cap}}_{n,i} & \text{if } \mathrm{pos}(t) = 0 \wedge \neg \mathrm{cyclic\_storage}_{n,i} \\ \left( 1 - \mathrm{storage}^{\mathrm{loss}}_{n,i,t} \right)^{\mathrm{timestep\_resolution}_{\mathrm{lookup\_cluster\_last\_timestep}(t)}} \cdot \mathit{storage}_{n,i,\mathrm{lookup\_cluster\_last\_timestep}(t)} & \text{if } \mathrm{cluster\_first\_timestep}_{t} \wedge \neg \left( \mathrm{pos}(t) = 0 \wedge \neg \mathrm{cyclic\_storage}_{n,i} \right) \\ \left( 1 - \mathrm{storage}^{\mathrm{loss}}_{n,i,t} \right)^{\mathrm{timestep\_resolution}_{t \ominus 1}} \cdot \mathit{storage}_{n,i,t \ominus 1} & \text{otherwise} \end{cases} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T} ``` **`cost_investment_storage_cap`** diff --git a/docs/examples/calliope/transmission.md b/docs/examples/calliope/transmission.md index b13ec174..bd9cdfe9 100644 --- a/docs/examples/calliope/transmission.md +++ b/docs/examples/calliope/transmission.md @@ -68,8 +68,8 @@ constraints: dims: [techs, timesteps] where: base_tech == 'transmission' expression: >- - sum(sum(flow_out_inc_eff, over=nodes), over=carriers) - == sum(sum(flow_in_inc_eff, over=nodes), over=carriers) + sum(flow_out_inc_eff, over=[nodes, carriers]) + == sum(flow_in_inc_eff, over=[nodes, carriers]) symmetric_transmission: description: "`symmetric_transmission` — a link has the same flow capacity at both ends" dims: [techs, carriers] @@ -108,7 +108,7 @@ constraints: **`balance_transmission`** ```math -\sum_{c \in \mathcal{C}} \sum_{n \in \mathcal{N}} \mathit{flow\_out\_inc\_eff}_{n,i,c,t} = \sum_{c \in \mathcal{C}} \sum_{n \in \mathcal{N}} \mathit{flow\_in\_inc\_eff}_{n,i,c,t} \qquad \forall\, i \in \mathcal{I},\ t \in \mathcal{T} \,:\, \mathrm{base\_tech}_{i} = \text{'}\mathrm{transmission}\text{'} +\sum_{n \in \mathcal{N},\ c \in \mathcal{C}} \mathit{flow\_out\_inc\_eff}_{n,i,c,t} = \sum_{n \in \mathcal{N},\ c \in \mathcal{C}} \mathit{flow\_in\_inc\_eff}_{n,i,c,t} \qquad \forall\, i \in \mathcal{I},\ t \in \mathcal{T} \,:\, \mathrm{base\_tech}_{i} = \text{'}\mathrm{transmission}\text{'} ``` **`symmetric_transmission`** diff --git a/docs/examples/calliope/variants/storage_inter_cluster.md b/docs/examples/calliope/variants/storage_inter_cluster.md index 7e98a509..8e78b0e6 100644 --- a/docs/examples/calliope/variants/storage_inter_cluster.md +++ b/docs/examples/calliope/variants/storage_inter_cluster.md @@ -40,11 +40,11 @@ relations: values: timesteps parameters: - storage_retention: + storage_loss: description: >- - `1 - storage_loss` — the share of what a store holds that it keeps for - an hour, data prep. Between days it is raised to 24, so it does not - vary over time steps here + `storage_loss` — the share of what a store holds that it loses in an + hour. Between days it is raised to 24, so it does not vary over time + steps here dims: [nodes, techs] variables: @@ -95,7 +95,7 @@ expressions: initial: when: position(datesteps) == 0 AND NOT cyclic_storage expression: storage_initial - otherwise: storage_retention ** 24 * shift(storage_inter_cluster, along=datesteps, offset=1, edge='wrap') + otherwise: (1 - storage_loss) ** 24 * shift(storage_inter_cluster, along=datesteps, offset=1, edge='wrap') storage_intra: description: >- `$storage_intra` of `balance_storage_inter` — what the clustered day of @@ -118,7 +118,7 @@ constraints: an initial fill carries it between days at the end, after a day's loss dims: [nodes, techs, datesteps] where: position(datesteps) == -1 AND storage_inter_cluster AND storage_initial AND cyclic_storage - expression: storage_inter_cluster * storage_retention ** 24 == storage_initial * storage_cap + expression: storage_inter_cluster * (1 - storage_loss) ** 24 == storage_initial * storage_cap storage_intra_max: description: "`storage_intra_max` — a store holds at most its most within its clustered day" dims: [nodes, techs, timesteps] @@ -141,7 +141,7 @@ constraints: dims: [nodes, techs, datesteps] where: include_storage OR base_tech == 'storage' expression: >- - storage_inter_cluster * storage_retention ** 24 + storage_inter_cluster * (1 - storage_loss) ** 24 + at(storage_intra_cluster_min, by=lookup_datestep_cluster, over=clusters, into=datesteps) >= 0 balance_storage_inter: description: >- @@ -183,13 +183,13 @@ assumptions: **`storage_previous_step`** ```math -\mathit{storage}^{\mathrm{previous,step}}_{n,i,t} = \begin{cases} \mathrm{storage}^{\mathrm{initial}}_{n,i} \cdot \mathit{storage}^{\mathrm{cap}}_{n,i} & \text{if } \mathrm{pos}(t) = 0 \wedge \neg \mathrm{cyclic\_storage}_{n,i} \\ 0 & \text{if } \mathrm{lookup\_cluster\_last\_timestep}(t) \text{ is defined} \wedge \neg \left( \mathrm{pos}(t) = 0 \wedge \neg \mathrm{cyclic\_storage}_{n,i} \right) \\ \mathrm{storage}^{\mathrm{retention}}_{n,i}^{\mathrm{timestep\_resolution}_{t \ominus 1}} \cdot \mathit{storage}_{n,i,t \ominus 1} & \text{otherwise} \end{cases} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T} +\mathit{storage}^{\mathrm{previous,step}}_{n,i,t} = \begin{cases} \mathrm{storage}^{\mathrm{initial}}_{n,i} \cdot \mathit{storage}^{\mathrm{cap}}_{n,i} & \text{if } \mathrm{pos}(t) = 0 \wedge \neg \mathrm{cyclic\_storage}_{n,i} \\ 0 & \text{if } \mathrm{lookup\_cluster\_last\_timestep}(t) \text{ is defined} \wedge \neg \left( \mathrm{pos}(t) = 0 \wedge \neg \mathrm{cyclic\_storage}_{n,i} \right) \\ \left( 1 - \mathrm{storage}^{\mathrm{loss}}_{n,i} \right)^{\mathrm{timestep\_resolution}_{t \ominus 1}} \cdot \mathit{storage}_{n,i,t \ominus 1} & \text{otherwise} \end{cases} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ t \in \mathcal{T} ``` **`storage_inter_previous_step`** ```math -\mathit{storage}^{\mathrm{inter,previous,step}}_{n,i,d} = \begin{cases} \mathrm{storage}^{\mathrm{initial}}_{n,i} & \text{if } \mathrm{pos}(d) = 0 \wedge \neg \mathrm{cyclic\_storage}_{n,i} \\ \mathrm{storage}^{\mathrm{retention}}_{n,i}^{24} \cdot \mathit{storage}^{\mathrm{inter,cluster}}_{n,i,d \ominus 1} & \text{otherwise} \end{cases} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ d \in \mathcal{D} +\mathit{storage}^{\mathrm{inter,previous,step}}_{n,i,d} = \begin{cases} \mathrm{storage}^{\mathrm{initial}}_{n,i} & \text{if } \mathrm{pos}(d) = 0 \wedge \neg \mathrm{cyclic\_storage}_{n,i} \\ \left( 1 - \mathrm{storage}^{\mathrm{loss}}_{n,i} \right)^{24} \cdot \mathit{storage}^{\mathrm{inter,cluster}}_{n,i,d \ominus 1} & \text{otherwise} \end{cases} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ d \in \mathcal{D} ``` **`storage_intra`** @@ -201,7 +201,7 @@ assumptions: **`set_storage_initial`** ```math -\mathit{storage}^{\mathrm{inter,cluster}}_{n,i,d} \cdot \mathrm{storage}^{\mathrm{retention}}_{n,i}^{24} = \mathrm{storage}^{\mathrm{initial}}_{n,i} \cdot \mathit{storage}^{\mathrm{cap}}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ d \in \mathcal{D} \,:\, \mathrm{pos}(d) = \lvert \mathcal{D} \rvert - 1 \wedge \mathit{storage}^{\mathrm{inter,cluster}}_{n,i,d} \text{ exists} \wedge \mathrm{storage}^{\mathrm{initial}}_{n,i} \text{ is defined} \wedge \mathrm{cyclic\_storage}_{n,i} +\mathit{storage}^{\mathrm{inter,cluster}}_{n,i,d} \cdot \left( 1 - \mathrm{storage}^{\mathrm{loss}}_{n,i} \right)^{24} = \mathrm{storage}^{\mathrm{initial}}_{n,i} \cdot \mathit{storage}^{\mathrm{cap}}_{n,i} \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ d \in \mathcal{D} \,:\, \mathrm{pos}(d) = \lvert \mathcal{D} \rvert - 1 \wedge \mathit{storage}^{\mathrm{inter,cluster}}_{n,i,d} \text{ exists} \wedge \mathrm{storage}^{\mathrm{initial}}_{n,i} \text{ is defined} \wedge \mathrm{cyclic\_storage}_{n,i} ``` **`storage_intra_max`** @@ -225,7 +225,7 @@ assumptions: **`storage_inter_min`** ```math -\mathit{storage}^{\mathrm{inter,cluster}}_{n,i,d} \cdot \mathrm{storage}^{\mathrm{retention}}_{n,i}^{24} + \mathit{storage}^{\mathrm{intra,cluster,min}}_{n,i,\mathrm{lookup\_datestep\_cluster}(d)} \ge 0 \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ d \in \mathcal{D} \,:\, \mathrm{include\_storage}_{n,i} \vee \mathrm{base\_tech}_{i} = \text{'}\mathrm{storage}\text{'} +\mathit{storage}^{\mathrm{inter,cluster}}_{n,i,d} \cdot \left( 1 - \mathrm{storage}^{\mathrm{loss}}_{n,i} \right)^{24} + \mathit{storage}^{\mathrm{intra,cluster,min}}_{n,i,\mathrm{lookup\_datestep\_cluster}(d)} \ge 0 \qquad \forall\, n \in \mathcal{N},\ i \in \mathcal{I},\ d \in \mathcal{D} \,:\, \mathrm{include\_storage}_{n,i} \vee \mathrm{base\_tech}_{i} = \text{'}\mathrm{storage}\text{'} ``` **`balance_storage_inter`** diff --git a/examples/calliope/cost.yaml b/examples/calliope/cost.yaml index de4124d2..a0e06a32 100644 --- a/examples/calliope/cost.yaml +++ b/examples/calliope/cost.yaml @@ -35,12 +35,6 @@ parameters: `lifetime` — the years a technology lasts. Calliope's default is `.inf`, and data prep fills it dims: [nodes, techs] - cost_annuity_factor: - description: >- - the annuity factor `r (1 + r) ** lifetime / ((1 + r) ** lifetime - 1)` - of the interest rate `r`, data prep. mathspec refuses a sum as the - base of `**` and as a divisor, over parameters too - dims: [nodes, techs, costs] given: parameters: @@ -87,7 +81,9 @@ expressions: no_interest: when: NOT cost_depreciation_rate AND (NOT cost_interest_rate OR cost_interest_rate == 0) expression: 1 / lifetime - otherwise: cost_annuity_factor + otherwise: >- + cost_interest_rate * (1 + cost_interest_rate) ** lifetime + / ((1 + cost_interest_rate) ** lifetime - 1) cost_investment_annualised: description: "`cost_investment_annualised` — the investment cost, as a year's share scaled to the modelled time" expression: annualisation_weight * depreciation_rate * cost_investment @@ -96,4 +92,4 @@ expressions: expression: cost_investment_annualised + sum(cost_operation_variable, over=timesteps) + cost_operation_fixed cost_of_techs: description: "`sum(sum(cost, over=[nodes, techs]) * objective_cost_weights, over=costs)` of `min_cost_optimisation`" - expression: sum(sum(sum(cost, over=nodes), over=techs) * objective_cost_weights) + expression: sum(sum(cost, over=[nodes, techs]) * objective_cost_weights) diff --git a/examples/calliope/extensions/annual_energy_balance.yaml b/examples/calliope/extensions/annual_energy_balance.yaml index c20611c2..7c631df9 100644 --- a/examples/calliope/extensions/annual_energy_balance.yaml +++ b/examples/calliope/extensions/annual_energy_balance.yaml @@ -62,12 +62,12 @@ constraints: description: "`annual_energy_balance_per_tech_and_node` — a technology at a node puts out at most its annual limit" dims: [nodes, techs] where: annual_flow_max - expression: sum(sum(flow_out, over=carriers), over=timesteps) <= annual_flow_max + expression: sum(flow_out, over=[carriers, timesteps]) <= annual_flow_max annual_energy_balance_global_per_tech: description: "`annual_energy_balance_global_per_tech` — a technology puts out at most its annual limit over every node" dims: [techs] where: annual_flow_max - expression: sum(sum(sum(flow_out, over=nodes), over=carriers), over=timesteps) <= annual_flow_max + expression: sum(flow_out, over=[nodes, carriers, timesteps]) <= annual_flow_max annual_energy_balance_global_multi_tech: description: "`annual_energy_balance_global_multi_tech` — the group of technologies puts out at most its annual limit over every node" dims: [] @@ -81,9 +81,9 @@ constraints: one, which is where `source_use` is built dims: [techs] where: base_tech == 'supply' AND annual_source_max - expression: sum(sum(source_use, over=nodes), over=timesteps) <= annual_source_max + expression: sum(source_use, over=[nodes, timesteps]) <= annual_source_max annual_energy_balance_total_sink_availability: description: "`annual_energy_balance_total_sink_availability` — a demand technology takes in at most its annual limit" dims: [techs] where: base_tech == 'demand' AND annual_sink_max - expression: sum(sum(sum(flow_in, over=nodes), over=carriers), over=timesteps) <= annual_sink_max + expression: sum(flow_in, over=[nodes, carriers, timesteps]) <= annual_sink_max diff --git a/examples/calliope/extensions/monthly_peak_flow_charge.yaml b/examples/calliope/extensions/monthly_peak_flow_charge.yaml index 97bbbaa6..33ee5c6f 100644 --- a/examples/calliope/extensions/monthly_peak_flow_charge.yaml +++ b/examples/calliope/extensions/monthly_peak_flow_charge.yaml @@ -51,7 +51,7 @@ variables: expressions: cost_month_peak_charge: description: "`sum(cost_month_peak * flow_peak_month, over=[carriers, months])` — the term Calliope writes into `cost_operation_fixed`, by restating it whole" - expression: sum(sum(cost_month_peak * flow_peak_month, over=carriers), over=months) + expression: sum(cost_month_peak * flow_peak_month, over=[carriers, months]) given: parameters: diff --git a/examples/calliope/extensions/net_import_share.yaml b/examples/calliope/extensions/net_import_share.yaml index f857317d..9b193f92 100644 --- a/examples/calliope/extensions/net_import_share.yaml +++ b/examples/calliope/extensions/net_import_share.yaml @@ -86,15 +86,15 @@ constraints: dims: [nodes, timesteps] where: count(count(carrier_out, over=carriers) >= 1 AND base_tech == 'transmission', over=techs) >= 1 expression: >- - net_import_share * sum(sum(electricity_imports, over=techs), over=carriers) - <= sum(sum(electricity_balance, over=techs), over=carriers) + net_import_share * sum(electricity_imports, over=[techs, carriers]) + <= sum(electricity_balance, over=[techs, carriers]) net_annual_import_share_max: description: "`net_annual_import_share_max` — electricity imports at a node are at most their share of its electricity balance over the year" dims: [nodes] where: count(count(carrier_out, over=carriers) >= 1 AND base_tech == 'transmission', over=techs) >= 1 expression: >- - net_import_share * sum(sum(sum(electricity_imports, over=techs), over=carriers), over=timesteps) - <= sum(sum(sum(electricity_balance, over=techs), over=carriers), over=timesteps) + net_import_share * sum(electricity_imports, over=[techs, carriers, timesteps]) + <= sum(electricity_balance, over=[techs, carriers, timesteps]) net_annual_import_share_max_node_group: description: "`net_annual_import_share_max_node_group` — heat imports at nodes `a` and `c` are at most their share of the group's heat balance over the year" dims: [] diff --git a/examples/calliope/extensions/share_all_timesteps.yaml b/examples/calliope/extensions/share_all_timesteps.yaml index 447ebb71..56786060 100644 --- a/examples/calliope/extensions/share_all_timesteps.yaml +++ b/examples/calliope/extensions/share_all_timesteps.yaml @@ -69,13 +69,13 @@ constraints: dims: [nodes, techs] where: demand_share_equals expression: >- - sum(sum(flow_out, over=timesteps), over=carriers) - == sum(sum(at(flow_in, by=demand_share_tech, over=demand, into=techs), over=timesteps), over=carriers) + sum(flow_out, over=[timesteps, carriers]) + == sum(at(flow_in, by=demand_share_tech, over=demand, into=techs), over=[timesteps, carriers]) * demand_share_equals supply_share_equals_per_tech: description: "`supply_share_equals_per_tech` — a technology puts out its share of a node's outflow of a carrier over the whole time" dims: [nodes, techs] where: supply_share_equals expression: >- - sum(sum(supply_share_flow_out, over=carriers), over=timesteps) - == sum(sum(supply_share_all_flow_out, over=carriers), over=timesteps) * supply_share_equals + sum(supply_share_flow_out, over=[carriers, timesteps]) + == sum(supply_share_all_flow_out, over=[carriers, timesteps]) * supply_share_equals diff --git a/examples/calliope/feasibility.yaml b/examples/calliope/feasibility.yaml index 8da98d16..2f10a90c 100644 --- a/examples/calliope/feasibility.yaml +++ b/examples/calliope/feasibility.yaml @@ -38,7 +38,7 @@ expressions: feasibility_carrier_flow: unmet_demand + unused_supply unmet_demand_penalty: description: "`$unmet_demand` of `min_cost_optimisation` — what unmet demand and unused supply cost" - expression: sum(sum(sum(unmet_demand - unused_supply, over=carriers), over=nodes) * timestep_weights) * bigM + expression: sum(sum(unmet_demand - unused_supply, over=[carriers, nodes]) * timestep_weights) * bigM unmet_sum: description: "`unmet_sum` — net unmet demand; reported" expression: unmet_demand + unused_supply diff --git a/examples/calliope/reporting.yaml b/examples/calliope/reporting.yaml index 8da89eae..a559761f 100644 --- a/examples/calliope/reporting.yaml +++ b/examples/calliope/reporting.yaml @@ -37,16 +37,16 @@ expressions: systemwide_capacity_factor: description: "`systemwide_capacity_factor` — the share of its flow capacity a technology puts out over every node and time step" expression: >- - sum(sum(flow_out * timestep_weights, over=nodes), over=timesteps) + sum(flow_out * timestep_weights, over=[nodes, timesteps]) / (sum(flow_cap, over=nodes) * sum(timestep_resolution * timestep_weights, over=timesteps)) total_generation: description: >- `total_generation` — outflow over every node and time step. Calliope weights only the export, as written here - expression: sum(sum(flow_out + flow_export * timestep_weights, over=nodes), over=timesteps) + expression: sum(flow_out + flow_export * timestep_weights, over=[nodes, timesteps]) systemwide_levelised_cost: description: "`systemwide_levelised_cost` — a technology's cost per unit of what it generates, over every node" expression: sum(cost, over=nodes) / total_generation total_levelised_cost: description: "`total_levelised_cost` — the system's cost per unit of a carrier generated" - expression: sum(sum(cost, over=nodes), over=techs) / sum(total_generation, over=techs) + expression: sum(cost, over=[nodes, techs]) / sum(total_generation, over=techs) diff --git a/examples/calliope/storage.yaml b/examples/calliope/storage.yaml index 76853e97..4c45271d 100644 --- a/examples/calliope/storage.yaml +++ b/examples/calliope/storage.yaml @@ -40,11 +40,8 @@ parameters: storage_initial: description: "`storage_initial` — what a store holds at the start, as a share of its capacity; given only where set" dims: [nodes, techs] - storage_retention: - description: >- - `1 - storage_loss` — the share of what a store holds that it keeps - for an hour, data prep. mathspec refuses a sum as the base of `**`, - over parameters too + storage_loss: + description: "`storage_loss` — the share of what a store holds that it loses in an hour" dims: [nodes, techs, timesteps] cyclic_storage: description: >- @@ -95,10 +92,10 @@ expressions: cluster_start: when: cluster_first_timestep AND NOT (position(timesteps) == 0 AND NOT cyclic_storage) expression: >- - storage_retention ** at(timestep_resolution, by=lookup_cluster_last_timestep, over=last, into=timesteps) + (1 - storage_loss) ** at(timestep_resolution, by=lookup_cluster_last_timestep, over=last, into=timesteps) * at(storage, by=lookup_cluster_last_timestep, over=last, into=timesteps) otherwise: >- - storage_retention ** shift(timestep_resolution, along=timesteps, offset=1, edge='wrap') + (1 - storage_loss) ** shift(timestep_resolution, along=timesteps, offset=1, edge='wrap') * shift(storage, along=timesteps, offset=1, edge='wrap') cost_investment_storage_cap: description: "`cost_investment_storage_cap` — the investment cost of storage capacity" @@ -154,7 +151,7 @@ constraints: store and reads the last step; this builds that row at the last step dims: [nodes, techs, timesteps] where: position(timesteps) == -1 AND storage AND storage_initial AND cyclic_storage - expression: storage * storage_retention ** timestep_resolution == storage_initial * storage_cap + expression: storage * (1 - storage_loss) ** timestep_resolution == storage_initial * storage_cap assumptions: unbounded_storage_cap_cost: diff --git a/examples/calliope/transmission.yaml b/examples/calliope/transmission.yaml index 76efa7f8..d132cffe 100644 --- a/examples/calliope/transmission.yaml +++ b/examples/calliope/transmission.yaml @@ -64,8 +64,8 @@ constraints: dims: [techs, timesteps] where: base_tech == 'transmission' expression: >- - sum(sum(flow_out_inc_eff, over=nodes), over=carriers) - == sum(sum(flow_in_inc_eff, over=nodes), over=carriers) + sum(flow_out_inc_eff, over=[nodes, carriers]) + == sum(flow_in_inc_eff, over=[nodes, carriers]) symmetric_transmission: description: "`symmetric_transmission` — a link has the same flow capacity at both ends" dims: [techs, carriers] diff --git a/examples/calliope/variants/storage_inter_cluster.yaml b/examples/calliope/variants/storage_inter_cluster.yaml index d63c64a2..e63e1314 100644 --- a/examples/calliope/variants/storage_inter_cluster.yaml +++ b/examples/calliope/variants/storage_inter_cluster.yaml @@ -29,11 +29,11 @@ relations: values: timesteps parameters: - storage_retention: + storage_loss: description: >- - `1 - storage_loss` — the share of what a store holds that it keeps for - an hour, data prep. Between days it is raised to 24, so it does not - vary over time steps here + `storage_loss` — the share of what a store holds that it loses in an + hour. Between days it is raised to 24, so it does not vary over time + steps here dims: [nodes, techs] variables: @@ -84,7 +84,7 @@ expressions: initial: when: position(datesteps) == 0 AND NOT cyclic_storage expression: storage_initial - otherwise: storage_retention ** 24 * shift(storage_inter_cluster, along=datesteps, offset=1, edge='wrap') + otherwise: (1 - storage_loss) ** 24 * shift(storage_inter_cluster, along=datesteps, offset=1, edge='wrap') storage_intra: description: >- `$storage_intra` of `balance_storage_inter` — what the clustered day of @@ -107,7 +107,7 @@ constraints: an initial fill carries it between days at the end, after a day's loss dims: [nodes, techs, datesteps] where: position(datesteps) == -1 AND storage_inter_cluster AND storage_initial AND cyclic_storage - expression: storage_inter_cluster * storage_retention ** 24 == storage_initial * storage_cap + expression: storage_inter_cluster * (1 - storage_loss) ** 24 == storage_initial * storage_cap storage_intra_max: description: "`storage_intra_max` — a store holds at most its most within its clustered day" dims: [nodes, techs, timesteps] @@ -130,7 +130,7 @@ constraints: dims: [nodes, techs, datesteps] where: include_storage OR base_tech == 'storage' expression: >- - storage_inter_cluster * storage_retention ** 24 + storage_inter_cluster * (1 - storage_loss) ** 24 + at(storage_intra_cluster_min, by=lookup_datestep_cluster, over=clusters, into=datesteps) >= 0 balance_storage_inter: description: >-