From b1fc9283039c5c6c88d6c1d333bc6f305a6e1855 Mon Sep 17 00:00:00 2001 From: Bryn Pickering <17178478+brynpickering@users.noreply.github.com> Date: Thu, 26 Oct 2023 11:50:29 +0100 Subject: [PATCH 1/4] Update badges and remove pdf gen from rtd (#503) * Update badges to work with current CI / apps * Update badges to all rounded since some can't do flat-square styling * Remove PDF generation of docs since the PDF build has been failing horribly for ~5 months --------- Co-authored-by: Stefan Pfenninger --- .github/workflows/commit-ci.yml | 1 + .github/workflows/pr-ci.yml | 1 + .readthedocs.yml | 1 - README.md | 15 ++++++--------- doc/user/introduction.rst | 2 +- 5 files changed, 9 insertions(+), 11 deletions(-) diff --git a/.github/workflows/commit-ci.yml b/.github/workflows/commit-ci.yml index c68986693..9f512313a 100644 --- a/.github/workflows/commit-ci.yml +++ b/.github/workflows/commit-ci.yml @@ -11,6 +11,7 @@ on: - CITATION - AUTHORS - doc/** + - .readthedocs.yml defaults: run: diff --git a/.github/workflows/pr-ci.yml b/.github/workflows/pr-ci.yml index 4be44298f..6c216489e 100644 --- a/.github/workflows/pr-ci.yml +++ b/.github/workflows/pr-ci.yml @@ -11,6 +11,7 @@ on: - CITATION - AUTHORS - doc/** + - .readthedocs.yml defaults: run: diff --git a/.readthedocs.yml b/.readthedocs.yml index 909c2c81b..406b61077 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -1,5 +1,4 @@ version: 2 # required -formats: [pdf] conda: environment: requirements/docs.yml build: diff --git a/README.md b/README.md index 5f09c120c..61b3e03ad 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -[![Chat on Gitter](https://img.shields.io/gitter/room/calliope-project/calliope.svg?style=flat-square)](https://app.gitter.im/#/room/#calliope-project_calliope:gitter.im) +[![Chat on Gitter](https://img.shields.io/gitter/room/calliope-project/calliope.svg)](https://app.gitter.im/#/room/#calliope-project_calliope:gitter.im) [![Main branch build status](https://github.com/calliope-project/calliope/actions/workflows/commit-ci.yml/badge.svg?branch=main)](https://github.com/calliope-project/calliope/actions/workflows/commit-ci.yml) -[![Documentation build status](https://img.shields.io/readthedocs/calliope.svg?style=flat-square)](https://readthedocs.org/projects/calliope/builds/) +[![Documentation build status](https://img.shields.io/readthedocs/calliope.svg?version=latest)](https://readthedocs.org/projects/calliope/builds/) [![Test coverage](https://codecov.io/gh/calliope-project/calliope/graph/badge.svg?token=UM542yaYrh)](https://codecov.io/gh/calliope-project/calliope) -[![PyPI version](https://img.shields.io/pypi/v/calliope.svg?style=flat-square)](https://pypi.python.org/pypi/calliope) -[![Anaconda.org/conda-forge version](https://img.shields.io/conda/vn/conda-forge/calliope.svg?style=flat-square&label=conda)](https://anaconda.org/conda-forge/calliope) -[![JOSS DOI](https://img.shields.io/badge/JOSS-10.21105/joss.00825-green.svg?style=flat-square)](https://doi.org/10.21105/joss.00825) +[![PyPI version](https://img.shields.io/pypi/v/calliope.svg)](https://pypi.python.org/pypi/calliope) +[![Anaconda.org/conda-forge version](https://img.shields.io/conda/vn/conda-forge/calliope.svg?label=conda)](https://anaconda.org/conda-forge/calliope) +[![JOSS DOI](https://img.shields.io/badge/JOSS-10.21105/joss.00825-green.svg)](https://doi.org/10.21105/joss.00825) --- @@ -49,10 +49,7 @@ More fully-featured examples that have been used in peer-reviewed scientific pub ## Documentation -Documentation is available on Read the Docs: - -- [Read the documentation online (recommended)](https://calliope.readthedocs.io/en/stable/) -- [Download all documentation in a single PDF file](https://readthedocs.org/projects/calliope/downloads/pdf/stable/) +Documentation is available on [Read the Docs](https://calliope.readthedocs.io/en/stable/). ## Contributing diff --git a/doc/user/introduction.rst b/doc/user/introduction.rst index 29ace485e..53614ae3c 100644 --- a/doc/user/introduction.rst +++ b/doc/user/introduction.rst @@ -51,7 +51,7 @@ Development has been partially funded by several grants throughout throughout th * The `Grantham Institute `_ at Imperial College London. * the European Institute of Innovation & Technology's `Climate-KIC program `_. * `Engineering and Physical Sciences Research Council `_, reference number: EP/L016095/1. -* `The Swiss Competence Center for Energy Research − Supply of Electricity (SCCER SoE) `_, contract number 1155002546. +* `The Swiss Competence Center for Energy Research - Supply of Electricity (SCCER SoE) `_, contract number 1155002546. * `Swiss Federal Office for Energy (SFOE) `_, grant number SI/501768-01. * `European Research Council `_ TRIPOD grant, grant agreement number 715132. * `The SENTINEL project `_ of the European Union's Horizon 2020 research and innovation programme under grant agreement No 837089. From 76883ede84fb0666d648af0eae6a1b7285e826f2 Mon Sep 17 00:00:00 2001 From: Bryn Pickering <17178478+brynpickering@users.noreply.github.com> Date: Mon, 30 Oct 2023 09:27:37 +0000 Subject: [PATCH 2/4] Custom math examples (#502) --- .github/workflows/commit-ci.yml | 2 +- .github/workflows/pr-ci.yml | 2 +- .../custom_math/annual_energy_balance.yaml | 51 ++ doc/_static/custom_math/chp_htp.yaml | 129 ++++ .../demand_share_per_timestep_decision.yaml | 68 ++ doc/_static/custom_math/fuel_dist.yaml | 98 +++ doc/_static/custom_math/max_time_varying.yaml | 23 + .../custom_math/piecewise_linear_costs.yaml | 42 ++ .../piecewise_linear_efficiency.yaml | 51 ++ .../custom_math/share_all_timesteps.yaml | 36 ++ .../custom_math/share_per_timestep.yaml | 33 + .../custom_math/uptime_downtime_limits.yaml | 51 ++ requirements/dev.txt | 1 + src/calliope/backend/expression_parser.py | 12 +- src/calliope/backend/helper_functions.py | 7 + src/calliope/math/base.yaml | 12 +- .../common/lp_files/annual_capacity_factor.lp | 41 ++ ...annual_energy_balance_global_multi_tech.lp | 30 + .../annual_energy_balance_global_per_tech.lp | 22 + ...annual_energy_balance_per_tech_and_node.lp | 25 + ..._energy_balance_total_sink_availability.lp | 22 + ...nergy_balance_total_source_availability.lp | 22 + .../lp_files/chp_backpressure_and_boiler.lp | 39 ++ .../lp_files/chp_backpressure_line_equals.lp | 25 + tests/common/lp_files/chp_extraction.lp | 39 ++ .../lp_files/demand_share_equals_per_tech.lp | 33 + ...demand_share_per_timestep_decision_main.lp | 57 ++ .../demand_share_per_timestep_decision_sum.lp | 23 + ...mand_share_per_timestep_equals_per_tech.lp | 39 ++ tests/common/lp_files/downtime_period.lp | 16 + .../lp_files/downtime_period_decision.lp | 21 + tests/common/lp_files/fuel_dist_base.lp | 121 ++++ tests/common/lp_files/fuel_dist_cost.lp | 34 + tests/common/lp_files/fuel_dist_nodal.lp | 31 + .../lp_files/max_time_varying_flow_cap.lp | 37 ++ .../lp_files/max_time_varying_storage.lp | 39 ++ .../lp_files/piecewise_cost_investment.lp | 33 + tests/common/lp_files/piecewise_efficiency.lp | 89 +++ .../lp_files/supply_share_equals_per_tech.lp | 41 ++ ...pply_share_per_timestep_equals_per_tech.lp | 47 ++ tests/common/test_model/model.yaml | 18 + tests/common/test_model/scenarios.yaml | 74 ++- tests/common/util.py | 24 +- tests/conftest.py | 6 +- tests/test_backend_helper_functions.py | 12 + tests/test_example_models.py | 4 - tests/test_math.py | 588 +++++++++++++++++- 47 files changed, 2229 insertions(+), 41 deletions(-) create mode 100644 doc/_static/custom_math/annual_energy_balance.yaml create mode 100644 doc/_static/custom_math/chp_htp.yaml create mode 100644 doc/_static/custom_math/demand_share_per_timestep_decision.yaml create mode 100644 doc/_static/custom_math/fuel_dist.yaml create mode 100644 doc/_static/custom_math/max_time_varying.yaml create mode 100644 doc/_static/custom_math/piecewise_linear_costs.yaml create mode 100644 doc/_static/custom_math/piecewise_linear_efficiency.yaml create mode 100644 doc/_static/custom_math/share_all_timesteps.yaml create mode 100644 doc/_static/custom_math/share_per_timestep.yaml create mode 100644 doc/_static/custom_math/uptime_downtime_limits.yaml create mode 100644 tests/common/lp_files/annual_capacity_factor.lp create mode 100644 tests/common/lp_files/annual_energy_balance_global_multi_tech.lp create mode 100644 tests/common/lp_files/annual_energy_balance_global_per_tech.lp create mode 100644 tests/common/lp_files/annual_energy_balance_per_tech_and_node.lp create mode 100644 tests/common/lp_files/annual_energy_balance_total_sink_availability.lp create mode 100644 tests/common/lp_files/annual_energy_balance_total_source_availability.lp create mode 100644 tests/common/lp_files/chp_backpressure_and_boiler.lp create mode 100644 tests/common/lp_files/chp_backpressure_line_equals.lp create mode 100644 tests/common/lp_files/chp_extraction.lp create mode 100644 tests/common/lp_files/demand_share_equals_per_tech.lp create mode 100644 tests/common/lp_files/demand_share_per_timestep_decision_main.lp create mode 100644 tests/common/lp_files/demand_share_per_timestep_decision_sum.lp create mode 100644 tests/common/lp_files/demand_share_per_timestep_equals_per_tech.lp create mode 100644 tests/common/lp_files/downtime_period.lp create mode 100644 tests/common/lp_files/downtime_period_decision.lp create mode 100644 tests/common/lp_files/fuel_dist_base.lp create mode 100644 tests/common/lp_files/fuel_dist_cost.lp create mode 100644 tests/common/lp_files/fuel_dist_nodal.lp create mode 100644 tests/common/lp_files/max_time_varying_flow_cap.lp create mode 100644 tests/common/lp_files/max_time_varying_storage.lp create mode 100644 tests/common/lp_files/piecewise_cost_investment.lp create mode 100644 tests/common/lp_files/piecewise_efficiency.lp create mode 100644 tests/common/lp_files/supply_share_equals_per_tech.lp create mode 100644 tests/common/lp_files/supply_share_per_timestep_equals_per_tech.lp diff --git a/.github/workflows/commit-ci.yml b/.github/workflows/commit-ci.yml index 9f512313a..ebcf4beed 100644 --- a/.github/workflows/commit-ci.yml +++ b/.github/workflows/commit-ci.yml @@ -26,7 +26,7 @@ jobs: - uses: mamba-org/setup-micromamba@v1 with: micromamba-version: latest - environment-name: ${{ github.event.repository.name }}-ubuntu-latest-311 + environment-name: ${{ github.event.repository.name }}-ubuntu-latest-311-${{ hashFiles('requirements/dev.txt') }} environment-file: requirements/base.txt create-args: >- -f requirements/dev.txt diff --git a/.github/workflows/pr-ci.yml b/.github/workflows/pr-ci.yml index 6c216489e..da9961784 100644 --- a/.github/workflows/pr-ci.yml +++ b/.github/workflows/pr-ci.yml @@ -44,7 +44,7 @@ jobs: - uses: mamba-org/setup-micromamba@v1 with: micromamba-version: latest - environment-name: ${{ github.event.repository.name }}-${{ matrix.os }}-3${{ matrix.py3version }} + environment-name: ${{ github.event.repository.name }}-${{ matrix.os }}-3${{ matrix.py3version }}-${{ hashFiles('requirements/dev.txt') }} environment-file: requirements/base.txt create-args: >- -f requirements/dev.txt diff --git a/doc/_static/custom_math/annual_energy_balance.yaml b/doc/_static/custom_math/annual_energy_balance.yaml new file mode 100644 index 000000000..d1b957131 --- /dev/null +++ b/doc/_static/custom_math/annual_energy_balance.yaml @@ -0,0 +1,51 @@ +# Limit or set the total (e.g. annual) outflow of a technology to a specified absolute value. + +# New technology-level parameters: +# annual_flow_max +# annual_source_max + +# New top-level parameters: +# annual_flow_max (if summing over technologies and/or nodes) +# flow_max_group (if summing over technologies and/or nodes) + +constraints: + annual_energy_balance_per_tech_and_node: + description: Limit total technology annual energy production at each possible deployment site. + foreach: [nodes, techs] + where: annual_flow_max + equations: + - expression: "sum(flow_out, over=[carriers, timesteps]) <= annual_flow_max" + + annual_energy_balance_global_per_tech: + description: Limit total technology annual energy production across all possible deployment sites. + foreach: [techs] + where: annual_flow_max + equations: + - expression: "sum(flow_out, over=[nodes, carriers, timesteps]) <= annual_flow_max" + + annual_energy_balance_global_multi_tech: + description: Limit total combined technology annual energy production across all possible deployment sites. + # To slice a model component with a list of values, we need to use referenced `slices`. + where: annual_flow_max AND flow_max_group + equations: + - expression: "sum(flow_out[techs=$techs], over=[nodes, techs, carriers, timesteps]) <= annual_flow_max" + slices: + techs: + - expression: flow_max_group + + annual_energy_balance_total_source_availability: + description: > + Limit total flow into the system from a particular source. + NOTE: this only works for supply_plus technologies. + For `supply` technologies you will need to convert `flow_out` to `flow_in` using `energy_eff` and limit that. + foreach: [techs] + where: source_use AND annual_source_max + equations: + - expression: "sum(source_use, over=[nodes, techs, timesteps]) <= annual_source_max" + + annual_energy_balance_total_sink_availability: + description: Limit total flow out of the system into a sink that is not pinned by `sink_equals`. + foreach: [techs] + where: inheritance(demand) AND annual_sink_max + equations: + - expression: "sum(flow_in, over=[nodes, carriers, timesteps]) <= annual_sink_max" diff --git a/doc/_static/custom_math/chp_htp.yaml b/doc/_static/custom_math/chp_htp.yaml new file mode 100644 index 000000000..2e307d952 --- /dev/null +++ b/doc/_static/custom_math/chp_htp.yaml @@ -0,0 +1,129 @@ +# Set Combined heat and power (CHP) plant heat to power operational zones. +# There are three types of CHP that can be described by the following constraints: +# 1. CHPs with extraction (condensing) turbines, where some electrical efficiency can be sacrificed by diverting high-temperature steam to provide more heat (following the extraction (cv) line). +# At maximum electrical efficiency, some heat can still be generated from the low-temperature steam output (following the backpressure (cb) line). +# The following figure shows the 2D plane of electricity and heat generation; there is a third dimension not shown here: fuel consumption. +# Along the cv line, fuel consumption remains constant. +# ^ +# |Electricity +# | +# |---- +# | \------- Cv +# | \------- +# | \------- +# | \------ +# | --/ +# | operating region --/ +# | --/ +# | --/ +# | --/ +# | --/ +# | --/ Cb +# | --/ +# | --/ +# | --/ +# | -/ Heat +# --------------------------------------> + +# 2. CHPs without extraction turbines, but with auxiliary boilers that can be used to divert fuel use for direct heat generation. +# At maximum electrical efficiency, some heat can be generated from the low-temperature steam output (following the backpressure (cb) line). +# For the same fuel input, electricity output can be sacrificed by diverting some fuel to direct use in a boiler, with its own efficiency. +# This leads to two sources of heat output (E = electricity output): H1 = E / cb; H2 / boiler_eff + E / turbine_eff = fuel_consumption. Total heat output H is then H1 + H2. +# The following figure shows the 2D plane of electricity and heat generation; there is a third dimension not shown here: fuel consumption. +# ^ +# |Electricity +# | +# | --/ +# | Cb --/ \- +# | --/ \- +# | --/ \- +# | --/ \- +# | --/ operating \- +# | -/ region \- Heat +# ----------------------------------------> + +# 3. CHPs without extraction turbines and without an auxiliary boiler. +# Here, there is no operating region; the output must follow the backpressure line. +# ^ +# |Electricity +# | +# | /-- +# | /-- +# | Cb /-- +# | /-- +# | /- +# | /-- +# | /-- +# | /-- +# | /- +# | /-- Heat +# --------------------------------------> + +# New technology-level parameters: +# turbine_type (used to group constraints together) +# power_loss_factor +# power_to_heat_ratio +# boiler_eff + +constraints: +# Extraction turbine constraints +# ~~ + chp_extraction_line: + description: > + Set the extraction line for combined heat and power plants with extraction turbines. + `power_loss_factor` is also referred to as simply `cv`. + foreach: [nodes, techs, timesteps] + where: turbine_type=extraction + equations: + - expression: > + flow_out[carriers=electricity] + <= (reduce_carrier_dim(flow_in, carrier_tier=in) * energy_eff) + - (flow_out[carriers=heat] * power_loss_factor) + + chp_backpressure_line_min: + description: > + Set the backpressure line as a lower bound for electricity generation in combined heat and power plants with extraction turbines. + `power_to_heat_ratio` is also referred to as the `backpressure ratio` or simply `cb`. + foreach: [nodes, techs, timesteps] + where: turbine_type=extraction + equations: + - expression: flow_out[carriers=electricity] >= flow_out[carriers=heat] * power_to_heat_ratio +# ~~ + +# Backpressure with direct boiler option +# ~~ + chp_backpressure_line_max: + description: > + Set the backpressure line as a lower bound for heat generation in combined heat and power plants without extraction turbines, but with the option to divert fuel to use in direct heat generation (e.g., via a boiler). + `power_to_heat_ratio` is also referred to as the `backpressure ratio` or simply `cb`. + foreach: [nodes, techs, timesteps] + where: turbine_type=backpressure AND boiler_eff + equations: + - expression: flow_out[carriers=electricity] <= flow_out[carriers=heat] * power_to_heat_ratio + + chp_divert_fuel_to_boiler: + description: > + Divert fuel input from use in combined heat and power generation to be used in direct heat generation (e.g., via a boiler). + `heat_eff` is the boiler efficiency. + `power_to_heat_ratio` is also referred to as the `backpressure ratio` or simply `cb`. + foreach: [nodes, techs, timesteps] + where: turbine_type=backpressure AND boiler_eff + equations: + - expression: > + flow_out[carriers=heat] + <= (reduce_carrier_dim(flow_in, carrier_tier=in) * boiler_eff) + - (flow_out[carriers=electricity] * ( + (boiler_eff / energy_eff) - (1 / power_to_heat_ratio) + ) + ) +# ~~ + +# Backpressure only + chp_backpressure_line_equals: + description: > + Fix the backpressure line for combined heat and power plants without extraction turbines. + `power_to_heat_ratio` is also referred to as the `backpressure ratio` or simply `cb`. + foreach: [nodes, techs, timesteps] + where: turbine_type=backpressure AND NOT boiler_eff + equations: + - expression: flow_out[carriers=electricity] == flow_out[carriers=heat] * power_to_heat_ratio \ No newline at end of file diff --git a/doc/_static/custom_math/demand_share_per_timestep_decision.yaml b/doc/_static/custom_math/demand_share_per_timestep_decision.yaml new file mode 100644 index 000000000..7b0fe9b2f --- /dev/null +++ b/doc/_static/custom_math/demand_share_per_timestep_decision.yaml @@ -0,0 +1,68 @@ +# Allows the model to decide on how a fraction of demand for a carrier is met by the given group of technologies, which will each have the same share in each timestep. +# Variables and constraints defined here could be extended to iterate over nodes and over carriers if desired. +# If summing over nodes, remove the summation over nodes in the constraints and add it into the list in `demand_share_per_timestep_decision_sum` (if using). +# If summing over carriers, the slicing of `sink_equals` will need to change per carrier by using a where statement in `slices: ...`. + +# The share is relative to the flow `sink` from a specified `demand` technology (or group thereof) only. + +# Specifying `relaxation` inside the constraint to a non-zero value allows the constraint some flexibility around a given value, making a model easier to solve. + +# New top-level parameters: +# relaxation (defined here directly). +# demand_share_limit. +# decide_demand_share <- Link supply technologies to the demand technology they are going to be a share of. + +variables: + demand_share_per_timestep_decision: + description: Relative share of demand that a given technology must meet per node. + unit: fraction + foreach: [nodes, techs] + where: NOT config.mode=operate AND decide_demand_share + bounds: + min: 0 + max: .inf + +# The two main constraints enforce that the shares are the same in each timestep, +# with an optional relaxation. + +constraints: + demand_share_per_timestep_decision_main_min: + description: Limit the lower bound of a technology's outflow to the share of demand that the model has decided it will meet. + foreach: [nodes, techs, timesteps] + where: demand_share_per_timestep_decision + equations: + - expression: > + flow_out[carriers=$carrier] >= + (1 - $relaxation) * select_from_lookup_arrays(sink_equals, techs=decide_demand_share) * demand_share_per_timestep_decision + sub_expressions: + # 0 == no relaxation, 0.01 == 1% relaxation (lhs == rhs -> lhs >= 0.99 * rhs & lhs <= 1.01 * rhs) + relaxation: &relaxation_component + - where: demand_share_relaxation + expression: demand_share_relaxation + - where: NOT demand_share_relaxation + expression: "0" + slices: &slice + carrier: + - expression: demand_share_carrier # assigned as a top-level parameter + + demand_share_per_timestep_decision_main_max: + description: Limit the upper bound of a technology's outflow to the share of demand that the model has decided it will meet. + foreach: [nodes, techs, timesteps] + where: demand_share_per_timestep_decision + equations: + - expression: > + flow_out[carriers=$carrier] <= + (1 + $relaxation) * select_from_lookup_arrays(sink_equals, techs=decide_demand_share) * demand_share_per_timestep_decision + sub_expressions: + relaxation: *relaxation_component + slices: *slice + +# The optional additional sum constraint ensures that all decision shares add up +# to a specified share of carrier demand (here, 50% = 0.5 of electricity demand). + + demand_share_per_timestep_decision_sum: + description: Limit the total share of demand that can be met by technologies controlled by the `demand_share_per_timestep_decision` variable. + foreach: [nodes, timesteps] + where: demand_share_per_timestep_decision AND demand_share_limit + equations: + - expression: sum(demand_share_per_timestep_decision, over=[techs]) == demand_share_limit \ No newline at end of file diff --git a/doc/_static/custom_math/fuel_dist.yaml b/doc/_static/custom_math/fuel_dist.yaml new file mode 100644 index 000000000..c2bbf0e4d --- /dev/null +++ b/doc/_static/custom_math/fuel_dist.yaml @@ -0,0 +1,98 @@ +# Here, we add in the ability to track the distribution of commodities in the system that do not travel along distinct networks. +# This allows the commodities to imported/exported to/from nodes without tracking exactly where they have come from / where they are going. +# The advantage is a simpler model definition; the disadvantage is not being able to track the source of a commodity that has been imported. +# We refer to "fuels" instead of "commodities" in the below math, but this could refer equally to other commodities that have a corresponding carrier (waste, water, ...). + +# New top-level parameters: +# fuel_import_max +# fuel_export_max +# fuel_distribution_max +# fuel_distributor_costs +# allow_fuel_distribution <- lookup array with a value of `True` for each carrier where you want to track its distribution + +variables: + fuel_distributor: + description: > + Fuel distributor, allowing copperplate transfer of specific carriers between model nodes. + Positive values indicate carrier imports at a node, negative values for carrier exports. + foreach: [nodes, carriers, timesteps] + # change [fuel1, fuel2] to match the carriers you want to allow to be distributed within the system. + where: "allow_fuel_distribution" + bounds: + min: -.inf + max: .inf + +constraints: + # Add variable to existing system balance constraint + system_balance: + equations: + - expression: "sum(flow_out, over=techs) - sum(flow_in, over=techs) - $flow_export + $unmet_demand_and_unused_supply + $fuel_distributor == 0" + sub_expressions: + fuel_distributor: + - where: fuel_distributor + expression: fuel_distributor + - where: NOT fuel_distributor + expression: "0" + + restrict_total_imports_and_exports: + description: > + Ensure all fuel distribution in the system balances to zero. I.e., all regional fuel exports must equal regional fuel imports. + Setting this constraint to an inequality would allow for net imports/exports from/to the system. + foreach: [carriers, timesteps] + where: fuel_distributor + equations: + - expression: sum(fuel_distributor, over=nodes) == 0 + + # fuel_import_max and fuel_export_max could be defined per node and carrier and could be collapsed into one constraining limit e.g., `fuel_distribution_max`. + # If using one constraining limit, two constraints will still be required. + restrict_nodal_imports: + description: > + Ensure all fuel distribution in the system balances to zero. I.e., all regional fuel exports must equal regional fuel imports. + Setting this constraint to an inequality would allow for net imports/exports from/to the system. + foreach: [nodes, carriers, timesteps] + where: fuel_distributor AND fuel_import_max + equations: + - expression: fuel_distributor <= fuel_import_max + + restrict_nodal_exports: + description: > + Ensure all fuel distribution in the system balances to zero. I.e., all regional fuel exports must equal regional fuel imports. + Setting this constraint to an inequality would allow for net imports/exports from/to the system. + foreach: [nodes, carriers, timesteps] + where: fuel_distributor AND fuel_export_max + equations: + - expression: -1 * fuel_distributor <= fuel_export_max + +global_expressions: + # To apply a different cost for imports vs exports, you will need to separate out the fuel_distributor decision variable into two positive decision variables representing imports and exports. + # You will then need to propagate that change throughout all constraints/global expressions/objective function given in this file. + # This change will increase model complexity and if different costs do exist you risk having "unrealistic" simultaneous imports/exports at a node to accrue revenue. + cost_fuel_distribution: + description: Cost of importing / exporting fuel. Exporting fuel will provide a node with revenue, while importing it will incur a cost. + foreach: [nodes, carriers, costs, timesteps] + where: fuel_distributor AND fuel_distributor_costs + equations: + - expression: timestep_weights * fuel_distributor * fuel_distributor_costs + +objectives: + # Update objective to include fuel distribution costs + # NOTE: these additional costs will have no impact on the objective function value _unless_ the cost of fuel distribution is different per node OR a systemwide imbalance in fuel distribution (inequality in `restrict_total_imports_and_exports`) is enabled. + minmax_cost_optimisation: + equations: + - expression: $cost_sum + $unmet_demand + $cost_fuel_distribution_sum + sub_expressions: + cost_sum: + - where: "any(cost, over=[nodes, techs, costs])" + expression: sum(sum(cost, over=[nodes, techs]) * objective_cost_class, over=costs) + - where: "NOT any(cost, over=[nodes, techs, costs])" + expression: "0" + unmet_demand: + - where: "config.ensure_feasibility=True" + expression: sum(sum(unmet_demand - unused_supply, over=[carriers, nodes]) * timestep_weights, over=timesteps) * bigM + - where: "NOT config.ensure_feasibility=True" + expression: "0" + cost_fuel_distribution_sum: + - where: "cost_fuel_distribution" + expression: sum(sum(cost_fuel_distribution, over=[nodes, carriers, timesteps]) * objective_cost_class, over=costs) + - where: "NOT any(cost_fuel_distribution, over=[nodes, carriers, costs, timesteps])" + expression: "0" \ No newline at end of file diff --git a/doc/_static/custom_math/max_time_varying.yaml b/doc/_static/custom_math/max_time_varying.yaml new file mode 100644 index 000000000..93d9b0b6a --- /dev/null +++ b/doc/_static/custom_math/max_time_varying.yaml @@ -0,0 +1,23 @@ +# Set per-timestep variations in limits to out/inflows, which would otherwise be limited by a static value. +# For example, `flow_cap` can be made to fluctuate per timestep above/below its rated value. +# User-defined timeseries parameters need to be in the model inputs for these constraints. +# This can be achieved by defining them for each relevant technology in your YAML and linking it to a CSV file (e.g., `techs.my_tech.constraints.flow_cap_max_relative_per_ts: file=my_file.csv`). + +# New top-level parameters: +# flow_cap_max_relative_per_ts +# storage_max_relative_per_ts + +constraints: + max_time_varying_flow_cap: + description: Limit flow out in each hour according to a time varying fractional limit that is multiplied by the technology flow cap. This represents, for instance, the impact of outdoor temperature on the maximum output of a technology relative to its rated max output. + foreach: [nodes, techs, timesteps] + where: "flow_cap_max_relative_per_ts" + equations: + - expression: "reduce_carrier_dim(flow_out, carrier_tier=out) <= flow_cap_max_relative_per_ts * flow_cap" + + max_time_varying_storage: + description: Limit flow out of a storage device in each hour according to a time varying fractional limit that is multiplied by the technology stored carrier. This represents, for instance, the inability to use all stored energy in a storage device as it is reserved for potential use in a context outside the system boundaries. + foreach: [nodes, techs, timesteps] + where: "storage_max_relative_per_ts" + equations: + - expression: "reduce_carrier_dim(flow_out, carrier_tier=out) <= storage_max_relative_per_ts * storage" diff --git a/doc/_static/custom_math/piecewise_linear_costs.yaml b/doc/_static/custom_math/piecewise_linear_costs.yaml new file mode 100644 index 000000000..3656a033a --- /dev/null +++ b/doc/_static/custom_math/piecewise_linear_costs.yaml @@ -0,0 +1,42 @@ +# Add a piecewise cost function that steadily increases investment costs with increasing technology rated capacity. +# This requires the user to switch on the binary purchase decision variable for any relevant technology. +# Without the purchase decision variable, the technology will have non-zero costs irrespective even if the technology capacity is zero. + +# New top-level parameters: +# cost_flow_cap_piecewise_slopes (defining the new parameter `pieces`) +# cost_flow_cap_piecewise_intercept (defining the new parameter `pieces`) + +variables: + piecewise_cost_investment: + description: Investment cost that increases monotonically + foreach: [nodes, techs, costs] + where: cost_flow_cap_piecewise_slopes AND cost_flow_cap_piecewise_intercept AND purchased + bounds: + min: 0 + max: .inf + +constraints: + piecewise_costs: + description: > + Limit the lower bound of piecewise investment costs to monotonically increasing values. + Since the model is cost minimising, this is equivalent to forcing the investment cost to a specific point along the curve that is traced by the superposition of all pieces. + foreach: [nodes, techs, costs, pieces] + where: piecewise_cost_investment + equations: + - expression: > + piecewise_cost_investment >= + cost_flow_cap_piecewise_slopes * flow_cap + cost_flow_cap_piecewise_intercept * purchased + +# We inject this new source of into the `cost` global expression +global_expressions: + cost: + foreach: [nodes, techs, costs] + where: "cost_investment OR cost_var OR piecewise_cost_investment" + equations: + - expression: $cost_investment + $cost_var_sum + $piecewise_cost_investment + sub_expressions: + piecewise_cost_investment: + - where: "piecewise_cost_investment" + expression: annualisation_weight * cost_depreciation_rate * piecewise_cost_investment + - where: "NOT piecewise_cost_investment" + expression: "0" diff --git a/doc/_static/custom_math/piecewise_linear_efficiency.yaml b/doc/_static/custom_math/piecewise_linear_efficiency.yaml new file mode 100644 index 000000000..1102ae210 --- /dev/null +++ b/doc/_static/custom_math/piecewise_linear_efficiency.yaml @@ -0,0 +1,51 @@ +# Add a piecewise technology efficiency function that steadily increases technology efficiency as outflow increases. +# This requires the user to switch on the integer operating_units decision variable for any relevant technology. +# Without the operating_units decision variable, the technology will have non-zero inflow requirements even if the technology capacity is zero and it has no outflow. + +# New top-level parameters: +# flow_eff_piecewise_slopes (defining the new parameter `pieces`) +# flow_eff_piecewise_intercept (defining the new parameter `pieces`) + +variables: + available_flow_cap: + description: Flow capacity that will be set to zero if the technology is not operating in a given timestep and will be set to the value of the decision variable `flow_cap` otherwise. + unit: power + foreach: [nodes, techs, timesteps] + where: operating_units AND flow_cap_max AND NOT flow_cap_per_unit + bounds: + min: 0 + max: flow_cap_max + +constraints: + piecewise_efficiency: + description: > + Limit the lower bound of inflow requirements to monotonically increasing values as the quantity of technology outflow increases. + Since the model is cost minimising and higher inflows translate to higher costs (because more carrier flow is needed in the system), this is equivalent to forcing the inflow to a specific point along the curve that is traced by the superposition of all pieces. + foreach: [nodes, techs, timesteps, pieces] + where: flow_eff_piecewise_slopes AND flow_eff_piecewise_intercept AND available_flow_cap + equations: + - expression: > + reduce_carrier_dim(flow_in, carrier_tier=in) >= + flow_eff_piecewise_slopes * reduce_carrier_dim(flow_out, carrier_tier=out) + + flow_eff_piecewise_intercept * available_flow_cap + + available_flow_cap_binary: + description: Limit flow capacity to zero if the technology is not operating in a given timestep. + foreach: [nodes, techs, timesteps] + where: available_flow_cap + equations: + - expression: available_flow_cap <= flow_cap_max * operating_units + + available_flow_cap_continuous: + description: Limit flow capacity to the value of the `flow_cap` decision variable when the technology is operating in a given timestep. + foreach: [nodes, techs, timesteps] + where: available_flow_cap + equations: + - expression: available_flow_cap <= flow_cap + + available_flow_cap_binary_continuous_switch: + description: Force flow capacity to equal the value of the `flow_cap` decision variable if the technology is operating in a given timestep, zero otherwise. + foreach: [nodes, techs, timesteps] + where: available_flow_cap + equations: + - expression: available_flow_cap >= flow_cap + ((operating_units - 1) * flow_cap_max) \ No newline at end of file diff --git a/doc/_static/custom_math/share_all_timesteps.yaml b/doc/_static/custom_math/share_all_timesteps.yaml new file mode 100644 index 000000000..007b7b815 --- /dev/null +++ b/doc/_static/custom_math/share_all_timesteps.yaml @@ -0,0 +1,36 @@ +# Set the share of a technology's (or group thereof) total out/inflow met by other technologies to specific values. +# If a single technology, it needs to be explicitly defined (e.g., `flow_in[techs=demand_power]`). +# If a group of technologies, they can be explicitly defined as a list or can be consolidated by a shared attribute (e.g., they produce a specific carrier: `flow_out[carriers=power]`) +# The parameters (`demand_share_equals`, `supply_share_equals`) can be defined per technology (and optionally per node) (e.g., `techs.my_tech.constraints.demand_share_equals: 0.5`) + +# New technology-level parameters: +# demand_share_equals +# supply_share_equals + +constraints: + demand_share_equals_per_tech: + description: Set the total outflow of certain technologies which produce the `power` carrier to a share of total demand inflow. + foreach: [nodes, techs] + where: demand_share_equals + equations: + - expression: > + sum(reduce_carrier_dim(flow_out, carrier_tier=out), over=[timesteps]) == + sum(flow_in[techs=$demand_tech], over=[timesteps, carriers]) + * demand_share_equals + slices: + demand_tech: + - expression: demand_share_tech # assigned as a top-level parameter + + + supply_share_equals_per_tech: + description: Set the total outflow of certain technologies which produce the `power` carrier to a share of total `power` outflow in each node. + foreach: [nodes, techs] + where: supply_share_equals + equations: + - expression: > + sum(flow_out[carriers=$carrier], over=[timesteps]) == + sum(flow_out[carriers=$carrier], over=[techs, timesteps]) + * supply_share_equals + slices: + carrier: + - expression: supply_share_carrier # assigned as a top-level parameter \ No newline at end of file diff --git a/doc/_static/custom_math/share_per_timestep.yaml b/doc/_static/custom_math/share_per_timestep.yaml new file mode 100644 index 000000000..1b9cb5e6c --- /dev/null +++ b/doc/_static/custom_math/share_per_timestep.yaml @@ -0,0 +1,33 @@ +# Set the per-timestep share of a technology's (or group thereof) out/inflow met by other technologies to specific values. +# If a single technology, it needs to be explicitly defined (e.g., `flow_in[techs=demand_power]`). +# If a group of technologies, they can be explicitly defined as a list or can be consolidated by a shared attribute (e.g., they produce a specific carrier: `flow_out[carriers=power]`) +# The parameters (demand_share_per_timestep_equals, supply_share_per_timestep_equals) can be defined per technology (and optionally per node) as a single value or as a time varying value (e.g., `techs.my_tech.constraints.demand_share_per_timestep_equals: file=my_file.csv`) + +# New technology-level parameters: +# demand_share_per_timestep_equals +# supply_share_per_timestep_equals + +constraints: + demand_share_per_timestep_equals_per_tech: + description: Set the per-timestep outflow of certain technologies which produce the `power` carrier to a share of demand inflow. + foreach: [nodes, techs, timesteps] + where: demand_share_per_timestep_equals + equations: + - expression: > + reduce_carrier_dim(flow_out, carrier_tier=out) == + sum(flow_in[techs=$demand_tech], over=carriers) * demand_share_per_timestep_equals + slices: + demand_tech: + - expression: demand_share_tech # assigned as a top-level parameter + + supply_share_per_timestep_equals_per_tech: + description: Set the per-timestep outflow of certain technologies which produce the `power` carrier to a share of all `power` outflow in each node. + foreach: [nodes, techs, timesteps] + where: supply_share_per_timestep_equals + equations: + - expression: > + flow_out[carriers=$carrier] == + sum(flow_out[carriers=$carrier], over=techs) * supply_share_per_timestep_equals + slices: + carrier: + - expression: supply_share_carrier # assigned as a top-level parameter \ No newline at end of file diff --git a/doc/_static/custom_math/uptime_downtime_limits.yaml b/doc/_static/custom_math/uptime_downtime_limits.yaml new file mode 100644 index 000000000..d8b1b4933 --- /dev/null +++ b/doc/_static/custom_math/uptime_downtime_limits.yaml @@ -0,0 +1,51 @@ +# Here we define various different constraints to represent the same intent: to stop a technology from operating too little or too much in a year. +# This can be used to represent technology downtime when e.g. undergoing maintenance. +# It can also be used to represent a simplistic inability to ramp technologies up/down too often, e.g. for nuclear power plants. +# The exact constraint you activate depends on how resolved your technologies are. +# For instance, the two annual capacity factor constraints can work to represent a fleet of nuclear power plants which tend to operate within an annual capacity factor range of e.g., 75-85%. +# The `downtime_period` constraint can be used if you know a specific period in time where your technology will need to be down for maintenance and can be defined by setting a value in those timesteps, leaving all other timesteps empty (i.e., NaN). +# The `downtime_period_decision` constraint will allow a technology with integer decision variables activated to decide on a certain number of timesteps in the year to not be operating, although it is not possible to enforce that these downtime periods are consecutive. + +# New technology-level parameters +# capacity_factor_min +# capacity_factor_max +# downtime_periods (from CSV as a timeseries) +# uptime_limit + +constraints: + annual_capacity_factor_min: + description: Limit the lower bound of annual technology operation as a fraction of annual operation at maximum capacity. + foreach: [nodes, techs] + where: capacity_factor_min + equations: + - expression: > + sum(reduce_carrier_dim(flow_out, carrier_tier=out) * timestep_weights, over=timesteps) >= + flow_cap * capacity_factor_min * $total_time + sub_expressions: + total_time: &total_time + - expression: sum(timestep_resolution * timestep_weights, over=timesteps) + + annual_capacity_factor_max: + description: Limit the upper bound of annual technology operation as a fraction of annual operation at maximum capacity. + foreach: [nodes, techs] + where: capacity_factor_max + equations: + - expression: > + sum(reduce_carrier_dim(flow_out, carrier_tier=out) * timestep_weights, over=[timesteps]) <= + flow_cap * capacity_factor_max * $total_time + sub_expressions: + total_time: *total_time + + downtime_period: + description: Force outflow to zero in pre-defined timesteps of technology downtime. + foreach: [nodes, techs, timesteps] + where: downtime_periods + equations: + - expression: sum(flow_out, over=carriers) == 0 + + downtime_period_decision: + description: Force operation to zero in an integer number of timesteps, for technologies with integer decision variables activated. + foreach: [nodes, techs] + where: operating_units AND uptime_limit + equations: + - expression: sum(operating_units * timestep_weights, over=timesteps) <= uptime_limit diff --git a/requirements/dev.txt b/requirements/dev.txt index 1c3a8be25..87d7eab50 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -6,6 +6,7 @@ hdf5 < 2 libnetcdf < 5 pre-commit < 4 pytest-cov < 5 +pytest-order < 2 pytest-xdist < 4 # pytest distributed testing plugin pytest < 8 nbmake < 2 \ No newline at end of file diff --git a/src/calliope/backend/expression_parser.py b/src/calliope/backend/expression_parser.py index 4a0f12b10..94e5fc357 100644 --- a/src/calliope/backend/expression_parser.py +++ b/src/calliope/backend/expression_parser.py @@ -133,7 +133,12 @@ def _eval( if not as_latex: evaluated = xr.DataArray(evaluated) if eval_kwargs.get("apply_where", True): - evaluated = evaluated.where(eval_kwargs["where"]) + try: + evaluated = evaluated.where(eval_kwargs["where"]) + except AttributeError: + evaluated = evaluated.broadcast_like(eval_kwargs["where"]).where( + eval_kwargs["where"] + ) return evaluated @@ -500,7 +505,10 @@ def eval( if eval_kwargs.get("as_dict"): return {"slice_reference": self.name} elif slice_dict is not None: - return slice_dict[self.name][0].eval(as_values=True, **eval_kwargs) + slicer = slice_dict[self.name][0].eval(as_values=True, **eval_kwargs) + if isinstance(slicer, xr.DataArray) and slicer.isnull().any(): + slicer = slicer.notnull() + return slicer class EvalSubExpressions(EvalString): diff --git a/src/calliope/backend/helper_functions.py b/src/calliope/backend/helper_functions.py index 3bccc91cc..a440c1f97 100644 --- a/src/calliope/backend/helper_functions.py +++ b/src/calliope/backend/helper_functions.py @@ -512,6 +512,13 @@ def as_array( for index_dim, index in lookup_arrays.items(): stacked_lookup = self._kwargs["model_data"][index.name].stack({dim: dims}) ix = array.indexes[index_dim].get_indexer(stacked_lookup) + if (ix == -1).all(): + received_lookup = ( + self._kwargs["model_data"][index.name].to_series().dropna() + ) + raise IndexError( + f"Trying to select items on the dimension {index_dim} from the {index.name} lookup array, but no matches found. Received: {received_lookup}" + ) ixs[index_dim] = xr.DataArray( np.fmax(0, ix), coords={dim: stacked_lookup[dim]}, diff --git a/src/calliope/math/base.yaml b/src/calliope/math/base.yaml index c23569a10..b99c1c10a 100644 --- a/src/calliope/math/base.yaml +++ b/src/calliope/math/base.yaml @@ -277,14 +277,14 @@ constraints: balance_storage: description: "Fix the quantity of carrier stored in a `storage` technology at the end of each timestep based on the net flow of carrier charged and discharged and the quantity of carrier stored at the start of the timestep." - foreach: [nodes, techs, carriers, timesteps] + foreach: [nodes, techs, timesteps] where: "inheritance(storage)" equations: - - expression: storage == $storage_previous_step - $flow_out + flow_in * flow_eff + - expression: storage == $storage_previous_step - $flow_out + reduce_carrier_dim(flow_in, carrier_tier=in) * flow_eff sub_expressions: flow_out: - where: flow_eff > 0 - expression: flow_out / flow_eff + expression: reduce_carrier_dim(flow_out, carrier_tier=out) / flow_eff - where: flow_eff = 0 expression: "0" storage_previous_step: *storage_previous_step @@ -540,16 +540,16 @@ variables: description: "The upper limit on a carrier that can be stored by a `supply_plus` or `storage` technology in any timestep." unit: energy foreach: [nodes, techs] - where: "(inheritance(storage) OR inheritance(supply_plus)) AND include_storage=True" + where: "include_storage=True" bounds: min: storage_cap_min max: storage_cap_max storage: - description: "The carrier stored by a `supply_plus` or `storage` technology in each timestep." + description: "The carrier stored by a `storage` technology in each timestep." unit: energy foreach: [nodes, techs, timesteps] - where: "(inheritance(storage) OR inheritance(supply_plus)) AND include_storage=True" + where: "include_storage=True" bounds: min: 0 max: .inf diff --git a/tests/common/lp_files/annual_capacity_factor.lp b/tests/common/lp_files/annual_capacity_factor.lp new file mode 100644 index 000000000..52215a557 --- /dev/null +++ b/tests/common/lp_files/annual_capacity_factor.lp @@ -0,0 +1,41 @@ +\* Source Pyomo model name=None *\ + +min +objectives(dummy_obj)(0): ++2.0 ONE_VAR_CONSTANT + +s.t. + +c_u_constraints(annual_capacity_factor_min)(a__test_supply_elec)_: ++1.6 variables(flow_cap)(a__test_supply_elec) +-1.0 variables(flow_out)(a__test_supply_elec__electricity__2005_01_01_00_00) +-1.0 variables(flow_out)(a__test_supply_elec__electricity__2005_01_01_01_00) +<= 0.0 + +c_u_constraints(annual_capacity_factor_min)(b__test_supply_elec)_: ++1.6 variables(flow_cap)(b__test_supply_elec) +-1.0 variables(flow_out)(b__test_supply_elec__electricity__2005_01_01_00_00) +-1.0 variables(flow_out)(b__test_supply_elec__electricity__2005_01_01_01_00) +<= 0.0 + +c_u_constraints(annual_capacity_factor_max)(a__test_supply_elec)_: +-1.8 variables(flow_cap)(a__test_supply_elec) ++1.0 variables(flow_out)(a__test_supply_elec__electricity__2005_01_01_00_00) ++1.0 variables(flow_out)(a__test_supply_elec__electricity__2005_01_01_01_00) +<= 0.0 + +c_u_constraints(annual_capacity_factor_max)(b__test_supply_elec)_: +-1.8 variables(flow_cap)(b__test_supply_elec) ++1.0 variables(flow_out)(b__test_supply_elec__electricity__2005_01_01_00_00) ++1.0 variables(flow_out)(b__test_supply_elec__electricity__2005_01_01_01_00) +<= 0.0 + +bounds + 1 <= ONE_VAR_CONSTANT <= 1 + 0 <= variables(flow_cap)(a__test_supply_elec) <= 10.0 + 0 <= variables(flow_out)(a__test_supply_elec__electricity__2005_01_01_00_00) <= +inf + 0 <= variables(flow_out)(a__test_supply_elec__electricity__2005_01_01_01_00) <= +inf + 0 <= variables(flow_cap)(b__test_supply_elec) <= 10.0 + 0 <= variables(flow_out)(b__test_supply_elec__electricity__2005_01_01_00_00) <= +inf + 0 <= variables(flow_out)(b__test_supply_elec__electricity__2005_01_01_01_00) <= +inf +end diff --git a/tests/common/lp_files/annual_energy_balance_global_multi_tech.lp b/tests/common/lp_files/annual_energy_balance_global_multi_tech.lp new file mode 100644 index 000000000..416517acb --- /dev/null +++ b/tests/common/lp_files/annual_energy_balance_global_multi_tech.lp @@ -0,0 +1,30 @@ +\* Source Pyomo model name=None *\ + +min +objectives(dummy_obj)(0): ++2.0 ONE_VAR_CONSTANT + +s.t. + +c_u_constraints(annual_energy_balance_global_multi_tech)_: ++1 variables(flow_out)(a__test_supply_elec__electricity__2005_01_01_00_00) ++1 variables(flow_out)(a__test_supply_elec__electricity__2005_01_01_01_00) ++1 variables(flow_out)(a__test_supply_plus__electricity__2005_01_01_00_00) ++1 variables(flow_out)(a__test_supply_plus__electricity__2005_01_01_01_00) ++1 variables(flow_out)(b__test_supply_elec__electricity__2005_01_01_00_00) ++1 variables(flow_out)(b__test_supply_elec__electricity__2005_01_01_01_00) ++1 variables(flow_out)(b__test_supply_plus__electricity__2005_01_01_00_00) ++1 variables(flow_out)(b__test_supply_plus__electricity__2005_01_01_01_00) +<= 10 + +bounds + 1 <= ONE_VAR_CONSTANT <= 1 + 0 <= variables(flow_out)(a__test_supply_elec__electricity__2005_01_01_00_00) <= +inf + 0 <= variables(flow_out)(a__test_supply_elec__electricity__2005_01_01_01_00) <= +inf + 0 <= variables(flow_out)(a__test_supply_plus__electricity__2005_01_01_00_00) <= +inf + 0 <= variables(flow_out)(a__test_supply_plus__electricity__2005_01_01_01_00) <= +inf + 0 <= variables(flow_out)(b__test_supply_elec__electricity__2005_01_01_00_00) <= +inf + 0 <= variables(flow_out)(b__test_supply_elec__electricity__2005_01_01_01_00) <= +inf + 0 <= variables(flow_out)(b__test_supply_plus__electricity__2005_01_01_00_00) <= +inf + 0 <= variables(flow_out)(b__test_supply_plus__electricity__2005_01_01_01_00) <= +inf +end diff --git a/tests/common/lp_files/annual_energy_balance_global_per_tech.lp b/tests/common/lp_files/annual_energy_balance_global_per_tech.lp new file mode 100644 index 000000000..cec21886f --- /dev/null +++ b/tests/common/lp_files/annual_energy_balance_global_per_tech.lp @@ -0,0 +1,22 @@ +\* Source Pyomo model name=None *\ + +min +objectives(dummy_obj)(0): ++2.0 ONE_VAR_CONSTANT + +s.t. + +c_u_constraints(annual_energy_balance_global_per_tech)(test_supply_elec)_: ++1 variables(flow_out)(a__test_supply_elec__electricity__2005_01_01_00_00) ++1 variables(flow_out)(a__test_supply_elec__electricity__2005_01_01_01_00) ++1 variables(flow_out)(b__test_supply_elec__electricity__2005_01_01_00_00) ++1 variables(flow_out)(b__test_supply_elec__electricity__2005_01_01_01_00) +<= 10.0 + +bounds + 1 <= ONE_VAR_CONSTANT <= 1 + 0 <= variables(flow_out)(a__test_supply_elec__electricity__2005_01_01_00_00) <= +inf + 0 <= variables(flow_out)(a__test_supply_elec__electricity__2005_01_01_01_00) <= +inf + 0 <= variables(flow_out)(b__test_supply_elec__electricity__2005_01_01_00_00) <= +inf + 0 <= variables(flow_out)(b__test_supply_elec__electricity__2005_01_01_01_00) <= +inf +end diff --git a/tests/common/lp_files/annual_energy_balance_per_tech_and_node.lp b/tests/common/lp_files/annual_energy_balance_per_tech_and_node.lp new file mode 100644 index 000000000..f073b3b71 --- /dev/null +++ b/tests/common/lp_files/annual_energy_balance_per_tech_and_node.lp @@ -0,0 +1,25 @@ +\* Source Pyomo model name=None *\ + +min +objectives(dummy_obj)(0): ++2.0 ONE_VAR_CONSTANT + +s.t. + +c_u_constraints(annual_energy_balance_per_tech_and_node)(a__test_supply_elec)_: ++1 variables(flow_out)(a__test_supply_elec__electricity__2005_01_01_00_00) ++1 variables(flow_out)(a__test_supply_elec__electricity__2005_01_01_01_00) +<= 10.0 + +c_u_constraints(annual_energy_balance_per_tech_and_node)(b__test_supply_elec)_: ++1 variables(flow_out)(b__test_supply_elec__electricity__2005_01_01_00_00) ++1 variables(flow_out)(b__test_supply_elec__electricity__2005_01_01_01_00) +<= 20.0 + +bounds + 1 <= ONE_VAR_CONSTANT <= 1 + 0 <= variables(flow_out)(a__test_supply_elec__electricity__2005_01_01_00_00) <= +inf + 0 <= variables(flow_out)(a__test_supply_elec__electricity__2005_01_01_01_00) <= +inf + 0 <= variables(flow_out)(b__test_supply_elec__electricity__2005_01_01_00_00) <= +inf + 0 <= variables(flow_out)(b__test_supply_elec__electricity__2005_01_01_01_00) <= +inf +end diff --git a/tests/common/lp_files/annual_energy_balance_total_sink_availability.lp b/tests/common/lp_files/annual_energy_balance_total_sink_availability.lp new file mode 100644 index 000000000..8a07f47d1 --- /dev/null +++ b/tests/common/lp_files/annual_energy_balance_total_sink_availability.lp @@ -0,0 +1,22 @@ +\* Source Pyomo model name=None *\ + +min +objectives(dummy_obj)(0): ++2.0 ONE_VAR_CONSTANT + +s.t. + +c_u_constraints(annual_energy_balance_total_sink_availability)(test_demand_elec)_: ++1 variables(flow_in)(a__test_demand_elec__electricity__2005_01_01_00_00) ++1 variables(flow_in)(a__test_demand_elec__electricity__2005_01_01_01_00) ++1 variables(flow_in)(b__test_demand_elec__electricity__2005_01_01_00_00) ++1 variables(flow_in)(b__test_demand_elec__electricity__2005_01_01_01_00) +<= 10.0 + +bounds + 1 <= ONE_VAR_CONSTANT <= 1 + 0 <= variables(flow_in)(a__test_demand_elec__electricity__2005_01_01_00_00) <= +inf + 0 <= variables(flow_in)(a__test_demand_elec__electricity__2005_01_01_01_00) <= +inf + 0 <= variables(flow_in)(b__test_demand_elec__electricity__2005_01_01_00_00) <= +inf + 0 <= variables(flow_in)(b__test_demand_elec__electricity__2005_01_01_01_00) <= +inf +end diff --git a/tests/common/lp_files/annual_energy_balance_total_source_availability.lp b/tests/common/lp_files/annual_energy_balance_total_source_availability.lp new file mode 100644 index 000000000..912aaaae9 --- /dev/null +++ b/tests/common/lp_files/annual_energy_balance_total_source_availability.lp @@ -0,0 +1,22 @@ +\* Source Pyomo model name=None *\ + +min +objectives(dummy_obj)(0): ++2.0 ONE_VAR_CONSTANT + +s.t. + +c_u_constraints(annual_energy_balance_total_source_availability)(test_supply_plus)_: ++1 variables(source_use)(a__test_supply_plus__2005_01_01_00_00) ++1 variables(source_use)(a__test_supply_plus__2005_01_01_01_00) ++1 variables(source_use)(b__test_supply_plus__2005_01_01_00_00) ++1 variables(source_use)(b__test_supply_plus__2005_01_01_01_00) +<= 10.0 + +bounds + 1 <= ONE_VAR_CONSTANT <= 1 + 0 <= variables(source_use)(a__test_supply_plus__2005_01_01_00_00) <= +inf + 0 <= variables(source_use)(a__test_supply_plus__2005_01_01_01_00) <= +inf + 0 <= variables(source_use)(b__test_supply_plus__2005_01_01_00_00) <= +inf + 0 <= variables(source_use)(b__test_supply_plus__2005_01_01_01_00) <= +inf +end diff --git a/tests/common/lp_files/chp_backpressure_and_boiler.lp b/tests/common/lp_files/chp_backpressure_and_boiler.lp new file mode 100644 index 000000000..65d5ec182 --- /dev/null +++ b/tests/common/lp_files/chp_backpressure_and_boiler.lp @@ -0,0 +1,39 @@ +\* Source Pyomo model name=None *\ + +min +objectives(dummy_obj)(0): ++2.0 ONE_VAR_CONSTANT + +s.t. + +c_u_constraints(chp_divert_fuel_to_boiler)(a__test_chp__2005_01_01_00_00)_: ++1 variables(flow_out)(a__test_chp__heat__2005_01_01_00_00) +-0.8 variables(flow_in)(a__test_chp__gas__2005_01_01_00_00) ++0.6666666666666669 variables(flow_out)(a__test_chp__electricity__2005_01_01_00_00) +<= 0.0 + +c_u_constraints(chp_divert_fuel_to_boiler)(a__test_chp__2005_01_01_01_00)_: ++1 variables(flow_out)(a__test_chp__heat__2005_01_01_01_00) +-0.8 variables(flow_in)(a__test_chp__gas__2005_01_01_01_00) ++0.6666666666666669 variables(flow_out)(a__test_chp__electricity__2005_01_01_01_00) +<= 0.0 + +c_u_constraints(chp_backpressure_line_max)(a__test_chp__2005_01_01_00_00)_: +-1.5 variables(flow_out)(a__test_chp__heat__2005_01_01_00_00) ++1 variables(flow_out)(a__test_chp__electricity__2005_01_01_00_00) +<= 0.0 + +c_u_constraints(chp_backpressure_line_max)(a__test_chp__2005_01_01_01_00)_: +-1.5 variables(flow_out)(a__test_chp__heat__2005_01_01_01_00) ++1 variables(flow_out)(a__test_chp__electricity__2005_01_01_01_00) +<= 0.0 + +bounds + 1 <= ONE_VAR_CONSTANT <= 1 + 0 <= variables(flow_out)(a__test_chp__heat__2005_01_01_00_00) <= +inf + 0 <= variables(flow_in)(a__test_chp__gas__2005_01_01_00_00) <= +inf + 0 <= variables(flow_out)(a__test_chp__electricity__2005_01_01_00_00) <= +inf + 0 <= variables(flow_out)(a__test_chp__heat__2005_01_01_01_00) <= +inf + 0 <= variables(flow_in)(a__test_chp__gas__2005_01_01_01_00) <= +inf + 0 <= variables(flow_out)(a__test_chp__electricity__2005_01_01_01_00) <= +inf +end diff --git a/tests/common/lp_files/chp_backpressure_line_equals.lp b/tests/common/lp_files/chp_backpressure_line_equals.lp new file mode 100644 index 000000000..680ac71f0 --- /dev/null +++ b/tests/common/lp_files/chp_backpressure_line_equals.lp @@ -0,0 +1,25 @@ +\* Source Pyomo model name=None *\ + +min +objectives(dummy_obj)(0): ++2.0 ONE_VAR_CONSTANT + +s.t. + +c_e_constraints(chp_backpressure_line_equals)(a__test_chp__2005_01_01_00_00)_: ++1 variables(flow_out)(a__test_chp__electricity__2005_01_01_00_00) +-1.25 variables(flow_out)(a__test_chp__heat__2005_01_01_00_00) += 0.0 + +c_e_constraints(chp_backpressure_line_equals)(a__test_chp__2005_01_01_01_00)_: ++1 variables(flow_out)(a__test_chp__electricity__2005_01_01_01_00) +-1.25 variables(flow_out)(a__test_chp__heat__2005_01_01_01_00) += 0.0 + +bounds + 1 <= ONE_VAR_CONSTANT <= 1 + 0 <= variables(flow_out)(a__test_chp__electricity__2005_01_01_00_00) <= +inf + 0 <= variables(flow_out)(a__test_chp__heat__2005_01_01_00_00) <= +inf + 0 <= variables(flow_out)(a__test_chp__electricity__2005_01_01_01_00) <= +inf + 0 <= variables(flow_out)(a__test_chp__heat__2005_01_01_01_00) <= +inf +end diff --git a/tests/common/lp_files/chp_extraction.lp b/tests/common/lp_files/chp_extraction.lp new file mode 100644 index 000000000..28f289d3b --- /dev/null +++ b/tests/common/lp_files/chp_extraction.lp @@ -0,0 +1,39 @@ +\* Source Pyomo model name=None *\ + +min +objectives(dummy_obj)(0): ++2.0 ONE_VAR_CONSTANT + +s.t. + +c_u_constraints(chp_extraction_line)(a__test_chp__2005_01_01_00_00)_: ++1 variables(flow_out)(a__test_chp__electricity__2005_01_01_00_00) +-0.6 variables(flow_in)(a__test_chp__gas__2005_01_01_00_00) ++0.1 variables(flow_out)(a__test_chp__heat__2005_01_01_00_00) +<= 0.0 + +c_u_constraints(chp_extraction_line)(a__test_chp__2005_01_01_01_00)_: ++1 variables(flow_out)(a__test_chp__electricity__2005_01_01_01_00) +-0.6 variables(flow_in)(a__test_chp__gas__2005_01_01_01_00) ++0.1 variables(flow_out)(a__test_chp__heat__2005_01_01_01_00) +<= 0.0 + +c_u_constraints(chp_backpressure_line_min)(a__test_chp__2005_01_01_00_00)_: +-1 variables(flow_out)(a__test_chp__electricity__2005_01_01_00_00) ++2.0 variables(flow_out)(a__test_chp__heat__2005_01_01_00_00) +<= 0.0 + +c_u_constraints(chp_backpressure_line_min)(a__test_chp__2005_01_01_01_00)_: +-1 variables(flow_out)(a__test_chp__electricity__2005_01_01_01_00) ++2.0 variables(flow_out)(a__test_chp__heat__2005_01_01_01_00) +<= 0.0 + +bounds + 1 <= ONE_VAR_CONSTANT <= 1 + 0 <= variables(flow_out)(a__test_chp__electricity__2005_01_01_00_00) <= +inf + 0 <= variables(flow_in)(a__test_chp__gas__2005_01_01_00_00) <= +inf + 0 <= variables(flow_out)(a__test_chp__heat__2005_01_01_00_00) <= +inf + 0 <= variables(flow_out)(a__test_chp__electricity__2005_01_01_01_00) <= +inf + 0 <= variables(flow_in)(a__test_chp__gas__2005_01_01_01_00) <= +inf + 0 <= variables(flow_out)(a__test_chp__heat__2005_01_01_01_00) <= +inf +end diff --git a/tests/common/lp_files/demand_share_equals_per_tech.lp b/tests/common/lp_files/demand_share_equals_per_tech.lp new file mode 100644 index 000000000..d8f63b7ad --- /dev/null +++ b/tests/common/lp_files/demand_share_equals_per_tech.lp @@ -0,0 +1,33 @@ +\* Source Pyomo model name=None *\ + +min +objectives(dummy_obj)(0): ++2.0 ONE_VAR_CONSTANT + +s.t. + +c_e_constraints(demand_share_equals_per_tech)(a__test_supply_elec)_: ++1 variables(flow_out)(a__test_supply_elec__electricity__2005_01_01_00_00) ++1 variables(flow_out)(a__test_supply_elec__electricity__2005_01_01_01_00) +-0.5 variables(flow_in)(a__test_demand_elec__electricity__2005_01_01_00_00) +-0.5 variables(flow_in)(a__test_demand_elec__electricity__2005_01_01_01_00) += 0.0 + +c_e_constraints(demand_share_equals_per_tech)(b__test_supply_elec)_: ++1 variables(flow_out)(b__test_supply_elec__electricity__2005_01_01_00_00) ++1 variables(flow_out)(b__test_supply_elec__electricity__2005_01_01_01_00) +-0.8 variables(flow_in)(b__test_demand_elec__electricity__2005_01_01_00_00) +-0.8 variables(flow_in)(b__test_demand_elec__electricity__2005_01_01_01_00) += 0.0 + +bounds + 1 <= ONE_VAR_CONSTANT <= 1 + 0 <= variables(flow_out)(a__test_supply_elec__electricity__2005_01_01_00_00) <= +inf + 0 <= variables(flow_out)(a__test_supply_elec__electricity__2005_01_01_01_00) <= +inf + 0 <= variables(flow_in)(a__test_demand_elec__electricity__2005_01_01_00_00) <= +inf + 0 <= variables(flow_in)(a__test_demand_elec__electricity__2005_01_01_01_00) <= +inf + 0 <= variables(flow_out)(b__test_supply_elec__electricity__2005_01_01_00_00) <= +inf + 0 <= variables(flow_out)(b__test_supply_elec__electricity__2005_01_01_01_00) <= +inf + 0 <= variables(flow_in)(b__test_demand_elec__electricity__2005_01_01_00_00) <= +inf + 0 <= variables(flow_in)(b__test_demand_elec__electricity__2005_01_01_01_00) <= +inf +end diff --git a/tests/common/lp_files/demand_share_per_timestep_decision_main.lp b/tests/common/lp_files/demand_share_per_timestep_decision_main.lp new file mode 100644 index 000000000..eca605088 --- /dev/null +++ b/tests/common/lp_files/demand_share_per_timestep_decision_main.lp @@ -0,0 +1,57 @@ +\* Source Pyomo model name=None *\ + +min +objectives(dummy_obj)(0): ++2.0 ONE_VAR_CONSTANT + +s.t. + +c_u_constraints(demand_share_per_timestep_decision_main_min)(a__test_conversion_plus__2005_01_01_00_00)_: ++4.95 variables(demand_share_per_timestep_decision)(a__test_conversion_plus) +-1 variables(flow_out)(a__test_conversion_plus__electricity__2005_01_01_00_00) +<= 0.0 + +c_u_constraints(demand_share_per_timestep_decision_main_min)(a__test_conversion_plus__2005_01_01_01_00)_: ++4.95 variables(demand_share_per_timestep_decision)(a__test_conversion_plus) +-1 variables(flow_out)(a__test_conversion_plus__electricity__2005_01_01_01_00) +<= 0.0 + +c_u_constraints(demand_share_per_timestep_decision_main_min)(a__test_supply_elec__2005_01_01_00_00)_: ++4.95 variables(demand_share_per_timestep_decision)(a__test_supply_elec) +-1 variables(flow_out)(a__test_supply_elec__electricity__2005_01_01_00_00) +<= 0.0 + +c_u_constraints(demand_share_per_timestep_decision_main_min)(a__test_supply_elec__2005_01_01_01_00)_: ++4.95 variables(demand_share_per_timestep_decision)(a__test_supply_elec) +-1 variables(flow_out)(a__test_supply_elec__electricity__2005_01_01_01_00) +<= 0.0 + +c_u_constraints(demand_share_per_timestep_decision_main_max)(a__test_conversion_plus__2005_01_01_00_00)_: +-5.05 variables(demand_share_per_timestep_decision)(a__test_conversion_plus) ++1 variables(flow_out)(a__test_conversion_plus__electricity__2005_01_01_00_00) +<= 0.0 + +c_u_constraints(demand_share_per_timestep_decision_main_max)(a__test_conversion_plus__2005_01_01_01_00)_: +-5.05 variables(demand_share_per_timestep_decision)(a__test_conversion_plus) ++1 variables(flow_out)(a__test_conversion_plus__electricity__2005_01_01_01_00) +<= 0.0 + +c_u_constraints(demand_share_per_timestep_decision_main_max)(a__test_supply_elec__2005_01_01_00_00)_: +-5.05 variables(demand_share_per_timestep_decision)(a__test_supply_elec) ++1 variables(flow_out)(a__test_supply_elec__electricity__2005_01_01_00_00) +<= 0.0 + +c_u_constraints(demand_share_per_timestep_decision_main_max)(a__test_supply_elec__2005_01_01_01_00)_: +-5.05 variables(demand_share_per_timestep_decision)(a__test_supply_elec) ++1 variables(flow_out)(a__test_supply_elec__electricity__2005_01_01_01_00) +<= 0.0 + +bounds + 1 <= ONE_VAR_CONSTANT <= 1 + 0 <= variables(demand_share_per_timestep_decision)(a__test_conversion_plus) <= +inf + 0 <= variables(flow_out)(a__test_conversion_plus__electricity__2005_01_01_00_00) <= +inf + 0 <= variables(flow_out)(a__test_conversion_plus__electricity__2005_01_01_01_00) <= +inf + 0 <= variables(demand_share_per_timestep_decision)(a__test_supply_elec) <= +inf + 0 <= variables(flow_out)(a__test_supply_elec__electricity__2005_01_01_00_00) <= +inf + 0 <= variables(flow_out)(a__test_supply_elec__electricity__2005_01_01_01_00) <= +inf +end diff --git a/tests/common/lp_files/demand_share_per_timestep_decision_sum.lp b/tests/common/lp_files/demand_share_per_timestep_decision_sum.lp new file mode 100644 index 000000000..9e7a60958 --- /dev/null +++ b/tests/common/lp_files/demand_share_per_timestep_decision_sum.lp @@ -0,0 +1,23 @@ +\* Source Pyomo model name=None *\ + +min +objectives(dummy_obj)(0): ++2.0 ONE_VAR_CONSTANT + +s.t. + +c_e_constraints(demand_share_per_timestep_decision_sum)(a__2005_01_01_00_00)_: ++1 variables(demand_share_per_timestep_decision)(a__test_conversion_plus) ++1 variables(demand_share_per_timestep_decision)(a__test_supply_elec) += 0.5 + +c_e_constraints(demand_share_per_timestep_decision_sum)(a__2005_01_01_01_00)_: ++1 variables(demand_share_per_timestep_decision)(a__test_conversion_plus) ++1 variables(demand_share_per_timestep_decision)(a__test_supply_elec) += 0.5 + +bounds + 1 <= ONE_VAR_CONSTANT <= 1 + 0 <= variables(demand_share_per_timestep_decision)(a__test_conversion_plus) <= +inf + 0 <= variables(demand_share_per_timestep_decision)(a__test_supply_elec) <= +inf +end diff --git a/tests/common/lp_files/demand_share_per_timestep_equals_per_tech.lp b/tests/common/lp_files/demand_share_per_timestep_equals_per_tech.lp new file mode 100644 index 000000000..b9daeccec --- /dev/null +++ b/tests/common/lp_files/demand_share_per_timestep_equals_per_tech.lp @@ -0,0 +1,39 @@ +\* Source Pyomo model name=None *\ + +min +objectives(dummy_obj)(0): ++2.0 ONE_VAR_CONSTANT + +s.t. + +c_e_constraints(demand_share_per_timestep_equals_per_tech)(a__test_supply_elec__2005_01_01_00_00)_: ++1 variables(flow_out)(a__test_supply_elec__electricity__2005_01_01_00_00) +-0.5 variables(flow_in)(a__test_demand_elec__electricity__2005_01_01_00_00) += 0.0 + +c_e_constraints(demand_share_per_timestep_equals_per_tech)(a__test_supply_elec__2005_01_01_01_00)_: ++1 variables(flow_out)(a__test_supply_elec__electricity__2005_01_01_01_00) +-0.5 variables(flow_in)(a__test_demand_elec__electricity__2005_01_01_01_00) += 0.0 + +c_e_constraints(demand_share_per_timestep_equals_per_tech)(b__test_supply_elec__2005_01_01_00_00)_: ++1 variables(flow_out)(b__test_supply_elec__electricity__2005_01_01_00_00) +-0.8 variables(flow_in)(b__test_demand_elec__electricity__2005_01_01_00_00) += 0.0 + +c_e_constraints(demand_share_per_timestep_equals_per_tech)(b__test_supply_elec__2005_01_01_01_00)_: ++1 variables(flow_out)(b__test_supply_elec__electricity__2005_01_01_01_00) +-0.8 variables(flow_in)(b__test_demand_elec__electricity__2005_01_01_01_00) += 0.0 + +bounds + 1 <= ONE_VAR_CONSTANT <= 1 + 0 <= variables(flow_out)(a__test_supply_elec__electricity__2005_01_01_00_00) <= +inf + 0 <= variables(flow_in)(a__test_demand_elec__electricity__2005_01_01_00_00) <= +inf + 0 <= variables(flow_out)(a__test_supply_elec__electricity__2005_01_01_01_00) <= +inf + 0 <= variables(flow_in)(a__test_demand_elec__electricity__2005_01_01_01_00) <= +inf + 0 <= variables(flow_out)(b__test_supply_elec__electricity__2005_01_01_00_00) <= +inf + 0 <= variables(flow_in)(b__test_demand_elec__electricity__2005_01_01_00_00) <= +inf + 0 <= variables(flow_out)(b__test_supply_elec__electricity__2005_01_01_01_00) <= +inf + 0 <= variables(flow_in)(b__test_demand_elec__electricity__2005_01_01_01_00) <= +inf +end diff --git a/tests/common/lp_files/downtime_period.lp b/tests/common/lp_files/downtime_period.lp new file mode 100644 index 000000000..4155f3295 --- /dev/null +++ b/tests/common/lp_files/downtime_period.lp @@ -0,0 +1,16 @@ +\* Source Pyomo model name=None *\ + +min +objectives(dummy_obj)(0): ++2.0 ONE_VAR_CONSTANT + +s.t. + +c_e_constraints(downtime_period)(a__test_supply_elec__2005_01_01_00_00)_: ++1 variables(flow_out)(a__test_supply_elec__electricity__2005_01_01_00_00) += 0.0 + +bounds + 1 <= ONE_VAR_CONSTANT <= 1 + 0 <= variables(flow_out)(a__test_supply_elec__electricity__2005_01_01_00_00) <= +inf +end diff --git a/tests/common/lp_files/downtime_period_decision.lp b/tests/common/lp_files/downtime_period_decision.lp new file mode 100644 index 000000000..51e710e7a --- /dev/null +++ b/tests/common/lp_files/downtime_period_decision.lp @@ -0,0 +1,21 @@ +\* Source Pyomo model name=None *\ + +min +objectives(dummy_obj)(0): ++2.0 ONE_VAR_CONSTANT + +s.t. + +c_u_constraints(downtime_period_decision)(a__test_supply_elec)_: ++1.0 variables(operating_units)(a__test_supply_elec__2005_01_01_00_00) ++1.0 variables(operating_units)(a__test_supply_elec__2005_01_01_01_00) +<= 1.0 + +bounds + 1 <= ONE_VAR_CONSTANT <= 1 + 0 <= variables(operating_units)(a__test_supply_elec__2005_01_01_00_00) <= +inf + 0 <= variables(operating_units)(a__test_supply_elec__2005_01_01_01_00) <= +inf +general + variables(operating_units)(a__test_supply_elec__2005_01_01_00_00) + variables(operating_units)(a__test_supply_elec__2005_01_01_01_00) +end diff --git a/tests/common/lp_files/fuel_dist_base.lp b/tests/common/lp_files/fuel_dist_base.lp new file mode 100644 index 000000000..a52d8062b --- /dev/null +++ b/tests/common/lp_files/fuel_dist_base.lp @@ -0,0 +1,121 @@ +\* Source Pyomo model name=None *\ + +min +objectives(dummy_obj)(0): ++2.0 ONE_VAR_CONSTANT + +s.t. + +c_e_constraints(system_balance)(a__coal__2005_01_01_00_00)_: ++1 variables(flow_out)(a__test_supply_coal__coal__2005_01_01_00_00) +-1 variables(flow_in)(a__test_conversion_plus__coal__2005_01_01_00_00) +-1 variables(flow_in)(a__test_demand_coal__coal__2005_01_01_00_00) ++1 variables(fuel_distributor)(a__coal__2005_01_01_00_00) += 0.0 + +c_e_constraints(system_balance)(a__coal__2005_01_01_01_00)_: ++1 variables(flow_out)(a__test_supply_coal__coal__2005_01_01_01_00) +-1 variables(flow_in)(a__test_conversion_plus__coal__2005_01_01_01_00) +-1 variables(flow_in)(a__test_demand_coal__coal__2005_01_01_01_00) ++1 variables(fuel_distributor)(a__coal__2005_01_01_01_00) += 0.0 + +c_e_constraints(system_balance)(b__coal__2005_01_01_00_00)_: ++1 variables(flow_out)(b__test_supply_coal__coal__2005_01_01_00_00) +-1 variables(flow_in)(b__test_conversion_plus__coal__2005_01_01_00_00) +-1 variables(flow_in)(b__test_demand_coal__coal__2005_01_01_00_00) ++1 variables(fuel_distributor)(b__coal__2005_01_01_00_00) += 0.0 + +c_e_constraints(system_balance)(b__coal__2005_01_01_01_00)_: ++1 variables(flow_out)(b__test_supply_coal__coal__2005_01_01_01_00) +-1 variables(flow_in)(b__test_conversion_plus__coal__2005_01_01_01_00) +-1 variables(flow_in)(b__test_demand_coal__coal__2005_01_01_01_00) ++1 variables(fuel_distributor)(b__coal__2005_01_01_01_00) += 0.0 + +c_e_constraints(system_balance)(a__electricity__2005_01_01_00_00)_: ++1 variables(flow_out)(a__test_conversion_plus__electricity__2005_01_01_00_00) +-1 variables(flow_in)(a__test_demand_elec__electricity__2005_01_01_00_00) += 0.0 + +c_e_constraints(system_balance)(a__electricity__2005_01_01_01_00)_: ++1 variables(flow_out)(a__test_conversion_plus__electricity__2005_01_01_01_00) +-1 variables(flow_in)(a__test_demand_elec__electricity__2005_01_01_01_00) += 0.0 + +c_e_constraints(system_balance)(a__gas__2005_01_01_00_00)_: ++1 variables(flow_out)(a__test_supply_gas__gas__2005_01_01_00_00) +-1 variables(flow_in)(a__test_conversion_plus__gas__2005_01_01_00_00) += 0.0 + +c_e_constraints(system_balance)(a__gas__2005_01_01_01_00)_: ++1 variables(flow_out)(a__test_supply_gas__gas__2005_01_01_01_00) +-1 variables(flow_in)(a__test_conversion_plus__gas__2005_01_01_01_00) += 0.0 + +c_e_constraints(system_balance)(b__electricity__2005_01_01_00_00)_: ++1 variables(flow_out)(b__test_conversion_plus__electricity__2005_01_01_00_00) +-1 variables(flow_in)(b__test_demand_elec__electricity__2005_01_01_00_00) += 0.0 + +c_e_constraints(system_balance)(b__electricity__2005_01_01_01_00)_: ++1 variables(flow_out)(b__test_conversion_plus__electricity__2005_01_01_01_00) +-1 variables(flow_in)(b__test_demand_elec__electricity__2005_01_01_01_00) += 0.0 + +c_e_constraints(system_balance)(b__gas__2005_01_01_00_00)_: ++1 variables(flow_out)(b__test_supply_gas__gas__2005_01_01_00_00) +-1 variables(flow_in)(b__test_conversion_plus__gas__2005_01_01_00_00) += 0.0 + +c_e_constraints(system_balance)(b__gas__2005_01_01_01_00)_: ++1 variables(flow_out)(b__test_supply_gas__gas__2005_01_01_01_00) +-1 variables(flow_in)(b__test_conversion_plus__gas__2005_01_01_01_00) += 0.0 + +c_e_constraints(restrict_total_imports_and_exports)(coal__2005_01_01_00_00)_: ++1 variables(fuel_distributor)(a__coal__2005_01_01_00_00) ++1 variables(fuel_distributor)(b__coal__2005_01_01_00_00) += 0.0 + +c_e_constraints(restrict_total_imports_and_exports)(coal__2005_01_01_01_00)_: ++1 variables(fuel_distributor)(a__coal__2005_01_01_01_00) ++1 variables(fuel_distributor)(b__coal__2005_01_01_01_00) += 0.0 + +bounds + 1 <= ONE_VAR_CONSTANT <= 1 + 0 <= variables(flow_out)(a__test_supply_coal__coal__2005_01_01_00_00) <= +inf + 0 <= variables(flow_in)(a__test_conversion_plus__coal__2005_01_01_00_00) <= +inf + 0 <= variables(flow_in)(a__test_demand_coal__coal__2005_01_01_00_00) <= +inf + -inf <= variables(fuel_distributor)(a__coal__2005_01_01_00_00) <= +inf + 0 <= variables(flow_out)(a__test_supply_coal__coal__2005_01_01_01_00) <= +inf + 0 <= variables(flow_in)(a__test_conversion_plus__coal__2005_01_01_01_00) <= +inf + 0 <= variables(flow_in)(a__test_demand_coal__coal__2005_01_01_01_00) <= +inf + -inf <= variables(fuel_distributor)(a__coal__2005_01_01_01_00) <= +inf + 0 <= variables(flow_out)(b__test_supply_coal__coal__2005_01_01_00_00) <= +inf + 0 <= variables(flow_in)(b__test_conversion_plus__coal__2005_01_01_00_00) <= +inf + 0 <= variables(flow_in)(b__test_demand_coal__coal__2005_01_01_00_00) <= +inf + -inf <= variables(fuel_distributor)(b__coal__2005_01_01_00_00) <= +inf + 0 <= variables(flow_out)(b__test_supply_coal__coal__2005_01_01_01_00) <= +inf + 0 <= variables(flow_in)(b__test_conversion_plus__coal__2005_01_01_01_00) <= +inf + 0 <= variables(flow_in)(b__test_demand_coal__coal__2005_01_01_01_00) <= +inf + -inf <= variables(fuel_distributor)(b__coal__2005_01_01_01_00) <= +inf + 0 <= variables(flow_out)(a__test_conversion_plus__electricity__2005_01_01_00_00) <= +inf + 0 <= variables(flow_in)(a__test_demand_elec__electricity__2005_01_01_00_00) <= +inf + 0 <= variables(flow_out)(a__test_conversion_plus__electricity__2005_01_01_01_00) <= +inf + 0 <= variables(flow_in)(a__test_demand_elec__electricity__2005_01_01_01_00) <= +inf + 0 <= variables(flow_out)(a__test_supply_gas__gas__2005_01_01_00_00) <= +inf + 0 <= variables(flow_in)(a__test_conversion_plus__gas__2005_01_01_00_00) <= +inf + 0 <= variables(flow_out)(a__test_supply_gas__gas__2005_01_01_01_00) <= +inf + 0 <= variables(flow_in)(a__test_conversion_plus__gas__2005_01_01_01_00) <= +inf + 0 <= variables(flow_out)(b__test_conversion_plus__electricity__2005_01_01_00_00) <= +inf + 0 <= variables(flow_in)(b__test_demand_elec__electricity__2005_01_01_00_00) <= +inf + 0 <= variables(flow_out)(b__test_conversion_plus__electricity__2005_01_01_01_00) <= +inf + 0 <= variables(flow_in)(b__test_demand_elec__electricity__2005_01_01_01_00) <= +inf + 0 <= variables(flow_out)(b__test_supply_gas__gas__2005_01_01_00_00) <= +inf + 0 <= variables(flow_in)(b__test_conversion_plus__gas__2005_01_01_00_00) <= +inf + 0 <= variables(flow_out)(b__test_supply_gas__gas__2005_01_01_01_00) <= +inf + 0 <= variables(flow_in)(b__test_conversion_plus__gas__2005_01_01_01_00) <= +inf +end diff --git a/tests/common/lp_files/fuel_dist_cost.lp b/tests/common/lp_files/fuel_dist_cost.lp new file mode 100644 index 000000000..5ef3d35f7 --- /dev/null +++ b/tests/common/lp_files/fuel_dist_cost.lp @@ -0,0 +1,34 @@ +\* Source Pyomo model name=None *\ + +min +objectives(minmax_cost_optimisation)(0): ++0.0002515252789726502 variables(flow_cap)(a__test_conversion_plus) ++0.0002515252789726502 variables(flow_cap)(a__test_supply_coal) ++0.0002515252789726502 variables(flow_cap)(a__test_supply_gas) ++0.0002515252789726502 variables(flow_cap)(b__test_conversion_plus) ++0.0002515252789726502 variables(flow_cap)(b__test_supply_coal) ++0.0002515252789726502 variables(flow_cap)(b__test_supply_gas) ++5.0 variables(fuel_distributor)(a__coal__2005_01_01_00_00) ++5.0 variables(fuel_distributor)(a__coal__2005_01_01_01_00) ++5.0 variables(fuel_distributor)(b__coal__2005_01_01_00_00) ++5.0 variables(fuel_distributor)(b__coal__2005_01_01_01_00) + +s.t. + +c_e_ONE_VAR_CONSTANT: ++1 ONE_VAR_CONSTANT += 1 + +bounds + 1 <= ONE_VAR_CONSTANT <= 1 + 0 <= variables(flow_cap)(a__test_conversion_plus) <= 20.0 + 0 <= variables(flow_cap)(a__test_supply_coal) <= 10.0 + 0 <= variables(flow_cap)(a__test_supply_gas) <= 15.0 + 0 <= variables(flow_cap)(b__test_conversion_plus) <= 20.0 + 0 <= variables(flow_cap)(b__test_supply_coal) <= 10.0 + 0 <= variables(flow_cap)(b__test_supply_gas) <= 15.0 + -inf <= variables(fuel_distributor)(a__coal__2005_01_01_00_00) <= +inf + -inf <= variables(fuel_distributor)(a__coal__2005_01_01_01_00) <= +inf + -inf <= variables(fuel_distributor)(b__coal__2005_01_01_00_00) <= +inf + -inf <= variables(fuel_distributor)(b__coal__2005_01_01_01_00) <= +inf +end diff --git a/tests/common/lp_files/fuel_dist_nodal.lp b/tests/common/lp_files/fuel_dist_nodal.lp new file mode 100644 index 000000000..01ea7b29d --- /dev/null +++ b/tests/common/lp_files/fuel_dist_nodal.lp @@ -0,0 +1,31 @@ +\* Source Pyomo model name=None *\ + +min +objectives(dummy_obj)(0): ++2.0 ONE_VAR_CONSTANT + +s.t. + +c_u_constraints(restrict_nodal_imports)(b__coal__2005_01_01_00_00)_: ++1 variables(fuel_distributor)(b__coal__2005_01_01_00_00) +<= 5.0 + +c_u_constraints(restrict_nodal_imports)(b__coal__2005_01_01_01_00)_: ++1 variables(fuel_distributor)(b__coal__2005_01_01_01_00) +<= 5.0 + +c_u_constraints(restrict_nodal_exports)(a__coal__2005_01_01_00_00)_: +-1.0 variables(fuel_distributor)(a__coal__2005_01_01_00_00) +<= 3.0 + +c_u_constraints(restrict_nodal_exports)(a__coal__2005_01_01_01_00)_: +-1.0 variables(fuel_distributor)(a__coal__2005_01_01_01_00) +<= 3.0 + +bounds + 1 <= ONE_VAR_CONSTANT <= 1 + -inf <= variables(fuel_distributor)(b__coal__2005_01_01_00_00) <= +inf + -inf <= variables(fuel_distributor)(b__coal__2005_01_01_01_00) <= +inf + -inf <= variables(fuel_distributor)(a__coal__2005_01_01_00_00) <= +inf + -inf <= variables(fuel_distributor)(a__coal__2005_01_01_01_00) <= +inf +end diff --git a/tests/common/lp_files/max_time_varying_flow_cap.lp b/tests/common/lp_files/max_time_varying_flow_cap.lp new file mode 100644 index 000000000..5b56f0bfd --- /dev/null +++ b/tests/common/lp_files/max_time_varying_flow_cap.lp @@ -0,0 +1,37 @@ +\* Source Pyomo model name=None *\ + +min +objectives(dummy_obj)(0): ++2.0 ONE_VAR_CONSTANT + +s.t. + +c_u_constraints(max_time_varying_flow_cap)(a__test_supply_elec__2005_01_01_00_00)_: ++1 variables(flow_out)(a__test_supply_elec__electricity__2005_01_01_00_00) +-0.8 variables(flow_cap)(a__test_supply_elec) +<= 0.0 + +c_u_constraints(max_time_varying_flow_cap)(a__test_supply_elec__2005_01_01_01_00)_: +-0.5 variables(flow_cap)(a__test_supply_elec) ++1 variables(flow_out)(a__test_supply_elec__electricity__2005_01_01_01_00) +<= 0.0 + +c_u_constraints(max_time_varying_flow_cap)(b__test_supply_elec__2005_01_01_00_00)_: ++1 variables(flow_out)(b__test_supply_elec__electricity__2005_01_01_00_00) +-0.8 variables(flow_cap)(b__test_supply_elec) +<= 0.0 + +c_u_constraints(max_time_varying_flow_cap)(b__test_supply_elec__2005_01_01_01_00)_: +-0.5 variables(flow_cap)(b__test_supply_elec) ++1 variables(flow_out)(b__test_supply_elec__electricity__2005_01_01_01_00) +<= 0.0 + +bounds + 1 <= ONE_VAR_CONSTANT <= 1 + 0 <= variables(flow_out)(a__test_supply_elec__electricity__2005_01_01_00_00) <= +inf + 0 <= variables(flow_cap)(a__test_supply_elec) <= 10.0 + 0 <= variables(flow_out)(a__test_supply_elec__electricity__2005_01_01_01_00) <= +inf + 0 <= variables(flow_out)(b__test_supply_elec__electricity__2005_01_01_00_00) <= +inf + 0 <= variables(flow_cap)(b__test_supply_elec) <= 10.0 + 0 <= variables(flow_out)(b__test_supply_elec__electricity__2005_01_01_01_00) <= +inf +end diff --git a/tests/common/lp_files/max_time_varying_storage.lp b/tests/common/lp_files/max_time_varying_storage.lp new file mode 100644 index 000000000..8c7230467 --- /dev/null +++ b/tests/common/lp_files/max_time_varying_storage.lp @@ -0,0 +1,39 @@ +\* Source Pyomo model name=None *\ + +min +objectives(dummy_obj)(0): ++2.0 ONE_VAR_CONSTANT + +s.t. + +c_u_constraints(max_time_varying_storage)(a__test_storage__2005_01_01_00_00)_: ++1 variables(flow_out)(a__test_storage__electricity__2005_01_01_00_00) +-0.8 variables(storage)(a__test_storage__2005_01_01_00_00) +<= 0.0 + +c_u_constraints(max_time_varying_storage)(a__test_storage__2005_01_01_01_00)_: ++1 variables(flow_out)(a__test_storage__electricity__2005_01_01_01_00) +-0.5 variables(storage)(a__test_storage__2005_01_01_01_00) +<= 0.0 + +c_u_constraints(max_time_varying_storage)(b__test_storage__2005_01_01_00_00)_: ++1 variables(flow_out)(b__test_storage__electricity__2005_01_01_00_00) +-0.8 variables(storage)(b__test_storage__2005_01_01_00_00) +<= 0.0 + +c_u_constraints(max_time_varying_storage)(b__test_storage__2005_01_01_01_00)_: ++1 variables(flow_out)(b__test_storage__electricity__2005_01_01_01_00) +-0.5 variables(storage)(b__test_storage__2005_01_01_01_00) +<= 0.0 + +bounds + 1 <= ONE_VAR_CONSTANT <= 1 + 0 <= variables(flow_out)(a__test_storage__electricity__2005_01_01_00_00) <= +inf + 0 <= variables(storage)(a__test_storage__2005_01_01_00_00) <= +inf + 0 <= variables(flow_out)(a__test_storage__electricity__2005_01_01_01_00) <= +inf + 0 <= variables(storage)(a__test_storage__2005_01_01_01_00) <= +inf + 0 <= variables(flow_out)(b__test_storage__electricity__2005_01_01_00_00) <= +inf + 0 <= variables(storage)(b__test_storage__2005_01_01_00_00) <= +inf + 0 <= variables(flow_out)(b__test_storage__electricity__2005_01_01_01_00) <= +inf + 0 <= variables(storage)(b__test_storage__2005_01_01_01_00) <= +inf +end diff --git a/tests/common/lp_files/piecewise_cost_investment.lp b/tests/common/lp_files/piecewise_cost_investment.lp new file mode 100644 index 000000000..9e3996f2d --- /dev/null +++ b/tests/common/lp_files/piecewise_cost_investment.lp @@ -0,0 +1,33 @@ +\* Source Pyomo model name=None *\ + +min +objectives(dummy_obj)(0): ++2.0 ONE_VAR_CONSTANT + +s.t. + +c_u_constraints(piecewise_costs)(a__test_supply_elec__monetary__0)_: ++5 variables(flow_cap)(a__test_supply_elec) +-1 variables(piecewise_cost_investment)(a__test_supply_elec__monetary) +<= 0.0 + +c_u_constraints(piecewise_costs)(a__test_supply_elec__monetary__1)_: ++7 variables(flow_cap)(a__test_supply_elec) +-1 variables(piecewise_cost_investment)(a__test_supply_elec__monetary) +-2 variables(purchased)(a__test_supply_elec) +<= 0.0 + +c_u_constraints(piecewise_costs)(a__test_supply_elec__monetary__2)_: ++14 variables(flow_cap)(a__test_supply_elec) +-1 variables(piecewise_cost_investment)(a__test_supply_elec__monetary) +-16 variables(purchased)(a__test_supply_elec) +<= 0.0 + +bounds + 1 <= ONE_VAR_CONSTANT <= 1 + 0 <= variables(flow_cap)(a__test_supply_elec) <= 10.0 + 0 <= variables(piecewise_cost_investment)(a__test_supply_elec__monetary) <= +inf + 0 <= variables(purchased)(a__test_supply_elec) <= 1 +binary + variables(purchased)(a__test_supply_elec) +end diff --git a/tests/common/lp_files/piecewise_efficiency.lp b/tests/common/lp_files/piecewise_efficiency.lp new file mode 100644 index 000000000..309201425 --- /dev/null +++ b/tests/common/lp_files/piecewise_efficiency.lp @@ -0,0 +1,89 @@ +\* Source Pyomo model name=None *\ + +min +objectives(dummy_obj)(0): ++2.0 ONE_VAR_CONSTANT + +s.t. + +c_u_constraints(piecewise_efficiency)(a__test_conversion__2005_01_01_00_00__0)_: ++5 variables(flow_out)(a__test_conversion__heat__2005_01_01_00_00) +-1 variables(flow_in)(a__test_conversion__gas__2005_01_01_00_00) +<= 0.0 + +c_u_constraints(piecewise_efficiency)(a__test_conversion__2005_01_01_00_00__1)_: ++7 variables(flow_out)(a__test_conversion__heat__2005_01_01_00_00) +-1 variables(flow_in)(a__test_conversion__gas__2005_01_01_00_00) +-2 variables(available_flow_cap)(a__test_conversion__2005_01_01_00_00) +<= 0.0 + +c_u_constraints(piecewise_efficiency)(a__test_conversion__2005_01_01_00_00__2)_: ++14 variables(flow_out)(a__test_conversion__heat__2005_01_01_00_00) +-1 variables(flow_in)(a__test_conversion__gas__2005_01_01_00_00) +-16 variables(available_flow_cap)(a__test_conversion__2005_01_01_00_00) +<= 0.0 + +c_u_constraints(piecewise_efficiency)(a__test_conversion__2005_01_01_01_00__0)_: ++5 variables(flow_out)(a__test_conversion__heat__2005_01_01_01_00) +-1 variables(flow_in)(a__test_conversion__gas__2005_01_01_01_00) +<= 0.0 + +c_u_constraints(piecewise_efficiency)(a__test_conversion__2005_01_01_01_00__1)_: ++7 variables(flow_out)(a__test_conversion__heat__2005_01_01_01_00) +-1 variables(flow_in)(a__test_conversion__gas__2005_01_01_01_00) +-2 variables(available_flow_cap)(a__test_conversion__2005_01_01_01_00) +<= 0.0 + +c_u_constraints(piecewise_efficiency)(a__test_conversion__2005_01_01_01_00__2)_: ++14 variables(flow_out)(a__test_conversion__heat__2005_01_01_01_00) +-1 variables(flow_in)(a__test_conversion__gas__2005_01_01_01_00) +-16 variables(available_flow_cap)(a__test_conversion__2005_01_01_01_00) +<= 0.0 + +c_u_constraints(available_flow_cap_binary)(a__test_conversion__2005_01_01_00_00)_: ++1 variables(available_flow_cap)(a__test_conversion__2005_01_01_00_00) +-15.0 variables(operating_units)(a__test_conversion__2005_01_01_00_00) +<= 0.0 + +c_u_constraints(available_flow_cap_binary)(a__test_conversion__2005_01_01_01_00)_: ++1 variables(available_flow_cap)(a__test_conversion__2005_01_01_01_00) +-15.0 variables(operating_units)(a__test_conversion__2005_01_01_01_00) +<= 0.0 + +c_u_constraints(available_flow_cap_continuous)(a__test_conversion__2005_01_01_00_00)_: ++1 variables(available_flow_cap)(a__test_conversion__2005_01_01_00_00) +-1 variables(flow_cap)(a__test_conversion) +<= 0.0 + +c_u_constraints(available_flow_cap_continuous)(a__test_conversion__2005_01_01_01_00)_: ++1 variables(available_flow_cap)(a__test_conversion__2005_01_01_01_00) +-1 variables(flow_cap)(a__test_conversion) +<= 0.0 + +c_u_constraints(available_flow_cap_binary_continuous_switch)(a__test_conversion__2005_01_01_00_00)_: +-1 variables(available_flow_cap)(a__test_conversion__2005_01_01_00_00) ++15.0 variables(operating_units)(a__test_conversion__2005_01_01_00_00) ++1 variables(flow_cap)(a__test_conversion) +<= 15.0 + +c_u_constraints(available_flow_cap_binary_continuous_switch)(a__test_conversion__2005_01_01_01_00)_: +-1 variables(available_flow_cap)(a__test_conversion__2005_01_01_01_00) ++15.0 variables(operating_units)(a__test_conversion__2005_01_01_01_00) ++1 variables(flow_cap)(a__test_conversion) +<= 15.0 + +bounds + 1 <= ONE_VAR_CONSTANT <= 1 + 0 <= variables(flow_out)(a__test_conversion__heat__2005_01_01_00_00) <= +inf + 0 <= variables(flow_in)(a__test_conversion__gas__2005_01_01_00_00) <= +inf + 0 <= variables(available_flow_cap)(a__test_conversion__2005_01_01_00_00) <= 15.0 + 0 <= variables(flow_out)(a__test_conversion__heat__2005_01_01_01_00) <= +inf + 0 <= variables(flow_in)(a__test_conversion__gas__2005_01_01_01_00) <= +inf + 0 <= variables(available_flow_cap)(a__test_conversion__2005_01_01_01_00) <= 15.0 + 0 <= variables(operating_units)(a__test_conversion__2005_01_01_00_00) <= +inf + 0 <= variables(operating_units)(a__test_conversion__2005_01_01_01_00) <= +inf + 0 <= variables(flow_cap)(a__test_conversion) <= 15.0 +general + variables(operating_units)(a__test_conversion__2005_01_01_00_00) + variables(operating_units)(a__test_conversion__2005_01_01_01_00) +end diff --git a/tests/common/lp_files/supply_share_equals_per_tech.lp b/tests/common/lp_files/supply_share_equals_per_tech.lp new file mode 100644 index 000000000..eb6faa341 --- /dev/null +++ b/tests/common/lp_files/supply_share_equals_per_tech.lp @@ -0,0 +1,41 @@ +\* Source Pyomo model name=None *\ + +min +objectives(dummy_obj)(0): ++2.0 ONE_VAR_CONSTANT + +s.t. + +c_e_constraints(supply_share_equals_per_tech)(a__test_supply_elec)_: ++0.5 variables(flow_out)(a__test_supply_elec__electricity__2005_01_01_00_00) ++0.5 variables(flow_out)(a__test_supply_elec__electricity__2005_01_01_01_00) +-0.5 variables(flow_out)(a__test_supply_plus__electricity__2005_01_01_00_00) +-0.5 variables(flow_out)(a__test_supply_plus__electricity__2005_01_01_01_00) +-0.5 variables(flow_out)(a__test_transmission_elec_b__electricity__2005_01_01_00_00) +-0.5 variables(flow_out)(a__test_transmission_elec_b__electricity__2005_01_01_01_00) += 0.0 + +c_e_constraints(supply_share_equals_per_tech)(b__test_supply_elec)_: ++0.19999999999999996 variables(flow_out)(b__test_supply_elec__electricity__2005_01_01_00_00) ++0.19999999999999996 variables(flow_out)(b__test_supply_elec__electricity__2005_01_01_01_00) +-0.8 variables(flow_out)(b__test_supply_plus__electricity__2005_01_01_00_00) +-0.8 variables(flow_out)(b__test_supply_plus__electricity__2005_01_01_01_00) +-0.8 variables(flow_out)(b__test_transmission_elec_a__electricity__2005_01_01_00_00) +-0.8 variables(flow_out)(b__test_transmission_elec_a__electricity__2005_01_01_01_00) += 0.0 + +bounds + 1 <= ONE_VAR_CONSTANT <= 1 + 0 <= variables(flow_out)(a__test_supply_elec__electricity__2005_01_01_00_00) <= +inf + 0 <= variables(flow_out)(a__test_supply_elec__electricity__2005_01_01_01_00) <= +inf + 0 <= variables(flow_out)(a__test_supply_plus__electricity__2005_01_01_00_00) <= +inf + 0 <= variables(flow_out)(a__test_supply_plus__electricity__2005_01_01_01_00) <= +inf + 0 <= variables(flow_out)(a__test_transmission_elec_b__electricity__2005_01_01_00_00) <= +inf + 0 <= variables(flow_out)(a__test_transmission_elec_b__electricity__2005_01_01_01_00) <= +inf + 0 <= variables(flow_out)(b__test_supply_elec__electricity__2005_01_01_00_00) <= +inf + 0 <= variables(flow_out)(b__test_supply_elec__electricity__2005_01_01_01_00) <= +inf + 0 <= variables(flow_out)(b__test_supply_plus__electricity__2005_01_01_00_00) <= +inf + 0 <= variables(flow_out)(b__test_supply_plus__electricity__2005_01_01_01_00) <= +inf + 0 <= variables(flow_out)(b__test_transmission_elec_a__electricity__2005_01_01_00_00) <= +inf + 0 <= variables(flow_out)(b__test_transmission_elec_a__electricity__2005_01_01_01_00) <= +inf +end diff --git a/tests/common/lp_files/supply_share_per_timestep_equals_per_tech.lp b/tests/common/lp_files/supply_share_per_timestep_equals_per_tech.lp new file mode 100644 index 000000000..14836fcea --- /dev/null +++ b/tests/common/lp_files/supply_share_per_timestep_equals_per_tech.lp @@ -0,0 +1,47 @@ +\* Source Pyomo model name=None *\ + +min +objectives(dummy_obj)(0): ++2.0 ONE_VAR_CONSTANT + +s.t. + +c_e_constraints(supply_share_per_timestep_equals_per_tech)(a__test_supply_elec__2005_01_01_00_00)_: ++0.5 variables(flow_out)(a__test_supply_elec__electricity__2005_01_01_00_00) +-0.5 variables(flow_out)(a__test_supply_plus__electricity__2005_01_01_00_00) +-0.5 variables(flow_out)(a__test_transmission_elec_b__electricity__2005_01_01_00_00) += 0.0 + +c_e_constraints(supply_share_per_timestep_equals_per_tech)(a__test_supply_elec__2005_01_01_01_00)_: ++0.5 variables(flow_out)(a__test_supply_elec__electricity__2005_01_01_01_00) +-0.5 variables(flow_out)(a__test_supply_plus__electricity__2005_01_01_01_00) +-0.5 variables(flow_out)(a__test_transmission_elec_b__electricity__2005_01_01_01_00) += 0.0 + +c_e_constraints(supply_share_per_timestep_equals_per_tech)(b__test_supply_elec__2005_01_01_00_00)_: ++0.19999999999999996 variables(flow_out)(b__test_supply_elec__electricity__2005_01_01_00_00) +-0.8 variables(flow_out)(b__test_supply_plus__electricity__2005_01_01_00_00) +-0.8 variables(flow_out)(b__test_transmission_elec_a__electricity__2005_01_01_00_00) += 0.0 + +c_e_constraints(supply_share_per_timestep_equals_per_tech)(b__test_supply_elec__2005_01_01_01_00)_: ++0.19999999999999996 variables(flow_out)(b__test_supply_elec__electricity__2005_01_01_01_00) +-0.8 variables(flow_out)(b__test_supply_plus__electricity__2005_01_01_01_00) +-0.8 variables(flow_out)(b__test_transmission_elec_a__electricity__2005_01_01_01_00) += 0.0 + +bounds + 1 <= ONE_VAR_CONSTANT <= 1 + 0 <= variables(flow_out)(a__test_supply_elec__electricity__2005_01_01_00_00) <= +inf + 0 <= variables(flow_out)(a__test_supply_plus__electricity__2005_01_01_00_00) <= +inf + 0 <= variables(flow_out)(a__test_transmission_elec_b__electricity__2005_01_01_00_00) <= +inf + 0 <= variables(flow_out)(a__test_supply_elec__electricity__2005_01_01_01_00) <= +inf + 0 <= variables(flow_out)(a__test_supply_plus__electricity__2005_01_01_01_00) <= +inf + 0 <= variables(flow_out)(a__test_transmission_elec_b__electricity__2005_01_01_01_00) <= +inf + 0 <= variables(flow_out)(b__test_supply_elec__electricity__2005_01_01_00_00) <= +inf + 0 <= variables(flow_out)(b__test_supply_plus__electricity__2005_01_01_00_00) <= +inf + 0 <= variables(flow_out)(b__test_transmission_elec_a__electricity__2005_01_01_00_00) <= +inf + 0 <= variables(flow_out)(b__test_supply_elec__electricity__2005_01_01_01_00) <= +inf + 0 <= variables(flow_out)(b__test_supply_plus__electricity__2005_01_01_01_00) <= +inf + 0 <= variables(flow_out)(b__test_transmission_elec_a__electricity__2005_01_01_01_00) <= +inf +end diff --git a/tests/common/test_model/model.yaml b/tests/common/test_model/model.yaml index 2452e6188..da7ccdad9 100644 --- a/tests/common/test_model/model.yaml +++ b/tests/common/test_model/model.yaml @@ -102,6 +102,16 @@ techs: carrier_ratios: carrier_out_2: { heat: 0.8 } + test_chp: + essentials: + name: Conversion plus tech + primary_carrier_out: electricity + carrier_in: gas + carrier_out: [electricity, heat] + parent: conversion_plus + constraints: + flow_cap_max: 20 + test_demand_elec: essentials: name: Demand elec tech @@ -118,6 +128,14 @@ techs: constraints: sink_equals: file=demand_heat.csv + test_demand_coal: + essentials: + name: Demand coal tech + carrier: coal + parent: demand + constraints: + sink_max: 10 + test_transmission_elec: essentials: name: Transmission elec tech diff --git a/tests/common/test_model/scenarios.yaml b/tests/common/test_model/scenarios.yaml index a274cc684..9e70df290 100644 --- a/tests/common/test_model/scenarios.yaml +++ b/tests/common/test_model/scenarios.yaml @@ -87,14 +87,16 @@ overrides: links.a,b.active: false supply_milp: + techs: + test_supply_elec: + switches.cap_method: integer + constraints: + units_max: 1 + flow_cap_per_unit: 15 nodes: a: techs: test_supply_elec: - switches.cap_method: integer - constraints: - units_max: 1 - flow_cap_per_unit: 15 test_demand_elec: b.active: false links.a,b.active: false @@ -149,16 +151,36 @@ overrides: links.a,b.active: false conversion_plus_milp: + techs: + test_conversion_plus: + switches.cap_method: integer + constraints: + units_max: 2 + flow_cap_per_unit: 15 nodes: a: techs: test_supply_elec: test_supply_gas: test_conversion_plus: - switches.cap_method: integer - constraints: - units_max: 2 - flow_cap_per_unit: 15 + test_demand_elec: + test_demand_heat: + + links.a,b.active: false + + conversion_milp: + techs: + test_conversion: + switches.cap_method: integer + constraints: + units_max: 1 + flow_cap_per_unit: null + nodes: + a: + techs: + test_supply_elec: + test_supply_gas: + test_conversion: test_demand_elec: test_demand_heat: @@ -193,6 +215,37 @@ overrides: links.a,b.active: false + simple_chp: + nodes: + a: + techs: + test_supply_elec: + test_supply_gas: + test_chp: + test_demand_elec: + test_demand_heat: + + links.a,b.active: false + + fuel_distribution: + techs: + test_conversion_plus: + essentials: + carrier_in: [gas, coal] + carrier_out_2: null + primary_carrier_in: gas + + nodes: + a,b: + techs: + test_supply_gas: + test_supply_coal: + test_conversion_plus: + test_demand_elec: + test_demand_coal: + + links.a,b.active: false + simple_storage: nodes: a: @@ -319,3 +372,8 @@ overrides: monetary: interest_rate: 0.1 flow_cap: 10 + + demand_elec_max: + techs.test_demand_elec.constraints: + sink_equals: null + sink_max: file=demand_elec.csv diff --git a/tests/common/util.py b/tests/common/util.py index 51cc4bfdc..275080271 100644 --- a/tests/common/util.py +++ b/tests/common/util.py @@ -88,7 +88,7 @@ def check_variable_exists( def build_lp( model: calliope.Model, outfile: Union[str, Path], - math: Optional[dict] = None, + math: Optional[dict[Union[dict, list]]] = None, backend: Literal["pyomo"] = "pyomo", ) -> None: """ @@ -106,12 +106,15 @@ def build_lp( for name, dict_ in model.math["variables"].items(): backend_instance.add_variable(name, dict_) - if math is not None: + if isinstance(math, dict): for component_group, component_math in math.items(): - for name, dict_ in component_math.items(): - getattr(backend_instance, f"add_{component_group.removesuffix('s')}")( - name, dict_ - ) + component = component_group.removesuffix("s") + if isinstance(component_math, dict): + for name, dict_ in component_math.items(): + getattr(backend_instance, f"add_{component}")(name, dict_) + elif isinstance(component_math, list): + for name in component_math: + getattr(backend_instance, f"add_{component}")(name) # MUST have an objective for a valid LP file if math is None or "objectives" not in math.keys(): @@ -121,8 +124,12 @@ def build_lp( ) backend_instance._instance.objectives["dummy_obj"][0].activate() elif "objectives" in math.keys(): - objective = list(math["objectives"].keys())[0] - backend_instance._instance.objectives[objective][0].activate() + if isinstance(math["objectives"], dict): + objectives = list(math["objectives"].keys()) + else: + objectives = math["objectives"] + assert len(objectives) == 1, "Can only test with one objective" + backend_instance._instance.objectives[objectives[0]][0].activate() backend_instance.verbose_strings() @@ -137,3 +144,4 @@ def build_lp( # reintroduce the trailing newline since both Pyomo and file formatters love them. Path(outfile).write_text("\n".join(stripped_lines) + "\n") + return backend_instance diff --git a/tests/conftest.py b/tests/conftest.py index 9ddc2f4ac..0d2176441 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -188,10 +188,8 @@ def dummy_model_data(): ["carriers", "techs"], [[1.0, np.nan, 1.0, np.nan], [np.nan, 1.0, np.nan, np.nan]], ), - "lookup_techs": ( - ["techs"], - ["foobar", np.nan, "foobaz", np.nan], - ), + "lookup_techs": (["techs"], ["foobar", np.nan, "foobaz", np.nan]), + "lookup_techs_no_match": (["techs"], ["foo", np.nan, "bar", np.nan]), "link_remote_nodes": ( ["nodes", "techs"], [["bar", np.nan, "bar", np.nan], ["foo", np.nan, np.nan, np.nan]], diff --git a/tests/test_backend_helper_functions.py b/tests/test_backend_helper_functions.py index 44dabd1e3..7c1fbe3d5 100644 --- a/tests/test_backend_helper_functions.py +++ b/tests/test_backend_helper_functions.py @@ -285,6 +285,18 @@ def test_select_from_lookup_arrays_fail_dim_slicer_mismatch( ["lookup arrays used to select items from `with_inf", "'techs'", "'nodes'"], ) + def test_select_from_lookup_arrays_no_match( + self, expression_select_from_lookup_arrays, dummy_model_data + ): + with pytest.raises(IndexError) as excinfo: + expression_select_from_lookup_arrays( + dummy_model_data.with_inf, techs=dummy_model_data.lookup_techs_no_match + ) + assert check_error_or_warning( + excinfo, + "Trying to select items on the dimension techs from the lookup_techs_no_match lookup array, but no matches found.", + ) + @pytest.mark.parametrize(["idx", "expected"], [(0, "foo"), (1, "bar"), (-1, "bar")]) def test_get_val_at_index(self, expression_get_val_at_index, idx, expected): assert expression_get_val_at_index(timesteps=idx) == expected diff --git a/tests/test_example_models.py b/tests/test_example_models.py index 8fabed9cb..ccac2980f 100755 --- a/tests/test_example_models.py +++ b/tests/test_example_models.py @@ -521,10 +521,6 @@ def example_tester(self, source_unit, solver="cbc", solver_io=None): override_dict=unit_override, subset_time=["2005-07-01", "2005-07-01"] ) - solve_kwargs = {"solver": solver} - if solver_io: - solve_kwargs["solver_io"] = solver_io - solve_kwargs = {"solver": solver} if solver_io: solve_kwargs["solver_io"] = solver_io diff --git a/tests/test_math.py b/tests/test_math.py index 021de44a7..cb1855339 100644 --- a/tests/test_math.py +++ b/tests/test_math.py @@ -1,6 +1,8 @@ +import importlib +from abc import ABC, abstractmethod from pathlib import Path +from typing import Optional -import calliope import numpy as np import pytest from calliope import AttrDict @@ -8,30 +10,44 @@ from .common.util import build_lp, build_test_model +CALLIOPE_DIR: Path = importlib.resources.files("calliope") + @pytest.fixture(scope="class") def compare_lps(tmpdir_factory): def _compare_lps(model, custom_math, filename): lp_file = filename + ".lp" generated_file = Path(tmpdir_factory.mktemp("lp_files")) / lp_file - build_lp(model, generated_file, custom_math) + backend = build_lp(model, generated_file, custom_math) # noqa: F841 expected_file = Path(__file__).parent / "common" / "lp_files" / lp_file - diff = lp_diff.load_and_compare_lp_baseline( + # Pyomo diff ignores trivial numeric differences (10 == 10.0) + # But it does not ignore a re-ordering of components + diff_ordered = lp_diff.load_and_compare_lp_baseline( generated_file.as_posix(), expected_file.as_posix() ) - assert diff == ([], []) + # Our unordered comparison ignores component ordering but cannot handle + # trivial differences in numerics (as everything is a string to it) + diff_unordered = _diff_files(generated_file, expected_file) + + # If one of the above matches across the board, we're good to go. + assert diff_ordered == ([], []) or not diff_unordered return _compare_lps -@pytest.fixture(scope="class") -def base_math(): - return AttrDict.from_yaml(Path(calliope.__file__).parent / "math" / "base.yaml") +def _diff_files(file1, file2): + file1_lines = file1.read_text().split("\n") + file2_lines = file2.read_text().split("\n") + return set(file1_lines).symmetric_difference(file2_lines) class TestBaseMath: TEST_REGISTER: set = set() + @pytest.fixture(scope="class") + def base_math(self): + return AttrDict.from_yaml(CALLIOPE_DIR / "math" / "base.yaml") + def test_flow_cap(self, compare_lps): self.TEST_REGISTER.add("variables.flow_cap") model = build_test_model( @@ -115,3 +131,561 @@ def test_all_math_registered(self, base_math): for key in self.TEST_REGISTER: base_math.del_key(key) assert not base_math + + +class CustomMathExamples(ABC): + TEST_REGISTER: set = set() + + #: source of all custom math files + CUSTOM_MATH_DIR = CALLIOPE_DIR.parent.parent / "doc" / "_static" / "custom_math" + + @property + @abstractmethod + def YAML_FILEPATH(self) -> str: + "Source of the specific test class custom math" + + @pytest.fixture(scope="class") + def abs_filepath(self): + return (self.CUSTOM_MATH_DIR / self.YAML_FILEPATH).absolute() + + @pytest.fixture(scope="class") + def custom_math(self): + return AttrDict.from_yaml(self.CUSTOM_MATH_DIR / self.YAML_FILEPATH) + + @pytest.fixture + def build_and_compare(self, abs_filepath, compare_lps): + def _build_and_compare( + filename: str, + scenario: str, + overrides: Optional[dict] = None, + components: Optional[dict[list[str]]] = None, + ): + if components is not None: + for component_group, component_list in components.items(): + for component in component_list: + self.TEST_REGISTER.add(f"{component_group}.{component}") + + custom_math = {k: v for k, v in components.items() if k != "variables"} + else: + self.TEST_REGISTER.add(f"constraints.{filename}") + custom_math = {"constraints": [filename]} + + if overrides is None: + overrides = {} + + model = build_test_model( + {"config.init.custom_math": [abs_filepath], **overrides}, + scenario, + ) + + compare_lps(model, custom_math, filename) + + return _build_and_compare + + @pytest.mark.order(-1) + def test_all_math_registered(self, custom_math): + "After running all the previous tests in the class, the register should be full, i.e. all math has been tested" + for key in self.TEST_REGISTER: + if custom_math.get_key(key, default=None) is not None: + custom_math.del_key(key) + assert not custom_math + + +@pytest.mark.filterwarnings( + "ignore:(?s).*defines unrecognised constraint `annual_flow_max`:calliope.exceptions.ModelWarning" +) +class TestAnnualEnergyBalance(CustomMathExamples): + YAML_FILEPATH = "annual_energy_balance.yaml" + + def test_annual_energy_balance_per_tech_and_node(self, build_and_compare): + overrides = { + "nodes.a.techs.test_supply_elec.constraints.annual_flow_max": 10, + "nodes.b.techs.test_supply_elec.constraints.annual_flow_max": 20, + } + build_and_compare( + "annual_energy_balance_per_tech_and_node", + "simple_supply,two_hours", + overrides, + ) + + def test_annual_energy_balance_global_per_tech(self, build_and_compare): + overrides = { + "parameters": { + "annual_flow_max": { + "data": 10, + "index": ["test_supply_elec"], + "dims": "techs", + }, + } + } + build_and_compare( + "annual_energy_balance_global_per_tech", + "simple_supply,two_hours", + overrides, + ) + + def test_annual_energy_balance_global_multi_tech(self, build_and_compare): + overrides = { + "parameters": { + "annual_flow_max": {"data": 10}, + "flow_max_group": { + "data": True, + "index": ["test_supply_elec", "test_supply_plus"], + "dims": "techs", + }, + } + } + build_and_compare( + "annual_energy_balance_global_multi_tech", + "simple_supply_and_supply_plus,two_hours", + overrides, + ) + + def test_annual_energy_balance_total_source_availability(self, build_and_compare): + overrides = { + "parameters": { + "annual_source_max": { + "data": 10, + "index": ["test_supply_plus"], + "dims": "techs", + }, + } + } + build_and_compare( + "annual_energy_balance_total_source_availability", + "simple_supply_and_supply_plus,two_hours", + overrides, + ) + + def test_annual_energy_balance_total_sink_availability(self, build_and_compare): + overrides = { + "parameters": { + "annual_sink_max": { + "data": 10, + "index": ["test_demand_elec"], + "dims": "techs", + }, + }, + } + build_and_compare( + "annual_energy_balance_total_sink_availability", + "simple_supply,two_hours,demand_elec_max", + overrides, + ) + + +class TestMaxTimeVarying(CustomMathExamples): + YAML_FILEPATH = "max_time_varying.yaml" + + def test_max_time_varying_flow_cap(self, build_and_compare): + overrides = { + "parameters": { + "flow_cap_max_relative_per_ts": { + "data": [0.8, 0.5], + "index": [ + ["test_supply_elec", "2005-01-01 00:00"], + ["test_supply_elec", "2005-01-01 01:00"], + ], + "dims": ["techs", "timesteps"], + }, + }, + } + build_and_compare( + "max_time_varying_flow_cap", + "simple_supply,two_hours", + overrides, + ) + + def test_max_time_varying_storage(self, build_and_compare): + overrides = { + "parameters": { + "storage_max_relative_per_ts": { + "data": [0.8, 0.5], + "index": [ + ["test_storage", "2005-01-01 00:00"], + ["test_storage", "2005-01-01 01:00"], + ], + "dims": ["techs", "timesteps"], + }, + }, + } + build_and_compare( + "max_time_varying_storage", + "simple_storage,two_hours", + overrides, + ) + + +@pytest.mark.filterwarnings( + "ignore:(?s).*defines unrecognised constraint `turbine_type`:calliope.exceptions.ModelWarning" +) +class TestCHPHTP(CustomMathExamples): + YAML_FILEPATH = "chp_htp.yaml" + + def test_chp_extraction(self, build_and_compare): + overrides = { + "techs.test_chp.constraints.power_loss_factor": 0.1, + "techs.test_chp.constraints.power_to_heat_ratio": 2, + "techs.test_chp.constraints.energy_eff": 0.6, + "techs.test_chp.constraints.turbine_type": "extraction", + } + build_and_compare( + "chp_extraction", + "simple_chp,two_hours", + overrides, + components={ + "constraints": ["chp_extraction_line", "chp_backpressure_line_min"] + }, + ) + + def test_chp_backpressure_and_boiler(self, build_and_compare): + overrides = { + "techs.test_chp.constraints.power_to_heat_ratio": 1.5, + "techs.test_chp.constraints.boiler_eff": 0.8, + "techs.test_chp.constraints.energy_eff": 0.6, + "techs.test_chp.constraints.turbine_type": "backpressure", + } + build_and_compare( + "chp_backpressure_and_boiler", + "simple_chp,two_hours", + overrides, + components={ + "constraints": [ + "chp_divert_fuel_to_boiler", + "chp_backpressure_line_max", + ] + }, + ) + + def test_chp_backpressure_no_boiler(self, build_and_compare): + overrides = { + "techs.test_chp.constraints.power_to_heat_ratio": 1.25, + "techs.test_chp.constraints.turbine_type": "backpressure", + } + build_and_compare( + "chp_backpressure_line_equals", + "simple_chp,two_hours", + overrides, + ) + + +class TestShareAllTimesteps(CustomMathExamples): + YAML_FILEPATH = "share_all_timesteps.yaml" + + @pytest.mark.filterwarnings( + "ignore:(?s).*defines unrecognised constraint `demand_share_equals`:calliope.exceptions.ModelWarning" + ) + def test_demand_share_equals_per_tech(self, build_and_compare): + overrides = { + "nodes.a.techs.test_supply_elec.constraints.demand_share_equals": 0.5, + "nodes.b.techs.test_supply_elec.constraints.demand_share_equals": 0.8, + "parameters": {"demand_share_tech.data": "test_demand_elec"}, + } + build_and_compare( + "demand_share_equals_per_tech", + "simple_supply,two_hours", + overrides, + ) + + @pytest.mark.filterwarnings( + "ignore:(?s).*defines unrecognised constraint `supply_share_equals`:calliope.exceptions.ModelWarning" + ) + def test_supply_share_equals_per_tech(self, build_and_compare): + overrides = { + "nodes.a.techs.test_supply_elec.constraints.supply_share_equals": 0.5, + "nodes.b.techs.test_supply_elec.constraints.supply_share_equals": 0.8, + "parameters": {"supply_share_carrier.data": "electricity"}, + } + build_and_compare( + "supply_share_equals_per_tech", + "simple_supply_and_supply_plus,two_hours", + overrides, + ) + + +class TestSharePerTimestep(CustomMathExamples): + YAML_FILEPATH = "share_per_timestep.yaml" + + @pytest.mark.filterwarnings( + "ignore:(?s).*defines unrecognised constraint `demand_share_per_timestep_equals`:calliope.exceptions.ModelWarning" + ) + def test_demand_share_per_timestep_equals_per_tech(self, build_and_compare): + overrides = { + "nodes.a.techs.test_supply_elec.constraints.demand_share_per_timestep_equals": 0.5, + "nodes.b.techs.test_supply_elec.constraints.demand_share_per_timestep_equals": 0.8, + "parameters": {"demand_share_tech.data": "test_demand_elec"}, + } + build_and_compare( + "demand_share_per_timestep_equals_per_tech", + "simple_supply,two_hours", + overrides, + ) + + @pytest.mark.filterwarnings( + "ignore:(?s).*defines unrecognised constraint `supply_share_per_timestep_equals`:calliope.exceptions.ModelWarning" + ) + def test_supply_share_per_timestep_equals_per_tech(self, build_and_compare): + overrides = { + "nodes.a.techs.test_supply_elec.constraints.supply_share_per_timestep_equals": 0.5, + "nodes.b.techs.test_supply_elec.constraints.supply_share_per_timestep_equals": 0.8, + "parameters": {"supply_share_carrier.data": "electricity"}, + } + build_and_compare( + "supply_share_per_timestep_equals_per_tech", + "simple_supply_and_supply_plus,two_hours", + overrides, + ) + + +class TestDemandSharePerTimestepDecision(CustomMathExamples): + YAML_FILEPATH = "demand_share_per_timestep_decision.yaml" + + def test_demand_share_per_timestep_decision_main(self, build_and_compare): + overrides = { + "parameters": { + "decide_demand_share": { + "data": "test_demand_elec", + "index": ["test_supply_elec", "test_conversion_plus"], + "dims": "techs", + }, + "demand_share_carrier.data": "electricity", + "demand_share_relaxation.data": 0.01, + } + } + build_and_compare( + "demand_share_per_timestep_decision_main", + "conversion_and_conversion_plus,two_hours", + overrides, + components={ + "constraints": [ + "demand_share_per_timestep_decision_main_min", + "demand_share_per_timestep_decision_main_max", + ], + "variables": ["demand_share_per_timestep_decision"], + }, + ) + + def test_demand_share_per_timestep_decision_sum(self, build_and_compare): + overrides = { + "parameters": { + "decide_demand_share": { + "data": "test_demand_elec", + "index": ["test_supply_elec", "test_conversion_plus"], + "dims": "techs", + }, + "demand_share_carrier.data": "electricity", + "demand_share_limit.data": 0.5, + }, + } + build_and_compare( + "demand_share_per_timestep_decision_sum", + "conversion_and_conversion_plus,two_hours", + overrides, + ) + + +class TestPiecewiseCosts(CustomMathExamples): + YAML_FILEPATH = "piecewise_linear_costs.yaml" + + def test_piecewise(self, build_and_compare): + overrides = { + "techs.test_supply_elec.constraints.lifetime": 10, + "techs.test_supply_elec.costs.monetary.interest_rate": 0.1, + "parameters": { + "cost_flow_cap_piecewise_slopes": { + "data": [5, 7, 14], + "index": [0, 1, 2], + "dims": "pieces", + }, + "cost_flow_cap_piecewise_intercept": { + "data": [0, -2, -16], + "index": [0, 1, 2], + "dims": "pieces", + }, + }, + } + build_and_compare( + "piecewise_cost_investment", + "supply_purchase,two_hours", + overrides, + components={ + "constraints": ["piecewise_costs"], + "variables": ["piecewise_cost_investment"], + "global_expressions": ["cost_investment", "cost_var", "cost"], + }, + ) + + +class TestPiecewiseEfficiency(CustomMathExamples): + YAML_FILEPATH = "piecewise_linear_efficiency.yaml" + + def test_piecewise(self, build_and_compare): + overrides = { + "parameters": { + "flow_eff_piecewise_slopes": { + "data": [5, 7, 14], + "index": [0, 1, 2], + "dims": "pieces", + }, + "flow_eff_piecewise_intercept": { + "data": [0, -2, -16], + "index": [0, 1, 2], + "dims": "pieces", + }, + }, + } + build_and_compare( + "piecewise_efficiency", + "conversion_milp,two_hours", + overrides, + components={ + "constraints": [ + "piecewise_efficiency", + "available_flow_cap_binary", + "available_flow_cap_continuous", + "available_flow_cap_binary_continuous_switch", + ], + "variables": ["available_flow_cap"], + }, + ) + + +@pytest.mark.filterwarnings( + "ignore:(?s).*`test_conversion_plus` gives a carrier ratio for `heat`:calliope.exceptions.ModelWarning" +) +class TestFuelDist(CustomMathExamples): + YAML_FILEPATH = "fuel_dist.yaml" + + def test_fuel_distribution(self, build_and_compare): + overrides = { + "parameters": { + "allow_fuel_distribution": { + "data": True, + "index": ["coal"], + "dims": "carriers", + }, + }, + } + build_and_compare( + "fuel_dist_base", + "fuel_distribution,two_hours", + overrides, + components={ + "constraints": [ + "system_balance", + "restrict_total_imports_and_exports", + ], + "variables": ["fuel_distributor"], + }, + ) + + def test_fuel_distribution_nodal_limits(self, build_and_compare): + overrides = { + "parameters": { + "allow_fuel_distribution": { + "data": True, + "index": ["coal"], + "dims": "carriers", + }, + "fuel_import_max": { + "data": 5, + "index": [["coal", "b"]], + "dims": ["carriers", "nodes"], + }, + "fuel_export_max": { + "data": 3, + "index": [["coal", "a"]], + "dims": ["carriers", "nodes"], + }, + }, + } + build_and_compare( + "fuel_dist_nodal", + "fuel_distribution,two_hours", + overrides, + components={ + "constraints": ["restrict_nodal_imports", "restrict_nodal_exports"], + }, + ) + + def test_fuel_distribution_costs(self, build_and_compare): + overrides = { + "parameters": { + "allow_fuel_distribution": { + "data": True, + "index": ["coal"], + "dims": "carriers", + }, + "fuel_distributor_costs": { + "data": 5, + "index": [["coal", "monetary"]], + "dims": ["carriers", "costs"], + }, + }, + } + build_and_compare( + "fuel_dist_cost", + "fuel_distribution,two_hours,investment_costs", + overrides, + components={ + "global_expressions": [ + "cost_investment", # Need to build these up so that `cost` is available in the objective + "cost_var", # Need to build these up so that `cost` is available in the objective + "cost", # Need to build these up so that `cost` is available in the objective + "cost_fuel_distribution", + ], + "objectives": ["minmax_cost_optimisation"], + }, + ) + + +class TestUptimeDowntime(CustomMathExamples): + YAML_FILEPATH = "uptime_downtime_limits.yaml" + + @pytest.mark.filterwarnings( + "ignore:(?s).*defines unrecognised constraint `capacity_factor:calliope.exceptions.ModelWarning" + ) + def test_annual_capacity_factor(self, build_and_compare): + overrides = { + "techs.test_supply_elec.constraints.capacity_factor_min": 0.8, + "techs.test_supply_elec.constraints.capacity_factor_max": 0.9, + } + build_and_compare( + "annual_capacity_factor", + "simple_supply,two_hours", + overrides, + components={ + "constraints": [ + "annual_capacity_factor_min", + "annual_capacity_factor_max", + ] + }, + ) + + def test_downtime(self, build_and_compare): + overrides = { + "parameters": { + "downtime_periods": { + "data": True, + "index": [["test_supply_elec", "a", "2005-01-01 00:00"]], + "dims": ["techs", "nodes", "timesteps"], + }, + }, + } + build_and_compare( + "downtime_period", + "simple_supply,two_hours", + overrides, + components={"constraints": ["downtime_period"]}, + ) + + @pytest.mark.filterwarnings( + "ignore:(?s).*defines unrecognised constraint `uptime_limit`:calliope.exceptions.ModelWarning" + ) + def test_downtime_decision(self, build_and_compare): + overrides = { + "techs.test_supply_elec.constraints.uptime_limit": 1, + } + build_and_compare( + "downtime_period_decision", "supply_milp,two_hours", overrides + ) From 45436c9e4b382c9ed13df1d82c4a9d9cd627fc56 Mon Sep 17 00:00:00 2001 From: Bryn Pickering <17178478+brynpickering@users.noreply.github.com> Date: Mon, 30 Oct 2023 10:53:34 +0000 Subject: [PATCH 3/4] Enable use of transmission techs in custom math (#505) * New expression helper function to get a list of transmission techs on which to slice. This is also enabled in the `defined` where string helper function. * New custom math to showcase it in action, creating a net import share constraint. * Revoke option to use `-` in node names, replacing with `_` (will need baking into a YAML schema in due course). --------- Co-authored-by: Stefan Pfenninger --- .../custom_math/annual_energy_balance.yaml | 4 + doc/_static/custom_math/chp_htp.yaml | 3 + .../demand_share_per_timestep_decision.yaml | 4 + doc/_static/custom_math/fuel_dist.yaml | 4 + doc/_static/custom_math/max_time_varying.yaml | 3 + doc/_static/custom_math/net_import_share.yaml | 53 ++ .../piecewise_linear_efficiency.yaml | 3 + .../custom_math/share_all_timesteps.yaml | 4 + .../custom_math/share_per_timestep.yaml | 4 + .../custom_math/uptime_downtime_limits.yaml | 4 + doc/_static/notebooks/national_scale.ipynb | 664 +++++++++--------- .../images/example_locations_national.svg | 6 +- doc/user/tutorials_01_national.rst | 4 +- src/calliope/backend/helper_functions.py | 113 ++- .../model_config/locations.yaml | 14 +- .../national_scale/scenarios.yaml | 24 +- .../timeseries_data/csp_resource.csv | 2 +- tests/common/constraint_sets.yaml | 120 ++-- .../lp_files/net_annual_import_share_max.lp | 68 ++ .../net_annual_import_share_max_node_group.lp | 42 ++ tests/common/lp_files/net_import_share_max.lp | 77 ++ .../common/lp_files/piecewise_efficiency_2.lp | 89 +++ tests/test_backend_helper_functions.py | 66 +- tests/test_backend_pyomo_objective.py | 4 +- tests/test_core_preprocess.py | 6 +- tests/test_example_models.py | 8 +- tests/test_math.py | 33 + 27 files changed, 989 insertions(+), 437 deletions(-) create mode 100644 doc/_static/custom_math/net_import_share.yaml create mode 100644 tests/common/lp_files/net_annual_import_share_max.lp create mode 100644 tests/common/lp_files/net_annual_import_share_max_node_group.lp create mode 100644 tests/common/lp_files/net_import_share_max.lp create mode 100644 tests/common/lp_files/piecewise_efficiency_2.lp diff --git a/doc/_static/custom_math/annual_energy_balance.yaml b/doc/_static/custom_math/annual_energy_balance.yaml index d1b957131..b26b07cdc 100644 --- a/doc/_static/custom_math/annual_energy_balance.yaml +++ b/doc/_static/custom_math/annual_energy_balance.yaml @@ -8,6 +8,10 @@ # annual_flow_max (if summing over technologies and/or nodes) # flow_max_group (if summing over technologies and/or nodes) +# helper functions used +# inheritance (where) +# sum (expression) + constraints: annual_energy_balance_per_tech_and_node: description: Limit total technology annual energy production at each possible deployment site. diff --git a/doc/_static/custom_math/chp_htp.yaml b/doc/_static/custom_math/chp_htp.yaml index 2e307d952..e9edbcc80 100644 --- a/doc/_static/custom_math/chp_htp.yaml +++ b/doc/_static/custom_math/chp_htp.yaml @@ -65,6 +65,9 @@ # power_to_heat_ratio # boiler_eff +# helper functions used +# reduce_carrier_dim (expression) + constraints: # Extraction turbine constraints # ~~ diff --git a/doc/_static/custom_math/demand_share_per_timestep_decision.yaml b/doc/_static/custom_math/demand_share_per_timestep_decision.yaml index 7b0fe9b2f..cbd95f27a 100644 --- a/doc/_static/custom_math/demand_share_per_timestep_decision.yaml +++ b/doc/_static/custom_math/demand_share_per_timestep_decision.yaml @@ -12,6 +12,10 @@ # demand_share_limit. # decide_demand_share <- Link supply technologies to the demand technology they are going to be a share of. +# helper functions used +# sum (expression) +# select_from_lookup_arrays (expression) + variables: demand_share_per_timestep_decision: description: Relative share of demand that a given technology must meet per node. diff --git a/doc/_static/custom_math/fuel_dist.yaml b/doc/_static/custom_math/fuel_dist.yaml index c2bbf0e4d..e9f5b31f3 100644 --- a/doc/_static/custom_math/fuel_dist.yaml +++ b/doc/_static/custom_math/fuel_dist.yaml @@ -10,6 +10,10 @@ # fuel_distributor_costs # allow_fuel_distribution <- lookup array with a value of `True` for each carrier where you want to track its distribution +# helper functions used +# any (where) +# sum (expression) + variables: fuel_distributor: description: > diff --git a/doc/_static/custom_math/max_time_varying.yaml b/doc/_static/custom_math/max_time_varying.yaml index 93d9b0b6a..2aaeb0462 100644 --- a/doc/_static/custom_math/max_time_varying.yaml +++ b/doc/_static/custom_math/max_time_varying.yaml @@ -7,6 +7,9 @@ # flow_cap_max_relative_per_ts # storage_max_relative_per_ts +# helper functions used +# reduce_carrier_dim (expression) + constraints: max_time_varying_flow_cap: description: Limit flow out in each hour according to a time varying fractional limit that is multiplied by the technology flow cap. This represents, for instance, the impact of outdoor temperature on the maximum output of a technology relative to its rated max output. diff --git a/doc/_static/custom_math/net_import_share.yaml b/doc/_static/custom_math/net_import_share.yaml new file mode 100644 index 000000000..66aacc775 --- /dev/null +++ b/doc/_static/custom_math/net_import_share.yaml @@ -0,0 +1,53 @@ +# Force upper limit on carrier imports within nodes or across all nodes as a share of all carrier flows at a node/across nodes. +# Imports of transmission technologies are defined by `flow_out` (outflow from a transmission technology that originated in a remote node). +# We assume that transmission technologies `test_transmission_elec` and `test_transmission_heat` have been defined. + +# New top-level parameters: +# net_import_share + +# helper functions used +# defined (where) +# sum (expression) +# get_transmission_techs (expression) + +constraints: + net_import_share_max: + description: Limit upper bound on electricity imports within nodes as a share of all electricity flows at each node. + foreach: [nodes, timesteps] + where: "defined(techs=test_transmission_elec, within=nodes, how=any)" + equations: + - expression: net_import_share * sum(flow_out[techs=$transmission_techs, carriers=electricity], over=techs) <= $total_energy_balance + sub_expressions: + total_energy_balance: + - expression: sum(flow_out[carriers=electricity], over=techs) - sum(flow_in[carriers=electricity], over=techs) + slices: + transmission_techs: + - expression: get_transmission_techs(test_transmission_elec) + + net_annual_import_share_max: + description: Limit upper bound on annual electricity imports within nodes as a share of all electricity flows at each node. + foreach: [nodes] + where: "defined(techs=test_transmission_elec, within=nodes, how=any)" + equations: + - expression: net_import_share * sum(flow_out[techs=$transmission_techs, carriers=electricity], over=[techs, timesteps]) <= $total_energy_balance + sub_expressions: + total_energy_balance: + - expression: sum(flow_out[carriers=electricity], over=[techs, timesteps]) - sum(flow_in[carriers=electricity], over=[techs, timesteps]) + slices: + transmission_techs: + - expression: get_transmission_techs(test_transmission_elec) + + net_annual_import_share_max_node_group: + description: Limit upper bound on annual heat imports across a subset of nodes in the model as a share of combined heat flows in those nodes. + equations: + - expression: net_import_share * sum(flow_out[techs=$transmission_techs, nodes=$node_group, carriers=$carrier], over=[nodes, techs, timesteps]) <= $total_energy_balance + sub_expressions: + total_energy_balance: + - expression: sum(flow_out[nodes=$node_group, carriers=$carrier], over=[nodes, techs, timesteps]) - sum(flow_in[nodes=$node_group, carriers=$carrier], over=[nodes, techs, timesteps]) + slices: + transmission_techs: + - expression: get_transmission_techs(test_transmission_heat) + node_group: # The subset of nodes in which to limit heat imports + - expression: "[a, c]" + carrier: # The carrier for which to limit imports + - expression: heat \ No newline at end of file diff --git a/doc/_static/custom_math/piecewise_linear_efficiency.yaml b/doc/_static/custom_math/piecewise_linear_efficiency.yaml index 1102ae210..147a46353 100644 --- a/doc/_static/custom_math/piecewise_linear_efficiency.yaml +++ b/doc/_static/custom_math/piecewise_linear_efficiency.yaml @@ -6,6 +6,9 @@ # flow_eff_piecewise_slopes (defining the new parameter `pieces`) # flow_eff_piecewise_intercept (defining the new parameter `pieces`) +# helper functions used +# reduce_carrier_dim (expression) + variables: available_flow_cap: description: Flow capacity that will be set to zero if the technology is not operating in a given timestep and will be set to the value of the decision variable `flow_cap` otherwise. diff --git a/doc/_static/custom_math/share_all_timesteps.yaml b/doc/_static/custom_math/share_all_timesteps.yaml index 007b7b815..1b1fbe908 100644 --- a/doc/_static/custom_math/share_all_timesteps.yaml +++ b/doc/_static/custom_math/share_all_timesteps.yaml @@ -7,6 +7,10 @@ # demand_share_equals # supply_share_equals +# helper functions used +# sum (expression) +# reduce_carrier_dim (expression) + constraints: demand_share_equals_per_tech: description: Set the total outflow of certain technologies which produce the `power` carrier to a share of total demand inflow. diff --git a/doc/_static/custom_math/share_per_timestep.yaml b/doc/_static/custom_math/share_per_timestep.yaml index 1b9cb5e6c..bc19df8ba 100644 --- a/doc/_static/custom_math/share_per_timestep.yaml +++ b/doc/_static/custom_math/share_per_timestep.yaml @@ -7,6 +7,10 @@ # demand_share_per_timestep_equals # supply_share_per_timestep_equals +# helper functions used +# sum (expression) +# reduce_carrier_dim (expression) + constraints: demand_share_per_timestep_equals_per_tech: description: Set the per-timestep outflow of certain technologies which produce the `power` carrier to a share of demand inflow. diff --git a/doc/_static/custom_math/uptime_downtime_limits.yaml b/doc/_static/custom_math/uptime_downtime_limits.yaml index d8b1b4933..ecaf9f2fa 100644 --- a/doc/_static/custom_math/uptime_downtime_limits.yaml +++ b/doc/_static/custom_math/uptime_downtime_limits.yaml @@ -12,6 +12,10 @@ # downtime_periods (from CSV as a timeseries) # uptime_limit +# helper functions used +# sum (expression) +# reduce_carrier_dim (expression) + constraints: annual_capacity_factor_min: description: Limit the lower bound of annual technology operation as a fraction of annual operation at maximum capacity. diff --git a/doc/_static/notebooks/national_scale.ipynb b/doc/_static/notebooks/national_scale.ipynb index 9355b49b8..903ba059c 100644 --- a/doc/_static/notebooks/national_scale.ipynb +++ b/doc/_static/notebooks/national_scale.ipynb @@ -418,12 +418,12 @@ " * costs (costs) object 'monetary'\n", " * loc_carriers (loc_carriers) object 'region1::power' .....\n", " * loc_techs (loc_techs) object 'region1::free_transmi...\n", - " * loc_techs_area (loc_techs_area) object 'region1-3::csp' ...\n", + " * loc_techs_area (loc_techs_area) object 'region1_3::csp' ...\n", " ... ...\n", - " * loc_techs_store (loc_techs_store) object 'region1-3::csp'...\n", - " * loc_techs_supply_plus (loc_techs_supply_plus) object 'region1-3...\n", + " * loc_techs_store (loc_techs_store) object 'region1_3::csp'...\n", + " * loc_techs_supply_plus (loc_techs_supply_plus) object 'region1_3...\n", " * loc_techs_transmission (loc_techs_transmission) object 'region1:...\n", - " * locs (locs) object 'region2' ... 'region1-3'\n", + " * locs (locs) object 'region2' ... 'region1_3'\n", " * techs (techs) object 'ac_transmission' ... 'csp'\n", " * timesteps (timesteps) datetime64[ns] 2005-01-01 ......\n", "Data variables: (12/34)\n", @@ -436,7 +436,7 @@ " ... ...\n", " lookup_loc_carriers (loc_carriers) object 'region1::free_tran...\n", " lookup_loc_techs (loc_techs_non_conversion) object 'region...\n", - " lookup_loc_techs_area (locs) object '' ... 'region1-3::csp'\n", + " lookup_loc_techs_area (locs) object '' ... 'region1_3::csp'\n", " timestep_resolution (timesteps) float64 1.0 1.0 1.0 ... 1.0 1.0\n", " timestep_weights (timesteps) float64 1.0 1.0 1.0 ... 1.0 1.0\n", " max_demand_timesteps (carriers) datetime64[ns] 2005-01-05T16:0...\n", @@ -445,42 +445,42 @@ " applied_overrides: \n", " scenario: None\n", " defaults: carrier_ratios:\\ncharge_rate:\\nenergy_cap_per_storag...\n", - " allow_operate_mode: 1