diff --git a/.github/actions/generate-docs/action.yml b/.github/actions/generate-docs/action.yml
index 4f2328b5d..9784ab9e5 100644
--- a/.github/actions/generate-docs/action.yml
+++ b/.github/actions/generate-docs/action.yml
@@ -1,9 +1,5 @@
name: Generate documentation
description: Generate all documentation
-inputs:
- build:
- description: Just subcommand for `build-docs` to run
- default: all
runs:
using: composite
@@ -22,4 +18,4 @@ runs:
enable-cache: true
- name: Build documentation
shell: bash
- run: just build-docs::${{ inputs.build }}
+ run: just build-docs
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 14f254db1..822574943 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -9,13 +9,7 @@ updates:
directory: "/"
schedule:
interval: "weekly"
- groups:
- rust:
- patterns: ["*"]
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
- groups:
- github-actions:
- patterns: ["*"]
diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml
index 103bc3d0a..a0b3d4aad 100644
--- a/.github/workflows/deploy-docs.yml
+++ b/.github/workflows/deploy-docs.yml
@@ -23,17 +23,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v6
- with:
- fetch-tags: true
- fetch-depth: 0
- - name: Set user name and email for git
- run: |
- # Needed so we can apply patches
- git config --global user.name "GitHub Actions"
- git config --global user.email "github-actions[bot]@users.noreply.github.com"
- uses: ./.github/actions/generate-docs
- with:
- build: all_with_old
- name: Setup pages
uses: actions/configure-pages@v5
- name: Upload artifact
diff --git a/.github/workflows/pre-commit-autoupdate.yml b/.github/workflows/pre-commit-autoupdate.yml
index bcf789263..bc8476281 100644
--- a/.github/workflows/pre-commit-autoupdate.yml
+++ b/.github/workflows/pre-commit-autoupdate.yml
@@ -16,10 +16,9 @@ jobs:
- uses: actions/checkout@v6
with:
persist-credentials: false
- - uses: actions/setup-python@v6
- - uses: actions-rust-lang/setup-rust-toolchain@v1
+ - uses: actions/setup-python@v5
- uses: browniebroke/pre-commit-autoupdate-action@main
- - uses: peter-evans/create-pull-request@v8
+ - uses: peter-evans/create-pull-request@v7
with:
token: ${{ secrets.GH_TOKEN_FOR_UPDATES }}
branch: update/pre-commit-hooks
diff --git a/.lycheeignore b/.lycheeignore
index 097724f09..a1358272f 100644
--- a/.lycheeignore
+++ b/.lycheeignore
@@ -1,6 +1,5 @@
# For some reason these spurious links appear in some cargo doc-generated files
^file://.+/MUSE2/book/api/index\.html$
-^file://.+/MUSE2/book/README\.html$
# Some of these links give a 404, even though they're generated in the API docs. No idea why.
^https://docs.rs/erased-serde/
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 8edd70794..076379741 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -31,7 +31,7 @@ repos:
files: /settings\.toml$
types_or: [toml]
- repo: https://github.com/igorshubovych/markdownlint-cli
- rev: v0.48.0
+ rev: v0.47.0
hooks:
- id: markdownlint-fix
- repo: https://github.com/pre-commit/mirrors-prettier
@@ -53,7 +53,7 @@ repos:
- id: clippy
args: ["--all-targets", "--", "-D", "warnings"]
- repo: https://github.com/astral-sh/ruff-pre-commit
- rev: v0.15.5
+ rev: v0.15.4
hooks:
- id: ruff-check
types_or: [python]
@@ -65,12 +65,12 @@ repos:
hooks:
- id: nbstripout
- repo: https://github.com/codespell-project/codespell
- rev: v2.4.2
+ rev: v2.4.1
hooks:
- id: codespell
args: [--ignore-words, .codespell_ignore.txt]
exclude: \.svg$
- - repo: https://github.com/EnergySystemsModellingLab/cffconvert
- rev: 2.1.0
+ - repo: https://github.com/citation-file-format/cffconvert
+ rev: b6045d78aac9e02b039703b030588d54d53262ac
hooks:
- id: validate-cff
diff --git a/Cargo.lock b/Cargo.lock
index ef2fef354..2875291ee 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -99,9 +99,9 @@ checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236"
[[package]]
name = "assert_cmd"
-version = "2.2.0"
+version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a686bbee5efb88a82df0621b236e74d925f470e5445d3220a5648b892ec99c9"
+checksum = "9c5bcfa8749ac45dd12cb11055aeeb6b27a3895560d60d71e3c23bf979e60514"
dependencies = [
"anstyle",
"bstr",
@@ -467,9 +467,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "erased-serde"
-version = "0.4.10"
+version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec"
+checksum = "89e8918065695684b2b0702da20382d5ae6065cf3327bc2d6436bd49a71ce9f3"
dependencies = [
"serde",
"serde_core",
@@ -1020,7 +1020,7 @@ dependencies = [
"serde_string_enum",
"strum 0.28.0",
"tempfile",
- "toml 1.0.6+spec-1.1.0",
+ "toml 1.0.3+spec-1.1.0",
"unicase",
"yaml-rust2",
]
@@ -1565,9 +1565,9 @@ dependencies = [
[[package]]
name = "tempfile"
-version = "3.27.0"
+version = "3.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
+checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0"
dependencies = [
"fastrand",
"getrandom 0.3.3",
@@ -1626,9 +1626,9 @@ dependencies = [
[[package]]
name = "toml"
-version = "1.0.6+spec-1.1.0"
+version = "1.0.3+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "399b1124a3c9e16766831c6bba21e50192572cdd98706ea114f9502509686ffc"
+checksum = "c7614eaf19ad818347db24addfa201729cf2a9b6fdfd9eb0ab870fcacc606c0c"
dependencies = [
"indexmap",
"serde_core",
diff --git a/Cargo.toml b/Cargo.toml
index 7c86b5593..6c2c1950f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -19,8 +19,8 @@ float-cmp = "0.10.0"
itertools = "0.14.0"
serde = {version = "1.0.228", features = ["derive", "rc"]}
serde_string_enum = "0.2.1"
-tempfile = "3.27.0"
-toml = "1.0.6"
+tempfile = "3.26.0"
+toml = "1.0.3"
unicase = "2.9.0"
fern = {version = "0.7.1", features = ["chrono", "colored"]}
chrono = "0.4"
@@ -37,10 +37,10 @@ strum = {version = "0.28.0", features = ["derive"]}
documented = "0.9.2"
dirs = "6.0.0"
edit = "0.1.5"
-erased-serde = "0.4.10"
+erased-serde = "0.4.9"
[dev-dependencies]
-assert_cmd = "2.2.0"
+assert_cmd = "2.1.2"
map-macro = "0.3.0"
rstest = {version = "0.26.1", default-features = false, features = ["crate-name"]}
yaml-rust2 = {version = "0.11.0", default-features = false}
diff --git a/build-docs.just b/build-docs.just
index 1a5cf67cd..d84474184 100644
--- a/build-docs.just
+++ b/build-docs.just
@@ -1,11 +1,8 @@
# Make rustdoc warnings fatal
export RUSTDOCFLAGS := "-D warnings"
-# Build all documentation, except old docs
-all: cli-help file-format examples versions book api
-
-# Build all documentation, including old docs
-all_with_old: all old
+# Build all documentation
+all: cli-help file-format examples book api
# Build book
book:
@@ -36,15 +33,3 @@ file-format *ARGS:
examples:
@echo Building docs for examples
@uv run docs/generate_example_docs.py
-
-# Build TOC for old versions
-versions:
- @echo Building TOC for old versions of documentation
- @uv run docs/generate_versions_docs.py
-
-# Build documentation for previous releases
-old:
- @# Clean output dir
- @rm -rf book/release
-
- @uv run docs/build_old_docs.py
diff --git a/docs/.gitignore b/docs/.gitignore
index 9eece75fc..ab9fc47fa 100644
--- a/docs/.gitignore
+++ b/docs/.gitignore
@@ -1,4 +1,3 @@
# Generated documentation files
command_line_help.md
examples.md
-versions.md
diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md
index 9b0c9a264..4df0560d3 100644
--- a/docs/SUMMARY.md
+++ b/docs/SUMMARY.md
@@ -23,4 +23,3 @@
- [Release notes](release_notes/README.md)
- [MUSE2 v2.0.0 (October 14, 2025)](release_notes/v2.0.0.md)
- [Next unreleased version](release_notes/upcoming.md)
-- [Other versions of documentation](versions.md)
diff --git a/docs/build_old_docs.py b/docs/build_old_docs.py
deleted file mode 100755
index 71fa35ae2..000000000
--- a/docs/build_old_docs.py
+++ /dev/null
@@ -1,86 +0,0 @@
-#!/usr/bin/env python3
-#
-# A script to generate documentation for previous releases of MUSE2.
-
-import os
-import shutil
-import subprocess as sp
-from pathlib import Path
-from tempfile import TemporaryDirectory
-
-from release import get_releases
-
-DOCS_SITE_ROOT = "https://energysystemsmodellinglab.github.io/MUSE2"
-REPO_ROOT = Path(__file__).parent.parent.absolute()
-DOCS_DIR = REPO_ROOT / "docs"
-
-
-def clone_repo_to(dest: Path):
- """Clone this repo somewhere else."""
- print("Making a copy of repo")
- sp.run(("git", "clone", REPO_ROOT, dest), check=True, capture_output=True)
-
- # Add a symlink to cargo cache dir
- try:
- os.symlink(REPO_ROOT / "target", dest / "target")
- except (NotImplementedError, OSError):
- # Only newer versions of Windows support symlinks and these require the user to have
- # additional privileges (or to be in developer mode)
- print(
- "WARN: Could not create symlink to cache directory; cache will not be stored"
- )
-
-
-def apply_patches_for_release(release: str, repo_path: Path) -> None:
- """Apply patches (if any) for the given release."""
- patches_dir = DOCS_DIR / "release" / "patches" / release
- for patch_path in sorted(patches_dir.glob("*.patch")):
- sp.run(("git", "-C", str(repo_path), "am", str(patch_path)), check=True)
-
-
-def build_docs_for_release(release: str, repo_path: Path, outdir: Path) -> None:
- """Build documentation for a given release."""
- print(f"Building docs for {release}")
-
- # Check out release
- sp.run(
- ("git", "-C", str(repo_path), "checkout", release),
- check=True,
- capture_output=True,
- )
-
- # Apply patches, if any
- apply_patches_for_release(release, repo_path)
-
- # Build docs
- sp.run(("just", f"{repo_path!s}/build-docs"), check=True)
-
- # Patch versions.html to redirect to main versions page
- with (repo_path / "book" / "versions.html").open("w", encoding="utf-8") as f:
- f.write(f"""
-
-""")
-
- # Move to output directory
- release_outdir = outdir / release
- print(f"Copying to {release_outdir}")
- shutil.move((repo_path / "book"), release_outdir)
-
-
-def build_old_docs() -> None:
- """Build documentation for previous releases."""
- outdir = REPO_ROOT / "book" / "release"
- outdir.mkdir(parents=True, exist_ok=True)
-
- # Clone this repo to a temporary directory
- with TemporaryDirectory() as tmpdir:
- repo_path = Path(tmpdir)
- clone_repo_to(repo_path)
-
- # Generate documentation for each previous release
- for release in get_releases():
- build_docs_for_release(release, repo_path, outdir)
-
-
-if __name__ == "__main__":
- build_old_docs()
diff --git a/docs/generate_versions_docs.py b/docs/generate_versions_docs.py
deleted file mode 100644
index a4cd5a67d..000000000
--- a/docs/generate_versions_docs.py
+++ /dev/null
@@ -1,31 +0,0 @@
-#!/usr/bin/env python3
-# /// script
-# dependencies = [
-# "jinja2",
-# ]
-# ///
-#
-# A script to generate the versions.md file, listing links to old versions of documentation.
-
-from pathlib import Path
-
-from jinja2 import Environment, FileSystemLoader
-from release import get_releases
-
-DOCS_DIR = Path(__file__).parent.absolute()
-
-
-def generate_versions_md() -> None:
- """Write the versions.md file."""
- path = DOCS_DIR / "versions.md"
- print(f"Writing {path}")
- env = Environment(loader=FileSystemLoader(DOCS_DIR / "templates"))
- template = env.get_template("versions.md.jinja")
- out = template.render(releases=get_releases())
-
- with path.open("w", encoding="utf-8") as f:
- f.write(out)
-
-
-if __name__ == "__main__":
- generate_versions_md()
diff --git a/docs/model/investment.md b/docs/model/investment.md
index cc753cdab..6dccf743a 100644
--- a/docs/model/investment.md
+++ b/docs/model/investment.md
@@ -41,6 +41,12 @@ investments.
- \\( \text{FOM}_{opt,r} \\): Annual fixed Operations & Maintenance costs per unit of capacity for
\\( opt \\) in \\( r \\).
+ - \\( FinancingInDecomDec_{ex} \\) (binary flag). This user-defined option specifies whether to
+ include estimated financing costs in the economic viability threshold when considering the
+ decommissioning of an existing asset. This can only be used on profit-evaluable assets. Used
+ with \\( PercentDebt_{ex} \\). Where financing costs are included, the percentage debt is
+ multiplied by the original capex, and the result is annualised.
+
- For new candidate assets:
- \\( \text{CAPEX}_{ca,r} \\): Upfront capital expenditure required per unit of new capacity for
@@ -63,48 +69,33 @@ providing investment and dynamic decommissioning decisions.
### Pre-calculation of metrics for each supply option
+> Note: This section contains a reference to "scopes", a feature that is not yet implemented
+
- Annualised fixed costs per unit of capacity (\\( AFC_{opt,r} \\)): For new candidates, this is
their annualised CAPEX plus FOM. For existing assets, the relevant fixed cost is its FOM.
-- Calculate the specific process and commodity flow costs (\\(\text{SPCF}\_{t})\\):
-
- \\[
- \text{SPCF}\_{t} = \sum\_{c} \Big( cost\_{\text{input}}[c] \cdot input\_{\text{coeff}}[c] +
- cost\_{\text{output}}[c] \cdot output\_{\text{coeff}}[c] \Big)
- \\]
-
-#### Coefficients of activity
-
-- Calculate net revenue per unit of activity \\(AC\_{t}^{NPV} \\) (Tool A):
- \\[
- \begin{aligned}
- AC\_{t}^{NPV} = &-cost\_{\text{var}}[t] \\\\
- &- \text{SPCF}\_{t} \\\\
- &+ \sum\_{c} \Big( output\_{\text{coeff}}[c] - input\_{\text{coeff}}[c] \Big)
- \cdot \lambda\_{c,r,t} \\\\
- &+ \varepsilon \\\\
- \end{aligned}
- \\]
- \\(\varepsilon \approx 1\times 10^{-14}\\) is added to
- each \\(AC\_{t}^{NPV} \\) to allow assets which are breakeven (or very close to breakeven) to be
- dispatched.
+- Costs per unit of activity in each time slice, calculated as follows:
-- Calculate cost per unit of activity \\( AC\_{t}^{LCOX} \\) (Tool B). Note that the commodity
- of interest (primary output \\( c\_{primary} \\)) is excluded from the price term:
\\[
\begin{aligned}
- AC\_{t}^{LCOX} = & \quad cost\_{\text{var}}[t] \\\\
- &+ \text{SPCF}\_{t} \\\\
- &- \sum\_{c \neq c\_{primary}} \Big( output\_{\text{coeff}}[c] - input\_{\text{coeff}}
- [c] \Big)
+ AC_t = & \quad cost\_{\text{var}}[t] \\\\
+ &+ \sum\_{c} \Big( cost\_{\text{input}}[c] \cdot input\_{\text{coeff}}[c]
+ + cost\_{\text{output}}[c] \cdot output\_{\text{coeff}}[c] \Big) \\\\
+ &+ \sum\_{c} \Big( input\_{\text{coeff}}[c] - output\_{\text{coeff}}[c] \Big)
\cdot \lambda\_{c,r,t} \\\\
+ &+ \sum\_{s,c} in\\_scope[s] \cdot \Big\\{ \\\\
+ &\quad \quad (cost\_{\text{prod}}[s,c] - \mu\_{s,c}^{\text{prod}})
+ \cdot output\_{\text{coeff}}[c] \\\\
+ &\quad \quad + (cost\_{\text{cons}}[s,c] - \mu\_{s,c}^{\text{cons}})
+ \cdot input\_{\text{coeff}}[c] \\\\
+ &\quad \quad + (cost\_{\text{net}}[s,c] - \mu\_{s,c}^{\text{net}})
+ \cdot (output\_{\text{coeff}}[c] - input\_{\text{coeff}}[c]) \\\\
+ &\Big\\}
\end{aligned}
\\]
-- The third term in both activity coefficients accounts for commodity price flow costs, which are
- the net costs or revenues associated with the commodity flows. In the LCOX case the commodity of
- interest is excluded from this term because the cost of production shouldn't depend on the market
- price of the commodity being produced.
+ When using the LCOX objective, the calculation is adjusted to exclude the commodity of interest
+ (\\( \lambda\_{c,r,t} \\) are set to zero).
### Initialise demand profiles for commodity of interest
@@ -139,8 +130,8 @@ providing investment and dynamic decommissioning decisions.
#### Tool A: NPV
-This method is used when the decision rule is `single` and the objective is annualised profit for
-agents serving commodity \\( c \\). It iteratively builds a supply portfolio by selecting
+This method is used when decision rule is single objective and objective is annualised profit for
+agents' serving commodity \\( c \\). This method iteratively builds a supply portfolio by selecting
options that offer the highest annualised profit for serving the current commodity demand. The
economic evaluation uses \\( \pi_{prevMSY} \\) prices and takes account of asset-specific
operational constraints (e.g., minimum load levels) and the balance level of the target commodity
@@ -148,10 +139,11 @@ operational constraints (e.g., minimum load levels) and the balance level of the
- **Optimise capacity and dispatch to maximise annualised profit:** Solve a small optimisation
sub-problem to maximise the asset's surplus, subject to its operational rules and the specific
- demand tranche it is being asked to serve.
+ demand tranche it is being asked to serve. \\(\varepsilon \approx 1×10^{-14}\\) is added to each
+ \\(AC_t \\) to allow assets which are breakeven (or very close to breakeven) to be dispatched.
\\[
- maximise \Big\\{\sum_t act_t AC\_{t}^{NPV}
+ maximise \Big\\{ - \sum_t act_t \* (AC_t + \varepsilon)
\Big\\}
\\]
@@ -163,25 +155,12 @@ operational constraints (e.g., minimum load levels) and the balance level of the
- A demand constraint, where output cannot exceed demand in the tranche, which adapts based on the
commodity's balance level (time slice, season, annual).
- - Capacity is constrained up to \\( CapMaxBuild \\) for candidates, and to known capacity for
+ - Capacity is constrained to \\( CapMaxBuild \\) for candidates, and to known capacity for
existing assets.
-- **Decide on metric:** The type of metric used to compare profitability is dependent on the value of
- \\(\text{AFC}\\). If \\(\text{AFC} = 0\\) within the tolerance provided by the `float_cmp` crate,
- the associated investment option is always prioritised over options with \\(\text{AFC} > 0\\).
-
-- **If \\(\text{AFC} > 0\\), Use the profitability index \\(\text{PI}\\) metric:** This is the total
- annualised surplus divided by the annualised fixed cost.
- \\[
- \text{PI} =
- \frac{\sum\_t act\_t \cdot \text{AC}\_t^{\text{NPV}}}{\text{AFC} \cdot \text{cap}}
- \\]
-
-- **If \\(\text{AFC} = 0\\), Use the total annualised surplus metric \\(\text{TAS}\\):**
- \\[
- \text{TAS} =
- \sum\_t act\_t \cdot \text{AC}\_t^{\text{NPV}}
- \\]
+- **Calculate a profitability index:** This is the total annualised surplus (\\( - \sum_t
+ act_t \* AC \\)) divided by the annualised fixed cost (\\(
+ AFC \* cap \\)).
#### Tool B: LCOX
@@ -196,12 +175,12 @@ commodities are set to zero, and the commodity of interest is assumed to have ze
For each asset option:
- **Optimise capacity and dispatch to minimise annualised cost:** Solve a small optimisation
- sub-problem to minimise the asset's annualised cost, subject to its operational rules and the specific
+ sub-problem to maximise the asset's surplus, subject to its operational rules and the specific
demand tranche it is being asked to serve.
\\[
minimise \Big\\{
- \text{AFC} \times cap + \sum\_t act\_t \times AC\_{t}^{LCOX} + VoLL \times UnmetD\_t
+ AF \* cap + \sum_t act_t \* AC_t + VoLL \* UnmetD_t
\Big\\}
\\]
@@ -214,195 +193,15 @@ For each asset option:
each timeslice of the tranche, which adapts based on the commodity's balance level (time slice,
season, annual).
- - Capacity is constrained up to \\( CapMaxBuild \\) for candidates, and to known capacity for
+ - Capacity is constrained to \\( CapMaxBuild \\) for candidates, and to known capacity for
existing assets.
- VoLL variables are active to ensure a feasible solution alongside maximum operation of the
asset.
-- **Calculate a Cost Index Metric:** This is the total annualised cost divided by the annual output.
- \\[
- \text{Cost Index} = \frac{\text{AFC} \times \text{cap}_r + \sum_t act_t
- \times \text{AC}_t^{\text{LCOX}}}{\sum_t act_t}
- \\]
-
-#### Equal-Metric Fallback
-
-If two or more investment options from the same tool have equal metrics, the following tie-breaking
-rules are applied in order:
-
-1. Assets which are already commissioned are preferred over new candidate assets.
-2. Newer (commissioned later) assets are preferred over older assets.
-3. If there is still a tie, the first option in the data structure storing the metrics is selected,
- which is an arbitrary choice. A `debug` level log message is emitted in this case.
-
-## Example: Gas Power Plant
-
-The following is an illustrative example of how the NPV and LCOX approaches work, using a simple
-gas combined-cycle power plant as the supply option under consideration.
-This example demonstrates the evaluation across two time periods
-\\(t\_0\\) (peak period) and \\(t\_1\\) (off-peak period) with variable operating costs
- \\( cost\_{var}[t] \\) constant in all time periods.
-
-### Model Parameters
-
-#### Asset Parameters
-
-| Parameter | Notation | Value | Description |
-|--------------------------------|--------------------------------------|---------------------------|-----------------------------------|
-| Primary output (Electricity) | \\( output\_{coeff}[c_{primary}] \\) | 1.0 MWh per unit activity | Main commodity produced |
-| By-product output (Waste heat) | \\( output\_{coeff}[c_{heat}] \\) | 0.5 MWh per unit activity | Co-product from generation |
-| Input (Natural gas) | \\( input\_{coeff}[c_{gas}] \\) | 2.5 MWh per unit activity | Fuel consumption |
-| Variable operating cost | \\( cost\_{var}[t] \\) | £5/MWh of activity | Operating costs per unit activity |
-
-
-All per-flow costs represented in the general formulas as \\( cost\_{input} \\) and
-\\( cost\_{output} \\) are assumed to be zero.
-
-#### Investment Parameters
-
-| Parameter | Notation | Value |
-|-----------------------|----------------------|-----------|
-| Annualised fixed cost | \\( AFC\_{opt,r} \\) | £1,000/MW |
-| Capacity | \\( cap \\) | 100 MW |
-
-#### Market Prices by Time Period
-
-| Commodity | Notation | \\(t_0\\) (Peak) | \\(t_1\\) (Off-peak) |
-|-------------|-------------------------------------|------------------|----------------------|
-| Electricity | \\( \lambda\_{c\_{primary},r,t} \\) | £90/MWh | £50/MWh |
-| Heat | \\( \lambda\_{c\_{heat},r,t} \\) | £25/MWh | £15/MWh |
-| Natural gas | \\( \lambda\_{c\_{gas},r,t} \\) | £35/MWh | £25/MWh |
-
-### NPV Approach (Tool A)
-
-#### Calculate Net Revenue per Unit of Activity
-
-**For \\(t\_0\\) (peak period):**
-
-\\[
-\begin{aligned}
-AC_{t_{0}}^{NPV} &= (1.0 \times 90) + (0.5 \times 25) + (-2.5 \times 35) - 5 \\\\
-&= 90 + 12.5 - 87.5 - 5 \\\\
-&= \text{£10/MWh}
-\end{aligned}
-\\]
-
-The asset earns £10 profit for every MWh it operates during peak periods.
-
-**For \\(t\_1\\) (off-peak period):**
-
-\\[
-\begin{aligned}
-AC_{t\_1}^{NPV} &= (1.0 \times 50) + (0.5 \times 15) + (-2.5 \times 25) - 5 \\\\
-&= 50 + 7.5 - 62.5 - 5 \\\\
-&= \text{£} -10 \text{/MWh}
-\end{aligned}
-\\]
-
-The asset loses £10 for every MWh it operates during off-peak periods.
-
-#### Dispatch Optimisation
-
-The optimisation maximises total net revenue across all time periods:
-
-\\[
-\max \sum\_t act\_t \cdot AC\_t^{NPV} = act\_{t_{0}} \cdot 10 + act\_{t\_1} \cdot (-10)
-\\]
-
-where \\( act\_t \\) is the activity (operational level) in each time slice, subject to operational
- constraints and demand requirements.
-
-In this case, the optimiser will prefer to dispatch the asset during \\(t\_0\\) (profitable) and
-minimise operation during \\(t\_1\\) (unprofitable), subject to technical constraints such as minimum
-load requirements.
-
-#### Profitability Index
-
-The profitability index is calculated as:
-
-\\[
-\text{PI} = \frac{\sum\_t act\_t \cdot AC\_t^{NPV}}{AFC \times cap}
-\\]
-
-Suppose the dispatch optimiser determines \\( act\_{t\_{0}} = 80 \\) MWh and \\( act\_{t\_1} = 20 \\)
-MWh are the optimal activity levels:
-
-\\[
-\begin{aligned}
-\text{PI} &= \frac{(80 \times 10) + (20 \times (-10))}{1{,}000 \times 100} \\\\
-&= \frac{800 - 200}{100{,}000} \\\\
-&= \frac{600}{100{,}000} \\\\
-&= 0.006
-\end{aligned}
-\\]
-
-The profitability index is then compared against all other options to determine which asset provides
- the best return on investment for serving the demand.
-
-### LCOX Approach (Tool B)
-
-#### Net Cost per Unit of Activity
-
-**For \\(t\_0\\) (peak period):**
-
-\\[
-\begin{aligned}
-AC\_{t\_{0}}^{LCOX} &= 5 + (2.5 \times 35) - (0.5 \times 25) \\\\
-&= 5 + 87.5 - 12.5 \\\\
-&= \text{£80/MWh}
-\end{aligned}
-\\]
-
-It costs £80 per MWh to operate during peak periods (net of heat by-product sales).
-
-**For \\(t_1\\) (off-peak period):**
-
-\\[
-\begin{aligned}
-AC\_{t\_1}^{LCOX} &= 5 + (2.5 \times 25) - (0.5 \times 15) \\\\
-&= 5 + 62.5 - 7.5 \\\\
-&= \text{£60/MWh}
-\end{aligned}
-\\]
-
-It costs £60 per MWh to operate during off-peak periods, reflecting lower gas prices
- and lower heat by-product value.
-
-#### Capacity and Dispatch Optimisation
-
-The optimiser determines the most cost-effective capacity and dispatch pattern to meet demand across
-both time periods by minimising the total annualised cost with respect to decision variables
-\\( cap \\) and \\( act\_t \\):
-
-\\[
-AFC \cdot cap + \sum\_t act\_t \cdot AC\_t^{LCOX} = 1{,}000 \cdot cap + act\_{t\_{0}}
- \cdot 80 + act\_{t\_1} \cdot 60
-\\]
-
-#### Cost Index (Levelised Cost of X)
-
-The Cost Index is calculated as:
-
-\\[
-\text{Cost Index} = \frac{AFC \cdot cap + \sum\_t act\_t \cdot AC\_t^{LCOX}}{\sum\_t act\_t}
-\\]
-
-Suppose the optimiser determines \\( cap = 100 \\) MW, \\( act\_{t\_{0}} = 150 \\) MWh,
- and \\( act\_{t\_1} = 80 \\) MWh are the optimal capacity and activity levels:
-
-\\[
-\begin{aligned}
-\text{Cost Index} &= \frac{(1{,}000 \times 100) + (150 \times 80) + (80 \times 60)}{150 + 80} \\\\
-&= \frac{100{,}000 + 12{,}000 + 4{,}800}{230} \\\\
-&= \frac{116{,}800}{230} \\\\
-&= \text{£508/MWh}
-\end{aligned}
-\\]
-
-The Cost Index is £508 per MWh of electricity produced.
- This metric is compared across all supply options to identify
- the lowest-cost solution for meeting demand.
+- **Calculate a cost index:** This is the total annualised cost (\\(
+ AFC \* cap_r + \sum_{t} act_t \* AC_t \\)), divided by the annual output
+ \\( \sum_t act_t \\).
[framework-overview]: https://energysystemsmodellinglab.github.io/MUSE2/model/index.html#framework-overview
[Dispatch Optimisation Formulation]: ./dispatch_optimisation.md
diff --git a/docs/release/__init__.py b/docs/release/__init__.py
deleted file mode 100644
index f2505b426..000000000
--- a/docs/release/__init__.py
+++ /dev/null
@@ -1,23 +0,0 @@
-"""Common functionality for working with different versions."""
-
-import re
-import subprocess as sp
-
-
-def is_release_tag(tag: str) -> bool:
- """Whether the git tag indicates a version.
-
- We don't include pre-releases.
- """
- return re.match(r"^v[0-9]+\.[0-9]+\.[0-9]+$", tag) is not None
-
-
-def get_releases() -> list[str]:
- """Get all release tags for this repo, sorted by semantic version."""
- ret = sp.run(
- ("git", "tag", "-l", "--sort=-version:refname"),
- capture_output=True,
- check=True,
- encoding="utf-8",
- )
- return [tag for tag in ret.stdout.splitlines() if is_release_tag(tag)]
diff --git a/docs/release/patches/v2.0.0/0001-remove-unrecognised-parameter.patch b/docs/release/patches/v2.0.0/0001-remove-unrecognised-parameter.patch
deleted file mode 100644
index 91602f9dd..000000000
--- a/docs/release/patches/v2.0.0/0001-remove-unrecognised-parameter.patch
+++ /dev/null
@@ -1,23 +0,0 @@
-From 3129c56aea05dd2b7e88c76bb2050fded0529243 Mon Sep 17 00:00:00 2001
-From: Aurash Karimi
-Date: Wed, 19 Nov 2025 09:48:11 +0000
-Subject: [PATCH] remove unrecognised parameter
-
----
- book.toml | 1 -
- 1 file changed, 1 deletion(-)
-
-diff --git a/book.toml b/book.toml
-index 41fba09c..84d181ee 100644
---- a/book.toml
-+++ b/book.toml
-@@ -1,7 +1,6 @@
- [book]
- authors = ["Alex Dewar"]
- language = "en"
--multilingual = false
- src = "docs"
- title = "MUSE2"
-
---
-2.53.0
diff --git a/docs/release_notes/upcoming.md b/docs/release_notes/upcoming.md
index e4b0ad259..6d94973c4 100644
--- a/docs/release_notes/upcoming.md
+++ b/docs/release_notes/upcoming.md
@@ -31,6 +31,8 @@ ready to be released, carry out the following steps:
- Allow for adding both a `prod` and `cons` levy to a commodity ([#969])
- Availability limits can now be provided at multiple levels for a process ([#1018])
- Pricing strategy can now vary by commodity ([#1021])
+- `marginal` and `full` commodity pricing strategies no longer require enabling
+ `please_give_me_broken_results` ([#1185])
## Experimental features
diff --git a/docs/templates/versions.md.jinja b/docs/templates/versions.md.jinja
deleted file mode 100644
index 66360aa2d..000000000
--- a/docs/templates/versions.md.jinja
+++ /dev/null
@@ -1,8 +0,0 @@
-# Other versions of documentation
-
-The MUSE2 documentation for different releases is available below.
-
-- [Current development version](./README.md)
-{%- for release in releases %}
-- [{{ release }}](release/{{ release }}/index.html)
-{%- endfor %}
diff --git a/rust-toolchain.toml b/rust-toolchain.toml
index cc6d07f37..71223b651 100644
--- a/rust-toolchain.toml
+++ b/rust-toolchain.toml
@@ -1,3 +1,3 @@
[toolchain]
-channel = "1.94.0"
+channel = "1.93.1"
profile = "default"
diff --git a/src/asset.rs b/src/asset.rs
index 7a59d54e4..c4cc85e57 100644
--- a/src/asset.rs
+++ b/src/asset.rs
@@ -10,7 +10,7 @@ use crate::simulation::CommodityPrices;
use crate::time_slice::{TimeSliceID, TimeSliceSelection};
use crate::units::{
Activity, ActivityPerCapacity, Capacity, Dimensionless, FlowPerActivity, MoneyPerActivity,
- MoneyPerCapacity, MoneyPerFlow, Year,
+ MoneyPerCapacity, MoneyPerFlow,
};
use anyhow::{Context, Result, ensure};
use indexmap::IndexMap;
@@ -611,35 +611,33 @@ impl Asset {
annual_capital_cost(capital_cost, lifetime, discount_rate)
}
- /// Get the annual fixed costs (AFC) per unit of activity for this asset
+ /// Get the annual capital cost per unit of activity for this asset
///
- /// Total capital costs and fixed opex are shared equally over the year in accordance with the
- /// annual activity.
- pub fn get_annual_fixed_costs_per_activity(
+ /// Total capital costs (cost per capacity * capacity) are shared equally over the year in
+ /// accordance with the annual activity.
+ pub fn get_annual_capital_cost_per_activity(
&self,
annual_activity: Activity,
) -> MoneyPerActivity {
let annual_capital_cost_per_capacity = self.get_annual_capital_cost_per_capacity();
- let annual_fixed_opex = self.process_parameter.fixed_operating_cost * Year(1.0);
- let total_annual_fixed_costs =
- (annual_capital_cost_per_capacity + annual_fixed_opex) * self.total_capacity();
+ let total_annual_capital_cost = annual_capital_cost_per_capacity * self.total_capacity();
assert!(
annual_activity > Activity::EPSILON,
- "Cannot calculate annual fixed costs per activity for an asset with zero annual activity"
+ "Cannot calculate annual capital cost per activity for an asset with zero annual activity"
);
- total_annual_fixed_costs / annual_activity
+ total_annual_capital_cost / annual_activity
}
- /// Get the annual fixed costs (AFC) per unit of output flow for this asset
+ /// Get the annual capital cost per unit of output flow for this asset
///
- /// Total capital costs and fixed opex are shared equally across all output flows in accordance
- /// with the annual activity and total output per unit of activity.
- pub fn get_annual_fixed_costs_per_flow(&self, annual_activity: Activity) -> MoneyPerFlow {
- let annual_fixed_costs_per_activity =
- self.get_annual_fixed_costs_per_activity(annual_activity);
+ /// Total capital costs (cost per capacity * capacity) are shared equally across all output
+ /// flows in accordance with the annual activity and total output per unit of activity.
+ pub fn get_annual_capital_cost_per_flow(&self, annual_activity: Activity) -> MoneyPerFlow {
+ let annual_capital_cost_per_activity =
+ self.get_annual_capital_cost_per_activity(annual_activity);
let total_output_per_activity = self.get_total_output_per_activity();
assert!(total_output_per_activity > FlowPerActivity::EPSILON); // input checks should guarantee this
- annual_fixed_costs_per_activity / total_output_per_activity
+ annual_capital_cost_per_activity / total_output_per_activity
}
/// Maximum activity for this asset
diff --git a/src/example/patches.rs b/src/example/patches.rs
index 66dbd278f..d05f65331 100644
--- a/src/example/patches.rs
+++ b/src/example/patches.rs
@@ -32,6 +32,30 @@ fn get_all_patches() -> PatchMap {
.with_addition("A0_RES,all,npv,,"),
],
),
+ (
+ // The simple example with electricity priced using marginal costs
+ "simple_marginal",
+ vec![FilePatch::new("commodities.csv").with_replacement(&[
+ "id,description,type,time_slice_level,pricing_strategy,units",
+ "GASPRD,Gas produced,sed,season,shadow,PJ",
+ "GASNAT,Natural gas,sed,season,shadow,PJ",
+ "ELCTRI,Electricity,sed,daynight,marginal,PJ",
+ "RSHEAT,Residential heating,svd,daynight,shadow,PJ",
+ "CO2EMT,CO2 emitted,oth,annual,unpriced,ktCO2",
+ ])],
+ ),
+ (
+ // The simple example with gas commodities priced using full costs
+ "simple_full",
+ vec![FilePatch::new("commodities.csv").with_replacement(&[
+ "id,description,type,time_slice_level,pricing_strategy,units",
+ "GASPRD,Gas produced,sed,season,full,PJ",
+ "GASNAT,Natural gas,sed,season,full,PJ",
+ "ELCTRI,Electricity,sed,daynight,shadow,PJ",
+ "RSHEAT,Residential heating,svd,daynight,shadow,PJ",
+ "CO2EMT,CO2 emitted,oth,annual,unpriced,ktCO2",
+ ])],
+ ),
]
.into_iter()
.collect()
diff --git a/src/graph/investment.rs b/src/graph/investment.rs
index 4af0d9797..8d014b78a 100644
--- a/src/graph/investment.rs
+++ b/src/graph/investment.rs
@@ -1,9 +1,8 @@
//! Module for solving the investment order of commodities
use super::{CommoditiesGraph, GraphEdge, GraphNode};
-use crate::commodity::{CommodityMap, CommodityType, PricingStrategy};
+use crate::commodity::{CommodityMap, CommodityType};
use crate::region::RegionID;
use crate::simulation::investment::InvestmentSet;
-use anyhow::{Result, ensure};
use highs::{Col, HighsModelStatus, RowProblem, Sense};
use indexmap::IndexMap;
use log::warn;
@@ -42,14 +41,14 @@ fn solve_investment_order_for_year(
graphs: &IndexMap<(RegionID, u32), CommoditiesGraph>,
commodities: &CommodityMap,
year: u32,
-) -> Result> {
+) -> Vec {
// Initialise InvestmentGraph for this year from the set of original `CommodityGraph`s
let mut investment_graph = init_investment_graph_for_year(graphs, year, commodities);
// TODO: condense sibling commodities (commodities that share at least one producer)
// Condense strongly connected components
- investment_graph = compress_cycles(&investment_graph, commodities)?;
+ investment_graph = compress_cycles(&investment_graph);
// Perform a topological sort on the condensed graph
// We can safely unwrap because `toposort` will only return an error in case of cycles, which
@@ -57,7 +56,7 @@ fn solve_investment_order_for_year(
let order = toposort(&investment_graph, None).unwrap();
// Compute layers for investment
- Ok(compute_layers(&investment_graph, &order))
+ compute_layers(&investment_graph, &order)
}
/// Initialise an `InvestmentGraph` for the given year from a set of `CommodityGraph`s
@@ -118,39 +117,15 @@ fn init_investment_graph_for_year(
}
/// Compresses cycles into `InvestmentSet::Cycle` nodes
-fn compress_cycles(graph: &InvestmentGraph, commodities: &CommodityMap) -> Result {
+fn compress_cycles(graph: &InvestmentGraph) -> InvestmentGraph {
// Detect strongly connected components
let mut condensed_graph = condensation(graph.clone(), true);
// Order nodes within each strongly connected component
order_sccs(&mut condensed_graph, graph);
- // Pre-scan SCCs for offending pricing strategies (FullCost / MarginalCost).
- for node_weight in condensed_graph.node_weights() {
- if node_weight.len() <= 1 {
- continue;
- }
- let offenders: Vec<_> = node_weight
- .iter()
- .flat_map(|s| s.iter_markets())
- .filter(|(cid, _)| {
- matches!(
- &commodities[cid].pricing_strategy,
- PricingStrategy::MarginalCost | PricingStrategy::FullCost
- )
- })
- .map(|(cid, _)| cid.clone())
- .collect();
-
- ensure!(
- offenders.is_empty(),
- "Cannot use FullCost/MarginalCost pricing strategies for commodities with circular \
- dependencies. Offending commodities: {offenders:?}"
- );
- }
-
// Map to a new InvestmentGraph
- let mapped = condensed_graph.map(
+ condensed_graph.map(
// Map nodes to InvestmentSet
// If only one member, keep as-is; if multiple members, create Cycle
|_, node_weight| match node_weight.len() {
@@ -166,9 +141,7 @@ fn compress_cycles(graph: &InvestmentGraph, commodities: &CommodityMap) -> Resul
},
// Keep edges the same
|_, edge_weight| edge_weight.clone(),
- );
-
- Ok(mapped)
+ )
}
/// Order the members of each strongly connected component using a mixed-integer linear program.
@@ -517,13 +490,13 @@ pub fn solve_investment_order_for_model(
commodity_graphs: &IndexMap<(RegionID, u32), CommoditiesGraph>,
commodities: &CommodityMap,
years: &[u32],
-) -> Result>> {
+) -> HashMap> {
let mut investment_orders = HashMap::new();
for year in years {
- let order = solve_investment_order_for_year(commodity_graphs, commodities, *year)?;
+ let order = solve_investment_order_for_year(commodity_graphs, commodities, *year);
investment_orders.insert(*year, order);
}
- Ok(investment_orders)
+ investment_orders
}
#[cfg(test)]
@@ -595,7 +568,7 @@ mod tests {
commodities.insert("C".into(), Rc::new(svd_commodity));
let graphs = IndexMap::from([(("GBR".into(), 2020), graph)]);
- let result = solve_investment_order_for_year(&graphs, &commodities, 2020).unwrap();
+ let result = solve_investment_order_for_year(&graphs, &commodities, 2020);
// Expected order: C, B, A (leaf nodes first)
// No cycles or layers, so all investment sets should be `Single`
@@ -623,7 +596,7 @@ mod tests {
commodities.insert("B".into(), Rc::new(sed_commodity));
let graphs = IndexMap::from([(("GBR".into(), 2020), graph)]);
- let result = solve_investment_order_for_year(&graphs, &commodities, 2020).unwrap();
+ let result = solve_investment_order_for_year(&graphs, &commodities, 2020);
// Should be a single `Cycle` investment set containing both commodities
assert_eq!(result.len(), 1);
@@ -662,7 +635,7 @@ mod tests {
commodities.insert("D".into(), Rc::new(svd_commodity));
let graphs = IndexMap::from([(("GBR".into(), 2020), graph)]);
- let result = solve_investment_order_for_year(&graphs, &commodities, 2020).unwrap();
+ let result = solve_investment_order_for_year(&graphs, &commodities, 2020);
// Expected order: D, Layer(B, C), A
assert_eq!(result.len(), 3);
@@ -701,7 +674,7 @@ mod tests {
(("GBR".into(), 2020), graph.clone()),
(("FRA".into(), 2020), graph),
]);
- let result = solve_investment_order_for_year(&graphs, &commodities, 2020).unwrap();
+ let result = solve_investment_order_for_year(&graphs, &commodities, 2020);
// Expected order: Should have three layers, each with two commodities (one per region)
assert_eq!(result.len(), 3);
diff --git a/src/input.rs b/src/input.rs
index 8ef1e1f04..7cdfcca23 100644
--- a/src/input.rs
+++ b/src/input.rs
@@ -263,8 +263,7 @@ pub fn load_model>(model_dir: P) -> Result {
)?;
// Solve investment order for each region/year
- let investment_order =
- solve_investment_order_for_model(&commodity_graphs, &commodities, years)?;
+ let investment_order = solve_investment_order_for_model(&commodity_graphs, &commodities, years);
let model_path = model_dir
.as_ref()
diff --git a/src/input/commodity.rs b/src/input/commodity.rs
index d30a79cfa..96e0601ad 100644
--- a/src/input/commodity.rs
+++ b/src/input/commodity.rs
@@ -164,18 +164,13 @@ fn validate_commodity(commodity: &Commodity) -> Result<()> {
}
}
- // Gatekeep alternative pricing options
- if !matches!(
- commodity.pricing_strategy,
- PricingStrategy::Shadow | PricingStrategy::Unpriced
- ) {
+ // Gatekeep scarcity-adjusted pricing option
+ if commodity.pricing_strategy == PricingStrategy::ScarcityAdjusted {
ensure!(
broken_model_options_allowed(),
- "Price strategies other than 'shadow' and 'unpriced' are currently experimental. \
+ "The 'scarcity' pricing strategy is currently experimental. \
To run anyway, set the {ALLOW_BROKEN_OPTION_NAME} option to true."
);
- }
- if commodity.pricing_strategy == PricingStrategy::ScarcityAdjusted {
warn!(
"The pricing strategy for {} is set to 'scarcity'. Commodity prices may be \
incorrect if assets have more than one output commodity. See: {ISSUES_URL}/677",
diff --git a/src/patch.rs b/src/patch.rs
index 7c28c0db9..6b0b843e1 100644
--- a/src/patch.rs
+++ b/src/patch.rs
@@ -120,6 +120,8 @@ pub struct FilePatch {
filename: String,
/// The header row (optional). If `None`, the header is not checked against base files.
header_row: Option>,
+ /// Full replacement content for this file (optional)
+ replacement_content: Option,
/// Rows to delete (each row is a vector of fields)
to_delete: CSVTable,
/// Rows to add (each row is a vector of fields)
@@ -132,6 +134,7 @@ impl FilePatch {
FilePatch {
filename: filename.into(),
header_row: None,
+ replacement_content: None,
to_delete: IndexSet::new(),
to_add: IndexSet::new(),
}
@@ -139,6 +142,10 @@ impl FilePatch {
/// Set the header row for this patch (header should be a comma-joined string, e.g. "a,b,c").
pub fn with_header(mut self, header: impl Into) -> Self {
+ assert!(
+ self.replacement_content.is_none(),
+ "Cannot set header when replacement content is set for this FilePatch",
+ );
assert!(
self.header_row.is_none(),
"Header already set for this FilePatch",
@@ -149,8 +156,48 @@ impl FilePatch {
self
}
+ /// Set full replacement content for this file from a slice of lines.
+ ///
+ /// Each line is joined with newlines, and a trailing newline is added.
+ /// All lines must have the same number of columns (commas).
+ /// Example: `with_replacement(&["header1,header2", "value1,value2"])`
+ pub fn with_replacement(mut self, lines: &[&str]) -> Self {
+ assert!(
+ self.header_row.is_none(),
+ "Cannot set replacement content when header is set for this FilePatch",
+ );
+ assert!(
+ self.to_delete.is_empty() && self.to_add.is_empty(),
+ "Cannot set replacement content when additions/deletions are set for this FilePatch",
+ );
+ assert!(
+ self.replacement_content.is_none(),
+ "Replacement content already set for this FilePatch",
+ );
+
+ // Validate that all lines have the same number of columns
+ if !lines.is_empty() {
+ let first_col_count = lines[0].matches(',').count() + 1;
+ for (idx, line) in lines.iter().enumerate() {
+ let col_count = line.matches(',').count() + 1;
+ assert_eq!(
+ col_count, first_col_count,
+ "Line {idx} has {col_count} columns but line 0 has {first_col_count}: {line:?}"
+ );
+ }
+ }
+
+ let content = lines.join("\n") + "\n";
+ self.replacement_content = Some(content);
+ self
+ }
+
/// Add a row to the patch (row should be a comma-joined string, e.g. "a,b,c").
pub fn with_addition(mut self, row: impl Into) -> Self {
+ assert!(
+ self.replacement_content.is_none(),
+ "Cannot add rows when replacement content is set for this FilePatch",
+ );
let s = row.into();
let v = s.split(',').map(|s| s.trim().to_string()).collect();
self.to_add.insert(v);
@@ -159,6 +206,10 @@ impl FilePatch {
/// Mark a row for deletion from the base (row should be a comma-joined string, e.g. "a,b,c").
pub fn with_deletion(mut self, row: impl Into) -> Self {
+ assert!(
+ self.replacement_content.is_none(),
+ "Cannot delete rows when replacement content is set for this FilePatch",
+ );
let s = row.into();
let v = s.split(',').map(|s| s.trim().to_string()).collect();
self.to_delete.insert(v);
@@ -167,13 +218,21 @@ impl FilePatch {
/// Apply this patch to a base model and return the modified CSV as a string.
fn apply(&self, base_model_dir: &Path) -> Result {
- // Read the base file to string
+ // Read and validate the base file path
let base_path = base_model_dir.join(&self.filename);
ensure!(
base_path.exists() && base_path.is_file(),
"Base file for patching does not exist: {}",
base_path.display()
);
+
+ // If this patch is a full replacement, validate the base file exists
+ // (checked above) and return the replacement content
+ if let Some(content) = &self.replacement_content {
+ return Ok(content.clone());
+ }
+
+ // Read the base file to string
let base = fs::read_to_string(&base_path)?;
// Apply the patch
@@ -232,7 +291,6 @@ fn modify_base_with_patch(base: &str, patch: &FilePatch) -> Result {
header_row_vec.join(", ")
);
}
-
// Read all rows from the base, preserving order and checking for duplicates
let mut base_rows: CSVTable = CSVTable::new();
for result in reader.records() {
@@ -278,6 +336,16 @@ fn modify_base_with_patch(base: &str, patch: &FilePatch) -> Result {
);
}
+ // Check all rows match base header length
+ let expected_len = base_header_vec.len();
+ for row in &base_rows {
+ ensure!(
+ row.len() == expected_len,
+ "Row has {} columns but header has {expected_len}: {row:?}",
+ row.len(),
+ );
+ }
+
// Serialize CSV output using csv::Writer
let mut wtr = Writer::from_writer(vec![]);
wtr.write_record(base_header_vec.iter())?;
@@ -379,6 +447,73 @@ mod tests {
assert!(assets_content.contains("GASDRV,GBR,A0_GEX,4003.26,2020"));
}
+ #[test]
+ fn file_patch_with_replacement() {
+ let expected = "col1,col2\nnew1,new2\n";
+
+ let model_dir = ModelPatch::from_example("simple")
+ .with_file_patch(
+ FilePatch::new("assets.csv").with_replacement(&["col1,col2", "new1,new2"]),
+ )
+ .build_to_tempdir()
+ .unwrap();
+
+ let assets_path = model_dir.path().join("assets.csv");
+ let assets_content = std::fs::read_to_string(assets_path).unwrap();
+ assert_eq!(assets_content, expected);
+ }
+
+ #[test]
+ #[should_panic(
+ expected = "Cannot set replacement content when header is set for this FilePatch"
+ )]
+ fn file_patch_replacement_after_header_panics() {
+ let _ = FilePatch::new("assets.csv")
+ .with_header("col1,col2")
+ .with_replacement(&["col1,col2", "a,b"]);
+ }
+
+ #[test]
+ #[should_panic(
+ expected = "Cannot set replacement content when additions/deletions are set for this FilePatch"
+ )]
+ fn file_patch_replacement_after_addition_panics() {
+ let _ = FilePatch::new("assets.csv")
+ .with_addition("a,b")
+ .with_replacement(&["col1,col2", "a,b"]);
+ }
+
+ #[test]
+ #[should_panic(expected = "Cannot add rows when replacement content is set for this FilePatch")]
+ fn file_patch_addition_after_replacement_panics() {
+ let _ = FilePatch::new("assets.csv")
+ .with_replacement(&["col1,col2", "a,b"])
+ .with_addition("c,d");
+ }
+
+ #[test]
+ fn file_patch_with_replacement_missing_base_file_fails() {
+ let model_patch = ModelPatch::from_example("simple").with_file_patch(
+ FilePatch::new("not_a_real_file.csv").with_replacement(&["x,y", "1,2"]),
+ );
+
+ let expected = format!(
+ "Base file for patching does not exist: {}",
+ std::path::PathBuf::from("examples")
+ .join("simple")
+ .join("not_a_real_file.csv")
+ .display()
+ );
+
+ assert_error!(model_patch.build_to_tempdir(), expected);
+ }
+
+ #[test]
+ #[should_panic(expected = "Line 1 has 2 columns but line 0 has 3")]
+ fn file_patch_replacement_column_count_mismatch_panics() {
+ let _ = FilePatch::new("test.csv").with_replacement(&["col1,col2,col3", "a,b"]);
+ }
+
#[test]
fn toml_patch() {
// Patch to add an extra milestone year (2050)
diff --git a/src/simulation/investment.rs b/src/simulation/investment.rs
index 5e9716a7f..688bd524d 100644
--- a/src/simulation/investment.rs
+++ b/src/simulation/investment.rs
@@ -19,8 +19,7 @@ use std::fmt::Display;
pub mod appraisal;
use appraisal::coefficients::calculate_coefficients_for_assets;
use appraisal::{
- AppraisalOutput, appraise_investment, count_equal_and_best_appraisal_outputs,
- sort_appraisal_outputs_by_investment_priority,
+ AppraisalOutput, appraise_investment, sort_appraisal_outputs_by_investment_priority,
};
/// A map of demand across time slices for a specific market
@@ -652,7 +651,7 @@ fn get_candidate_assets<'a>(
}
/// Print debug message if there are multiple equally good outputs
-fn log_on_equal_appraisal_outputs(
+fn warn_on_equal_appraisal_outputs(
outputs: &[AppraisalOutput],
agent_id: &AgentID,
commodity_id: &CommodityID,
@@ -662,7 +661,11 @@ fn log_on_equal_appraisal_outputs(
return;
}
- let num_identical = count_equal_and_best_appraisal_outputs(outputs);
+ // Count the number of identical (or nearly identical) appraisal outputs
+ let num_identical = outputs[1..]
+ .iter()
+ .take_while(|output| outputs[0].compare_metric(output).is_eq())
+ .count();
if num_identical > 0 {
let asset_details = outputs[..=num_identical]
@@ -826,7 +829,7 @@ fn select_best_assets(
}
// Warn if there are multiple equally good assets
- log_on_equal_appraisal_outputs(&outputs_for_opts, &agent.id, &commodity.id, region_id);
+ warn_on_equal_appraisal_outputs(&outputs_for_opts, &agent.id, &commodity.id, region_id);
let best_output = outputs_for_opts.into_iter().next().unwrap();
diff --git a/src/simulation/investment/appraisal.rs b/src/simulation/investment/appraisal.rs
index acbb303cb..7fbf42d68 100644
--- a/src/simulation/investment/appraisal.rs
+++ b/src/simulation/investment/appraisal.rs
@@ -378,21 +378,6 @@ pub fn sort_appraisal_outputs_by_investment_priority(outputs_for_opts: &mut Vec<
});
}
-/// Counts the number of top appraisal outputs in a sorted slice that are indistinguishable
-/// by both metric and fallback ordering. Excludes the first element from the count.
-pub fn count_equal_and_best_appraisal_outputs(outputs: &[AppraisalOutput]) -> usize {
- if outputs.is_empty() {
- return 0;
- }
- outputs[1..]
- .iter()
- .take_while(|output| {
- output.compare_metric(&outputs[0]).is_eq()
- && compare_asset_fallback(&output.asset, &outputs[0].asset).is_eq()
- })
- .count()
-}
-
#[cfg(test)]
mod tests {
use super::*;
@@ -958,137 +943,4 @@ mod tests {
// The invalid output should have been filtered out
assert_eq!(outputs.len(), 0);
}
-
- /// Tests for counting number of equal metrics using identical assets so only metric values
- /// affect the count.
- #[rstest]
- #[case(vec![5.0], 0, "single_element")]
- #[case(vec![5.0, 5.0, 5.0], 2, "all_equal_returns_len_minus_one")]
- #[case(vec![1.0, 2.0, 3.0], 0, "none_equal_to_best")]
- #[case(vec![5.0, 5.0, 9.0], 1, "partial_equality_stops_at_first_difference")]
- #[case(vec![5.0, 5.0, 9.0, 5.0], 1, "equality_does_not_resume_after_gap")]
- fn count_equal_best_lcox_metric(
- asset: Asset,
- #[case] metric_values: Vec,
- #[case] expected_count: usize,
- #[case] description: &str,
- ) {
- let metrics: Vec> = metric_values
- .into_iter()
- .map(|v| Box::new(LCOXMetric::new(MoneyPerActivity(v))) as Box)
- .collect();
-
- let outputs =
- appraisal_outputs_with_investment_priority_invariant_to_assets(metrics, &asset);
-
- assert_eq!(
- count_equal_and_best_appraisal_outputs(&outputs),
- expected_count,
- "Failed for case: {description}"
- );
- }
-
- /// Empty slice count should return 0.
- #[test]
- fn count_equal_best_empty_slice_returns_zero() {
- let outputs: Vec = vec![];
- assert_eq!(count_equal_and_best_appraisal_outputs(&outputs), 0);
- }
-
- /// Equal metrics but differing asset fallback (commissioned vs. candidate) →
- /// outputs are distinguishable, so count should be 0.
- #[rstest]
- fn count_equal_best_equal_metric_different_fallback_returns_zero(
- process: Process,
- region_id: RegionID,
- agent_id: AgentID,
- ) {
- let process_rc = Rc::new(process);
- let capacity = Capacity(10.0);
-
- let commissioned = Asset::new_commissioned(
- agent_id.clone(),
- process_rc.clone(),
- region_id.clone(),
- capacity,
- 2020,
- )
- .unwrap();
- let candidate =
- Asset::new_candidate(process_rc.clone(), region_id.clone(), capacity, 2020).unwrap();
-
- let metric_value = MoneyPerActivity(5.0);
- let outputs = appraisal_outputs(
- vec![commissioned, candidate],
- vec![
- Box::new(LCOXMetric::new(metric_value)),
- Box::new(LCOXMetric::new(metric_value)),
- ],
- );
-
- assert_eq!(count_equal_and_best_appraisal_outputs(&outputs), 0);
- }
-
- /// Equal metrics and equal asset fallback (same commissioned status and commission year) →
- /// the second element is indistinguishable, so count should be 1.
- #[rstest]
- fn count_equal_best_equal_metric_and_equal_fallback_returns_one(
- process: Process,
- region_id: RegionID,
- agent_id: AgentID,
- ) {
- let process_rc = Rc::new(process);
- let capacity = Capacity(10.0);
- let year = 2020;
-
- let asset1 = Asset::new_commissioned(
- agent_id.clone(),
- process_rc.clone(),
- region_id.clone(),
- capacity,
- year,
- )
- .unwrap();
- let asset2 = Asset::new_commissioned(
- agent_id.clone(),
- process_rc.clone(),
- region_id.clone(),
- capacity,
- year,
- )
- .unwrap();
-
- let metric_value = MoneyPerActivity(5.0);
- let outputs = appraisal_outputs(
- vec![asset1, asset2],
- vec![
- Box::new(LCOXMetric::new(metric_value)),
- Box::new(LCOXMetric::new(metric_value)),
- ],
- );
-
- assert_eq!(count_equal_and_best_appraisal_outputs(&outputs), 1);
- }
-
- /// Equal NPV metrics and identical assets → second element should be counted.
- #[rstest]
- fn count_equal_best_equal_npv_metrics(asset: Asset) {
- let make_npv = |surplus: f64, fixed_cost: f64| {
- Box::new(NPVMetric::new(ProfitabilityIndex {
- total_annualised_surplus: Money(surplus),
- annualised_fixed_cost: Money(fixed_cost),
- })) as Box
- };
-
- let metrics = vec![
- make_npv(200.0, 100.0),
- make_npv(200.0, 100.0), // Equal to best
- make_npv(100.0, 100.0), // Worse
- ];
-
- let outputs =
- appraisal_outputs_with_investment_priority_invariant_to_assets(metrics, &asset);
-
- assert_eq!(count_equal_and_best_appraisal_outputs(&outputs), 1);
- }
}
diff --git a/src/simulation/optimisation.rs b/src/simulation/optimisation.rs
index e14926033..c86638224 100644
--- a/src/simulation/optimisation.rs
+++ b/src/simulation/optimisation.rs
@@ -263,14 +263,6 @@ impl Solution<'_> {
.map(|((asset, time_slice), &value)| (asset, time_slice, Activity(value)))
}
- /// Iterate over the keys for activity for each existing asset
- pub fn iter_activity_keys_for_existing(
- &self,
- ) -> impl Iterator- {
- self.iter_activity_for_existing()
- .map(|(asset, time_slice, _activity)| (asset, time_slice))
- }
-
/// Activity for each candidate asset
pub fn iter_activity_for_candidates(
&self,
@@ -283,14 +275,6 @@ impl Solution<'_> {
.map(|((asset, time_slice), &value)| (asset, time_slice, Activity(value)))
}
- /// Iterate over the keys for activity for each candidate asset
- pub fn iter_activity_keys_for_candidates(
- &self,
- ) -> impl Iterator
- {
- self.iter_activity_for_candidates()
- .map(|(asset, time_slice, _activity)| (asset, time_slice))
- }
-
/// Iterate over unmet demand
pub fn iter_unmet_demand(
&self,
diff --git a/src/simulation/prices.rs b/src/simulation/prices.rs
index 81c2b4f84..46dabd86d 100644
--- a/src/simulation/prices.rs
+++ b/src/simulation/prices.rs
@@ -8,8 +8,12 @@ use crate::time_slice::{TimeSliceID, TimeSliceInfo, TimeSliceSelection};
use crate::units::{Activity, Dimensionless, MoneyPerActivity, MoneyPerFlow, Year};
use anyhow::Result;
use indexmap::IndexMap;
+use itertools::iproduct;
use std::collections::{HashMap, HashSet};
+/// Iterator item type for asset activity iterators
+type Item<'a> = (&'a AssetRef, &'a TimeSliceID, Activity);
+
/// Calculate commodity prices.
///
/// Calculate prices for each commodity/region/time-slice according to the commodity's configured
@@ -26,81 +30,73 @@ use std::collections::{HashMap, HashSet};
/// A `CommodityPrices` mapping `(commodity, region, time_slice)` to `MoneyPerFlow` representing
/// endogenous prices computed from the optimisation solution.
pub fn calculate_prices(model: &Model, solution: &Solution, year: u32) -> Result {
- // Collect shadow prices for all SED/SVD commodities
+ // Compute shadow prices for all SED/SVD commodities (needed by all strategies)
let shadow_prices = CommodityPrices::from_iter(solution.iter_commodity_balance_duals());
+ // Partition markets by pricing strategy into a map keyed by `PricingStrategy`.
+ // For now, commodities use a single strategy for all regions, but this may change in the future.
+ let mut pricing_sets = HashMap::new();
+ for ((commodity_id, commodity), region_id) in
+ iproduct!(&model.commodities, model.iter_regions())
+ {
+ if commodity.pricing_strategy == PricingStrategy::Unpriced {
+ continue;
+ }
+ pricing_sets
+ .entry(&commodity.pricing_strategy)
+ .or_insert_with(HashSet::new)
+ .insert((commodity_id.clone(), region_id.clone()));
+ }
+
// Set up empty prices map
let mut result = CommodityPrices::default();
- // Get investment order for the year - prices will be calculated in the reverse of this order
- let investment_order = &model.investment_order[&year];
-
- // Iterate over investment sets in reverse order. Markets within the same set can be priced
- // simultaneously, since they are independent (apart from Cycle sets when using the "marginal"
- // and "full" strategies, which get flagged at the validation stage).
- for investment_set in investment_order.iter().rev() {
- // Partition markets by pricing strategy into a map keyed by `PricingStrategy`.
- // For now, commodities use a single strategy for all regions, but this may change in the future.
- let mut pricing_sets = HashMap::new();
- for (commodity_id, region_id) in investment_set.iter_markets() {
- let commodity = &model.commodities[commodity_id];
- if commodity.pricing_strategy == PricingStrategy::Unpriced {
- continue;
- }
- pricing_sets
- .entry(&commodity.pricing_strategy)
- .or_insert_with(HashSet::new)
- .insert((commodity_id.clone(), region_id.clone()));
- }
-
- // Add prices for shadow-priced commodities
- if let Some(shadow_set) = pricing_sets.get(&PricingStrategy::Shadow) {
- for (commodity_id, region_id, time_slice) in shadow_prices.keys() {
- if shadow_set.contains(&(commodity_id.clone(), region_id.clone())) {
- let price = shadow_prices
- .get(commodity_id, region_id, time_slice)
- .unwrap();
- result.insert(commodity_id, region_id, time_slice, price);
- }
+ // Add prices for shadow-priced commodities
+ if let Some(shadow_set) = pricing_sets.get(&PricingStrategy::Shadow) {
+ for (commodity_id, region_id, time_slice) in shadow_prices.keys() {
+ if shadow_set.contains(&(commodity_id.clone(), region_id.clone())) {
+ let price = shadow_prices
+ .get(commodity_id, region_id, time_slice)
+ .unwrap();
+ result.insert(commodity_id, region_id, time_slice, price);
}
}
+ }
- // Add prices for scarcity-adjusted commodities
- if let Some(scarcity_set) = pricing_sets.get(&PricingStrategy::ScarcityAdjusted) {
- let scarcity_prices = calculate_scarcity_adjusted_prices(
- solution.iter_activity_duals(),
- &shadow_prices,
- scarcity_set,
- );
- result.extend(scarcity_prices);
- }
+ // Add prices for scarcity-adjusted commodities
+ if let Some(scarcity_set) = pricing_sets.get(&PricingStrategy::ScarcityAdjusted) {
+ let scarcity_prices = calculate_scarcity_adjusted_prices(
+ solution.iter_activity_duals(),
+ &shadow_prices,
+ scarcity_set,
+ );
+ result.extend(scarcity_prices);
+ }
- // Add prices for marginal cost commodities
- if let Some(marginal_set) = pricing_sets.get(&PricingStrategy::MarginalCost) {
- let marginal_cost_prices = calculate_marginal_cost_prices(
- solution.iter_activity_keys_for_existing(),
- solution.iter_activity_keys_for_candidates(),
- &result,
- year,
- marginal_set,
- );
- result.extend(marginal_cost_prices);
- }
+ // Add prices for marginal cost commodities
+ if let Some(marginal_set) = pricing_sets.get(&PricingStrategy::MarginalCost) {
+ let marginal_cost_prices = calculate_marginal_cost_prices(
+ solution.iter_activity_for_existing(),
+ solution.iter_activity_for_candidates(),
+ &shadow_prices,
+ year,
+ marginal_set,
+ );
+ result.extend(marginal_cost_prices);
+ }
- // Add prices for full cost commodities
- if let Some(fullcost_set) = pricing_sets.get(&PricingStrategy::FullCost) {
- let annual_activities =
- calculate_annual_activities(solution.iter_activity_for_existing());
- let full_cost_prices = calculate_full_cost_prices(
- solution.iter_activity_keys_for_existing(),
- solution.iter_activity_keys_for_candidates(),
- &annual_activities,
- &result,
- year,
- fullcost_set,
- );
- result.extend(full_cost_prices);
- }
+ // Add prices for full cost commodities
+ if let Some(fullcost_set) = pricing_sets.get(&PricingStrategy::FullCost) {
+ let annual_activities = calculate_annual_activities(solution.iter_activity_for_existing());
+ let full_cost_prices = calculate_full_cost_prices(
+ solution.iter_activity_for_existing(),
+ solution.iter_activity_for_candidates(),
+ &annual_activities,
+ &shadow_prices,
+ year,
+ fullcost_set,
+ );
+ result.extend(full_cost_prices);
}
// Return the completed prices map
@@ -274,12 +270,12 @@ fn calculate_scarcity_adjusted_prices<'a, I>(
activity_duals: I,
shadow_prices: &CommodityPrices,
markets_to_price: &HashSet<(CommodityID, RegionID)>,
-) -> HashMap<(CommodityID, RegionID, TimeSliceID), MoneyPerFlow>
+) -> IndexMap<(CommodityID, RegionID, TimeSliceID), MoneyPerFlow>
where
I: Iterator
- ,
{
// Calculate highest activity dual for each commodity/region/time slice
- let mut highest_duals = HashMap::new();
+ let mut highest_duals = IndexMap::new();
for (asset, time_slice, dual) in activity_duals {
let region_id = asset.region_id();
@@ -305,7 +301,7 @@ where
}
// Add this to the shadow price for each commodity/region/time slice
- let mut scarcity_prices = HashMap::new();
+ let mut scarcity_prices = IndexMap::new();
for ((commodity, region, time_slice), highest_dual) in &highest_duals {
// There should always be a shadow price for commodities we are considering here, so it
// should be safe to unwrap
@@ -349,12 +345,13 @@ where
///
/// ---
///
-/// For each region, the price in each time slice is taken from the installed asset with the highest
-/// marginal cost. If there are no producers of the commodity in that region (in particular, this
-/// may occur when there's no demand for the commodity), then candidate assets are considered: we
-/// take the price from the candidate asset with the _lowest_ marginal cost, assuming full
-/// utilisation (i.e. the single candidate asset that would be most competitive if a small amount of
-/// demand was added).
+/// If any existing assets produce a given commodity in a particular region and time slice, the
+/// price is taken from the asset with the highest marginal cost among those existing assets. If _no_
+/// existing assets produce the commodity in that region and time slice (in particular, this will
+/// occur when there's no demand for the commodity), then candidate assets are considered: we
+/// take the price from the candidate asset with the _lowest_ marginal cost, assuming full utilisation
+/// (i.e. the single candidate asset that would be most competitive if a small amount of demand was
+/// added).
///
/// Note: this should be similar to the "shadow price" strategy, which is also based on marginal
/// costs of the most expensive producer, but may be more successful in cases where there are
@@ -362,40 +359,47 @@ where
///
/// # Arguments
///
-/// * `activity_keys_for_existing` - Iterator over activity keys from optimisation solution for
-/// existing assets
-/// * `activity_keys_for_candidates` - Iterator over activity keys from optimisation solution for
-/// candidate assets
-/// * `upstream_prices` - Prices for commodities upstream of the ones we are calculating prices for
+/// * `activity_for_existing` - Iterator over activity from optimisation solution for existing
+/// assets
+/// * `activity_for_candidates` - Iterator over activity from optimisation solution for candidate
+/// assets. Note: we only need the keys, since we assume full utilisation for candidates.
+/// * `annual_activities` - Map of annual activities for each asset computed by
+/// `calculate_annual_activities`. This only needs to include existing assets.
+/// * `shadow_prices` - Shadow prices for all commodities
/// * `year` - The year for which prices are being calculated
-/// * `markets_to_price` - Set of markets to calculate marginal prices for
+/// * `markets_to_price` - Set of markets to calculate full cost prices for
///
/// # Returns
///
/// A map of marginal cost prices for the specified markets in all time slices
fn calculate_marginal_cost_prices<'a, I, J>(
- activity_keys_for_existing: I,
- activity_keys_for_candidates: J,
- upstream_prices: &CommodityPrices,
+ activity_for_existing: I,
+ activity_for_candidates: J,
+ shadow_prices: &CommodityPrices,
year: u32,
markets_to_price: &HashSet<(CommodityID, RegionID)>,
-) -> HashMap<(CommodityID, RegionID, TimeSliceID), MoneyPerFlow>
+) -> IndexMap<(CommodityID, RegionID, TimeSliceID), MoneyPerFlow>
where
- I: Iterator
- ,
- J: Iterator
- ,
+ I: Iterator
- >,
+ J: Iterator
- >,
{
- let mut prices: HashMap<(CommodityID, RegionID, TimeSliceID), MoneyPerFlow> = HashMap::new();
+ let mut prices: IndexMap<(CommodityID, RegionID, TimeSliceID), MoneyPerFlow> = IndexMap::new();
// Start by looking at existing assets
// Calculate highest marginal cost for each commodity/region/time slice
// Keep track of keys with prices - missing keys will be handled by candidates later
let mut priced_by_existing = HashSet::new();
- for (asset, time_slice) in activity_keys_for_existing {
+ for (asset, time_slice, activity) in activity_for_existing {
let region_id = asset.region_id();
+ // Only proceed if the asset has non-zero activity in this time slice
+ if activity < Activity::EPSILON {
+ continue;
+ }
+
// Iterate over all the SED/SVD marginal costs for commodities we need prices for
for (commodity_id, marginal_cost) in asset.iter_marginal_costs_with_filter(
- upstream_prices,
+ shadow_prices,
year,
time_slice,
|commodity_id: &CommodityID| {
@@ -414,7 +418,7 @@ where
// Next, look at candidate assets for any markets not covered by existing assets
// For these, we take the _lowest_ marginal cost
- for (asset, time_slice) in activity_keys_for_candidates {
+ for (asset, time_slice, _activity) in activity_for_candidates {
let region_id = asset.region_id();
// Only consider markets not already priced by existing assets
@@ -429,7 +433,7 @@ where
// Iterate over all the SED/SVD marginal costs for markets we need prices for
for (commodity_id, marginal_cost) in asset.iter_marginal_costs_with_filter(
- upstream_prices,
+ shadow_prices,
year,
time_slice,
|cid: &CommodityID| should_process(cid),
@@ -447,10 +451,10 @@ where
prices
}
-/// Calculate annual activities for each asset by summing across all time slices
+/// Calculated annual activities for each asset by summing across all time slices
fn calculate_annual_activities<'a, I>(activities: I) -> HashMap
where
- I: IntoIterator
- ,
+ I: IntoIterator
- >,
{
activities
.into_iter()
@@ -496,23 +500,23 @@ where
///
/// ---
///
-/// For each region, the price in each time slice is taken from the installed asset with the highest
-/// full cost (excluding assets with zero annual activity, as the full cost of these as calculated
-/// above would be infinite). If there are no producers of the commodity in that region (in
-/// particular, this may occur when there's no demand for the commodity), then candidate assets are
-/// considered: we take the price from the candidate asset with the _lowest_ full cost, assuming
-/// maximum possible dispatch (i.e. the single candidate asset that would be most competitive if a
-/// small amount of demand was added).
+/// If any existing assets produce a given commodity in a particular region and time slice, the
+/// price is taken from the asset with the highest full cost among those existing assets. If _no_
+/// existing assets produce the commodity in that region and time slice (in particular, this will
+/// occur when there's no demand for the commodity), then candidate assets are considered: we
+/// take the price from the candidate asset with the _lowest_ full cost, assuming maximum
+/// possible dispatch (i.e. the single candidate asset that would be most competitive if a small
+/// amount of demand was added).
///
/// # Arguments
///
-/// * `activity_keys_for_existing` - Iterator over activity keys from optimisation solution for
-/// existing assets
-/// * `activity_keys_for_candidates` - Iterator over activity keys from optimisation solution for
-/// candidate assets
+/// * `activity_for_existing` - Iterator over activity from optimisation solution for existing
+/// assets
+/// * `activity_for_candidates` - Iterator over activity from optimisation solution for candidate
+/// assets. Note: we only need the keys, since we assume full dispatch for candidates.
/// * `annual_activities` - Map of annual activities for each asset computed by
/// `calculate_annual_activities`. This only needs to include existing assets.
-/// * `upstream_prices` - Prices for commodities upstream of the ones we are calculating prices for
+/// * `shadow_prices` - Shadow prices for all commodities
/// * `year` - The year for which prices are being calculated
/// * `markets_to_price` - Set of markets to calculate full cost prices for
///
@@ -520,31 +524,30 @@ where
///
/// A map of full cost prices for the specified markets in all time slices
fn calculate_full_cost_prices<'a, I, J>(
- activity_keys_for_existing: I,
- activity_keys_for_candidates: J,
+ activity_for_existing: I,
+ activity_for_candidates: J,
annual_activities: &HashMap,
- upstream_prices: &CommodityPrices,
+ shadow_prices: &CommodityPrices,
year: u32,
markets_to_price: &HashSet<(CommodityID, RegionID)>,
-) -> HashMap<(CommodityID, RegionID, TimeSliceID), MoneyPerFlow>
+) -> IndexMap<(CommodityID, RegionID, TimeSliceID), MoneyPerFlow>
where
- I: Iterator
- ,
- J: Iterator
- ,
+ I: Iterator
- >,
+ J: Iterator
- >,
{
- let mut prices: HashMap<(CommodityID, RegionID, TimeSliceID), MoneyPerFlow> = HashMap::new();
+ let mut prices: IndexMap<(CommodityID, RegionID, TimeSliceID), MoneyPerFlow> = IndexMap::new();
// Start by looking at existing assets
// Calculate highest full cost for each commodity/region/time slice
// Keep track of keys with prices - missing keys will be handled by candidates later
- let mut annual_fixed_costs_cache = HashMap::new();
+ let mut annual_capital_costs_cache = HashMap::new();
let mut priced_by_existing = HashSet::new();
- for (asset, time_slice) in activity_keys_for_existing {
+ for (asset, time_slice, activity) in activity_for_existing {
let annual_activity = annual_activities[asset];
let region_id = asset.region_id();
- // If annual activity is zero, we can't calculate a capital cost per flow, so skip this
- // asset.
- if annual_activity < Activity::EPSILON {
+ // Only proceed if the asset has non-zero activity in this time slice
+ if activity < Activity::EPSILON {
continue;
}
@@ -556,20 +559,20 @@ where
continue;
}
- // Calculate/cache annual fixed costs for this asset
- let annual_fixed_costs_per_flow = *annual_fixed_costs_cache
+ // Calculate/cache annual capital cost for this asset
+ let annual_capital_cost_per_flow = *annual_capital_costs_cache
.entry(asset.clone())
- .or_insert_with(|| asset.get_annual_fixed_costs_per_flow(annual_activity));
+ .or_insert_with(|| asset.get_annual_capital_cost_per_flow(annual_activity));
// Iterate over all the SED/SVD marginal costs for commodities we need prices for
for (commodity_id, marginal_cost) in asset.iter_marginal_costs_with_filter(
- upstream_prices,
+ shadow_prices,
year,
time_slice,
|cid: &CommodityID| markets_to_price.contains(&(cid.clone(), region_id.clone())),
) {
- // Add annual fixed costs per flow to marginal cost to get full cost
- let marginal_cost = marginal_cost + annual_fixed_costs_per_flow;
+ // Add capital cost per flow to marginal cost to get full cost
+ let marginal_cost = marginal_cost + annual_capital_cost_per_flow;
// Update the highest cost for this commodity/region/time slice
let key = (commodity_id.clone(), region_id.clone(), time_slice.clone());
@@ -583,7 +586,7 @@ where
// Next, look at candidate assets for any markets not covered by existing assets
// For these we assume full utilisation, and take the _lowest_ full cost
- for (asset, time_slice) in activity_keys_for_candidates {
+ for (asset, time_slice, _activity) in activity_for_candidates {
let region_id = asset.region_id();
// Only consider markets not already priced by existing assets
@@ -604,12 +607,12 @@ where
continue;
}
- // Calculate/cache annual fixed cost per flow for this asset assuming full dispatch
+ // Calculate/cache annual capital cost per flow for this asset assuming full dispatch
// (bound by the activity limits of the asset)
- let annual_fixed_costs_per_flow = *annual_fixed_costs_cache
+ let annual_capital_cost_per_flow = *annual_capital_costs_cache
.entry(asset.clone())
.or_insert_with(|| {
- asset.get_annual_fixed_costs_per_flow(
+ asset.get_annual_capital_cost_per_flow(
*asset
.get_activity_limits_for_selection(&TimeSliceSelection::Annual)
.end(),
@@ -618,13 +621,13 @@ where
// Iterate over all the SED/SVD marginal costs for markets we need prices for
for (commodity_id, marginal_cost) in asset.iter_marginal_costs_with_filter(
- upstream_prices,
+ shadow_prices,
year,
time_slice,
|cid: &CommodityID| should_process(cid),
) {
- // Add annual fixed costs per flow to marginal cost to get full cost
- let full_cost = marginal_cost + annual_fixed_costs_per_flow;
+ // Add capital cost per flow to marginal cost to get full cost
+ let full_cost = marginal_cost + annual_capital_cost_per_flow;
// Update the _lowest_ cost for this commodity/region/time slice
let key = (commodity_id.clone(), region_id.clone(), time_slice.clone());
@@ -677,7 +680,6 @@ mod tests {
year: u32,
time_slice_info: &TimeSliceInfo,
variable_operating_cost: MoneyPerActivity,
- fixed_operating_cost: MoneyPerCapacityPerYear,
capital_cost: MoneyPerCapacity,
lifetime: u32,
discount_rate: Dimensionless,
@@ -688,7 +690,7 @@ mod tests {
let mut process_parameter_map = HashMap::new();
let proc_param = ProcessParameter {
capital_cost,
- fixed_operating_cost,
+ fixed_operating_cost: MoneyPerCapacityPerYear(0.0),
variable_operating_cost,
lifetime,
discount_rate,
@@ -719,7 +721,7 @@ mod tests {
}
fn assert_price_approx(
- prices: &HashMap<(CommodityID, RegionID, TimeSliceID), MoneyPerFlow>,
+ prices: &IndexMap<(CommodityID, RegionID, TimeSliceID), MoneyPerFlow>,
commodity: &CommodityID,
region: &RegionID,
time_slice: &TimeSliceID,
@@ -813,11 +815,10 @@ mod tests {
®ion_id,
2015u32,
&time_slice_info,
- MoneyPerActivity(5.0), // variable operating cost
- MoneyPerCapacityPerYear(0.0), // fixed operating cost
- MoneyPerCapacity(0.0), // capital cost
- 5, // lifetime
- Dimensionless(1.0), // discount rate
+ MoneyPerActivity(5.0), // variable operating cost
+ MoneyPerCapacity(0.0), // capital cost
+ 5, // lifetime
+ Dimensionless(1.0), // discount rate
);
let asset =
@@ -830,7 +831,7 @@ mod tests {
markets.insert((b.id.clone(), region_id.clone()));
markets.insert((c.id.clone(), region_id.clone()));
- let existing = vec![(&asset_ref, &time_slice)];
+ let existing = vec![(&asset_ref, &time_slice, Activity(1.0))];
let candidates = Vec::new();
let prices = calculate_marginal_cost_prices(
@@ -889,11 +890,10 @@ mod tests {
®ion_id,
2015u32,
&time_slice_info,
- MoneyPerActivity(5.0), // variable operating cost
- MoneyPerCapacityPerYear(1.0), // fixed operating cost
- MoneyPerCapacity(1.5), // capital cost per capacity so annualised=1.5
- 1, // lifetime so annualised = capital_cost
- Dimensionless(0.0), // discount rate
+ MoneyPerActivity(5.0), // variable operating cost
+ MoneyPerCapacity(2.5), // capital cost per capacity so annualised=2.5
+ 1, // lifetime so annualised = capital_cost
+ Dimensionless(0.0), // discount rate
);
let asset =
@@ -906,7 +906,7 @@ mod tests {
markets.insert((b.id.clone(), region_id.clone()));
markets.insert((c.id.clone(), region_id.clone()));
- let existing = vec![(&asset_ref, &time_slice)];
+ let existing = vec![(&asset_ref, &time_slice, Activity(2.0))];
let candidates = Vec::new();
let mut annual_activities = HashMap::new();
diff --git a/tests/data/circularity/commodity_prices.csv b/tests/data/circularity/commodity_prices.csv
index d8042ea00..763694956 100644
--- a/tests/data/circularity/commodity_prices.csv
+++ b/tests/data/circularity/commodity_prices.csv
@@ -31,22 +31,6 @@ milestone_year,commodity_id,region_id,time_slice,price
2020,GASPRD,GBR,autumn.day,2.20452
2020,GASPRD,GBR,autumn.peak,2.20452
2020,GASPRD,GBR,autumn.evening,2.20452
-2020,BIOPRD,GBR,winter.night,3.5869398412698414
-2020,BIOPRD,GBR,winter.day,3.5869398412698414
-2020,BIOPRD,GBR,winter.peak,3.5869398412698414
-2020,BIOPRD,GBR,winter.evening,3.5869398412698414
-2020,BIOPRD,GBR,peak.night,3.5869398412698414
-2020,BIOPRD,GBR,peak.day,3.5869398412698414
-2020,BIOPRD,GBR,peak.peak,3.5869398412698414
-2020,BIOPRD,GBR,peak.evening,3.5869398412698414
-2020,BIOPRD,GBR,summer.night,0.25
-2020,BIOPRD,GBR,summer.day,0.25
-2020,BIOPRD,GBR,summer.peak,0.25
-2020,BIOPRD,GBR,summer.evening,0.25
-2020,BIOPRD,GBR,autumn.night,3.586939841269842
-2020,BIOPRD,GBR,autumn.day,3.586939841269842
-2020,BIOPRD,GBR,autumn.peak,3.586939841269842
-2020,BIOPRD,GBR,autumn.evening,3.586939841269842
2020,GASOLI,GBR,winter.night,8.66986824186952
2020,GASOLI,GBR,winter.day,8.66986824186952
2020,GASOLI,GBR,winter.peak,8.66986824186952
@@ -95,22 +79,6 @@ milestone_year,commodity_id,region_id,time_slice,price
2020,GASNAT,GBR,autumn.day,2.9170059999999998
2020,GASNAT,GBR,autumn.peak,2.9170059999999998
2020,GASNAT,GBR,autumn.evening,2.9170059999999998
-2020,BIOPEL,GBR,winter.night,4.7221140833333335
-2020,BIOPEL,GBR,winter.day,4.7221140833333335
-2020,BIOPEL,GBR,winter.peak,4.7221140833333335
-2020,BIOPEL,GBR,winter.evening,4.7221140833333335
-2020,BIOPEL,GBR,peak.night,4.7221140833333335
-2020,BIOPEL,GBR,peak.day,4.7221140833333335
-2020,BIOPEL,GBR,peak.peak,4.7221140833333335
-2020,BIOPEL,GBR,peak.evening,4.7221140833333335
-2020,BIOPEL,GBR,summer.night,1.21832725
-2020,BIOPEL,GBR,summer.day,1.21832725
-2020,BIOPEL,GBR,summer.peak,1.21832725
-2020,BIOPEL,GBR,summer.evening,1.21832725
-2020,BIOPEL,GBR,autumn.night,4.7221140833333335
-2020,BIOPEL,GBR,autumn.day,4.7221140833333335
-2020,BIOPEL,GBR,autumn.peak,4.7221140833333335
-2020,BIOPEL,GBR,autumn.evening,4.7221140833333335
2020,ELCTRI,GBR,winter.night,7.993308999999999
2020,ELCTRI,GBR,winter.day,17.26223303030303
2020,ELCTRI,GBR,winter.peak,17.482621345499997
@@ -175,6 +143,38 @@ milestone_year,commodity_id,region_id,time_slice,price
2020,RSHEAT,GBR,autumn.day,5.8665369
2020,RSHEAT,GBR,autumn.peak,5.8665369
2020,RSHEAT,GBR,autumn.evening,5.8665369
+2020,BIOPRD,GBR,winter.night,3.5869398412698414
+2020,BIOPRD,GBR,winter.day,3.5869398412698414
+2020,BIOPRD,GBR,winter.peak,3.5869398412698414
+2020,BIOPRD,GBR,winter.evening,3.5869398412698414
+2020,BIOPRD,GBR,peak.night,3.5869398412698414
+2020,BIOPRD,GBR,peak.day,3.5869398412698414
+2020,BIOPRD,GBR,peak.peak,3.5869398412698414
+2020,BIOPRD,GBR,peak.evening,3.5869398412698414
+2020,BIOPRD,GBR,summer.night,0.25
+2020,BIOPRD,GBR,summer.day,0.25
+2020,BIOPRD,GBR,summer.peak,0.25
+2020,BIOPRD,GBR,summer.evening,0.25
+2020,BIOPRD,GBR,autumn.night,3.586939841269842
+2020,BIOPRD,GBR,autumn.day,3.586939841269842
+2020,BIOPRD,GBR,autumn.peak,3.586939841269842
+2020,BIOPRD,GBR,autumn.evening,3.586939841269842
+2020,BIOPEL,GBR,winter.night,4.7221140833333335
+2020,BIOPEL,GBR,winter.day,4.7221140833333335
+2020,BIOPEL,GBR,winter.peak,4.7221140833333335
+2020,BIOPEL,GBR,winter.evening,4.7221140833333335
+2020,BIOPEL,GBR,peak.night,4.7221140833333335
+2020,BIOPEL,GBR,peak.day,4.7221140833333335
+2020,BIOPEL,GBR,peak.peak,4.7221140833333335
+2020,BIOPEL,GBR,peak.evening,4.7221140833333335
+2020,BIOPEL,GBR,summer.night,1.21832725
+2020,BIOPEL,GBR,summer.day,1.21832725
+2020,BIOPEL,GBR,summer.peak,1.21832725
+2020,BIOPEL,GBR,summer.evening,1.21832725
+2020,BIOPEL,GBR,autumn.night,4.7221140833333335
+2020,BIOPEL,GBR,autumn.day,4.7221140833333335
+2020,BIOPEL,GBR,autumn.peak,4.7221140833333335
+2020,BIOPEL,GBR,autumn.evening,4.7221140833333335
2030,OILCRD,GBR,winter.night,3.072868
2030,OILCRD,GBR,winter.day,3.072868
2030,OILCRD,GBR,winter.peak,3.072868
@@ -207,22 +207,6 @@ milestone_year,commodity_id,region_id,time_slice,price
2030,GASPRD,GBR,autumn.day,2.20452
2030,GASPRD,GBR,autumn.peak,2.20452
2030,GASPRD,GBR,autumn.evening,2.20452
-2030,BIOPRD,GBR,winter.night,4.249632460317461
-2030,BIOPRD,GBR,winter.day,4.249632460317461
-2030,BIOPRD,GBR,winter.peak,4.249632460317461
-2030,BIOPRD,GBR,winter.evening,4.249632460317461
-2030,BIOPRD,GBR,peak.night,1.822057119047619
-2030,BIOPRD,GBR,peak.day,1.822057119047619
-2030,BIOPRD,GBR,peak.peak,1.822057119047619
-2030,BIOPRD,GBR,peak.evening,1.822057119047619
-2030,BIOPRD,GBR,summer.night,0.25
-2030,BIOPRD,GBR,summer.day,0.25
-2030,BIOPRD,GBR,summer.peak,0.25
-2030,BIOPRD,GBR,summer.evening,0.25
-2030,BIOPRD,GBR,autumn.night,1.822057119047619
-2030,BIOPRD,GBR,autumn.day,1.822057119047619
-2030,BIOPRD,GBR,autumn.peak,1.822057119047619
-2030,BIOPRD,GBR,autumn.evening,1.822057119047619
2030,GASOLI,GBR,winter.night,5.585457080000001
2030,GASOLI,GBR,winter.day,5.585457080000001
2030,GASOLI,GBR,winter.peak,5.585457080000001
@@ -271,22 +255,6 @@ milestone_year,commodity_id,region_id,time_slice,price
2030,GASNAT,GBR,autumn.day,2.9170059999999998
2030,GASNAT,GBR,autumn.peak,2.9170059999999998
2030,GASNAT,GBR,autumn.evening,2.9170059999999998
-2030,BIOPEL,GBR,winter.night,4.7221140833333335
-2030,BIOPEL,GBR,winter.day,4.7221140833333335
-2030,BIOPEL,GBR,winter.peak,4.7221140833333335
-2030,BIOPEL,GBR,winter.evening,4.7221140833333335
-2030,BIOPEL,GBR,peak.night,2.173159975
-2030,BIOPEL,GBR,peak.day,2.173159975
-2030,BIOPEL,GBR,peak.peak,2.173159975
-2030,BIOPEL,GBR,peak.evening,2.173159975
-2030,BIOPEL,GBR,summer.night,0.5225
-2030,BIOPEL,GBR,summer.day,0.5225
-2030,BIOPEL,GBR,summer.peak,0.5225
-2030,BIOPEL,GBR,summer.evening,0.5225
-2030,BIOPEL,GBR,autumn.night,2.173159975
-2030,BIOPEL,GBR,autumn.day,2.173159975
-2030,BIOPEL,GBR,autumn.peak,2.173159975
-2030,BIOPEL,GBR,autumn.evening,2.173159975
2030,ELCTRI,GBR,winter.night,7.993308999999999
2030,ELCTRI,GBR,winter.day,17.26223303030303
2030,ELCTRI,GBR,winter.peak,17.482621345499997
@@ -351,6 +319,38 @@ milestone_year,commodity_id,region_id,time_slice,price
2030,RSHEAT,GBR,autumn.day,5.8665369
2030,RSHEAT,GBR,autumn.peak,5.8665369
2030,RSHEAT,GBR,autumn.evening,5.8665369
+2030,BIOPRD,GBR,winter.night,4.249632460317461
+2030,BIOPRD,GBR,winter.day,4.249632460317461
+2030,BIOPRD,GBR,winter.peak,4.249632460317461
+2030,BIOPRD,GBR,winter.evening,4.249632460317461
+2030,BIOPRD,GBR,peak.night,1.822057119047619
+2030,BIOPRD,GBR,peak.day,1.822057119047619
+2030,BIOPRD,GBR,peak.peak,1.822057119047619
+2030,BIOPRD,GBR,peak.evening,1.822057119047619
+2030,BIOPRD,GBR,summer.night,0.25
+2030,BIOPRD,GBR,summer.day,0.25
+2030,BIOPRD,GBR,summer.peak,0.25
+2030,BIOPRD,GBR,summer.evening,0.25
+2030,BIOPRD,GBR,autumn.night,1.822057119047619
+2030,BIOPRD,GBR,autumn.day,1.822057119047619
+2030,BIOPRD,GBR,autumn.peak,1.822057119047619
+2030,BIOPRD,GBR,autumn.evening,1.822057119047619
+2030,BIOPEL,GBR,winter.night,4.7221140833333335
+2030,BIOPEL,GBR,winter.day,4.7221140833333335
+2030,BIOPEL,GBR,winter.peak,4.7221140833333335
+2030,BIOPEL,GBR,winter.evening,4.7221140833333335
+2030,BIOPEL,GBR,peak.night,2.173159975
+2030,BIOPEL,GBR,peak.day,2.173159975
+2030,BIOPEL,GBR,peak.peak,2.173159975
+2030,BIOPEL,GBR,peak.evening,2.173159975
+2030,BIOPEL,GBR,summer.night,0.5225
+2030,BIOPEL,GBR,summer.day,0.5225
+2030,BIOPEL,GBR,summer.peak,0.5225
+2030,BIOPEL,GBR,summer.evening,0.5225
+2030,BIOPEL,GBR,autumn.night,2.173159975
+2030,BIOPEL,GBR,autumn.day,2.173159975
+2030,BIOPEL,GBR,autumn.peak,2.173159975
+2030,BIOPEL,GBR,autumn.evening,2.173159975
2040,OILCRD,GBR,winter.night,3.072868
2040,OILCRD,GBR,winter.day,3.072868
2040,OILCRD,GBR,winter.peak,3.072868
@@ -383,22 +383,6 @@ milestone_year,commodity_id,region_id,time_slice,price
2040,GASPRD,GBR,autumn.day,2.20452
2040,GASPRD,GBR,autumn.peak,2.20452
2040,GASPRD,GBR,autumn.evening,2.20452
-2040,BIOPRD,GBR,winter.night,0.25
-2040,BIOPRD,GBR,winter.day,0.25
-2040,BIOPRD,GBR,winter.peak,0.25
-2040,BIOPRD,GBR,winter.evening,0.25
-2040,BIOPRD,GBR,peak.night,0.25
-2040,BIOPRD,GBR,peak.day,0.25
-2040,BIOPRD,GBR,peak.peak,0.25
-2040,BIOPRD,GBR,peak.evening,0.25
-2040,BIOPRD,GBR,summer.night,0.25
-2040,BIOPRD,GBR,summer.day,0.25
-2040,BIOPRD,GBR,summer.peak,0.25
-2040,BIOPRD,GBR,summer.evening,0.25
-2040,BIOPRD,GBR,autumn.night,0.25
-2040,BIOPRD,GBR,autumn.day,0.25
-2040,BIOPRD,GBR,autumn.peak,0.25
-2040,BIOPRD,GBR,autumn.evening,0.25
2040,GASOLI,GBR,winter.night,5.585457080000001
2040,GASOLI,GBR,winter.day,5.585457080000001
2040,GASOLI,GBR,winter.peak,5.585457080000001
@@ -431,22 +415,6 @@ milestone_year,commodity_id,region_id,time_slice,price
2040,GASNAT,GBR,autumn.day,2.9170059999999998
2040,GASNAT,GBR,autumn.peak,2.9170059999999998
2040,GASNAT,GBR,autumn.evening,2.9170059999999998
-2040,BIOPEL,GBR,winter.night,0.5225
-2040,BIOPEL,GBR,winter.day,0.5225
-2040,BIOPEL,GBR,winter.peak,0.5225
-2040,BIOPEL,GBR,winter.evening,0.5225
-2040,BIOPEL,GBR,peak.night,0.5225
-2040,BIOPEL,GBR,peak.day,0.5225
-2040,BIOPEL,GBR,peak.peak,0.5225
-2040,BIOPEL,GBR,peak.evening,0.5225
-2040,BIOPEL,GBR,summer.night,0.5225
-2040,BIOPEL,GBR,summer.day,0.5225
-2040,BIOPEL,GBR,summer.peak,0.5225
-2040,BIOPEL,GBR,summer.evening,0.5225
-2040,BIOPEL,GBR,autumn.night,0.5225
-2040,BIOPEL,GBR,autumn.day,0.5225
-2040,BIOPEL,GBR,autumn.peak,0.5225
-2040,BIOPEL,GBR,autumn.evening,0.5225
2040,ELCTRI,GBR,winter.night,7.993308999999999
2040,ELCTRI,GBR,winter.day,7.993308999999999
2040,ELCTRI,GBR,winter.peak,7.993308999999999
@@ -511,3 +479,35 @@ milestone_year,commodity_id,region_id,time_slice,price
2040,RSHEAT,GBR,autumn.day,0.827
2040,RSHEAT,GBR,autumn.peak,0.827
2040,RSHEAT,GBR,autumn.evening,0.827
+2040,BIOPRD,GBR,winter.night,0.25
+2040,BIOPRD,GBR,winter.day,0.25
+2040,BIOPRD,GBR,winter.peak,0.25
+2040,BIOPRD,GBR,winter.evening,0.25
+2040,BIOPRD,GBR,peak.night,0.25
+2040,BIOPRD,GBR,peak.day,0.25
+2040,BIOPRD,GBR,peak.peak,0.25
+2040,BIOPRD,GBR,peak.evening,0.25
+2040,BIOPRD,GBR,summer.night,0.25
+2040,BIOPRD,GBR,summer.day,0.25
+2040,BIOPRD,GBR,summer.peak,0.25
+2040,BIOPRD,GBR,summer.evening,0.25
+2040,BIOPRD,GBR,autumn.night,0.25
+2040,BIOPRD,GBR,autumn.day,0.25
+2040,BIOPRD,GBR,autumn.peak,0.25
+2040,BIOPRD,GBR,autumn.evening,0.25
+2040,BIOPEL,GBR,winter.night,0.5225
+2040,BIOPEL,GBR,winter.day,0.5225
+2040,BIOPEL,GBR,winter.peak,0.5225
+2040,BIOPEL,GBR,winter.evening,0.5225
+2040,BIOPEL,GBR,peak.night,0.5225
+2040,BIOPEL,GBR,peak.day,0.5225
+2040,BIOPEL,GBR,peak.peak,0.5225
+2040,BIOPEL,GBR,peak.evening,0.5225
+2040,BIOPEL,GBR,summer.night,0.5225
+2040,BIOPEL,GBR,summer.day,0.5225
+2040,BIOPEL,GBR,summer.peak,0.5225
+2040,BIOPEL,GBR,summer.evening,0.5225
+2040,BIOPEL,GBR,autumn.night,0.5225
+2040,BIOPEL,GBR,autumn.day,0.5225
+2040,BIOPEL,GBR,autumn.peak,0.5225
+2040,BIOPEL,GBR,autumn.evening,0.5225
diff --git a/tests/data/missing_commodity/commodity_prices.csv b/tests/data/missing_commodity/commodity_prices.csv
index 55a52dd42..8bafd7e4b 100644
--- a/tests/data/missing_commodity/commodity_prices.csv
+++ b/tests/data/missing_commodity/commodity_prices.csv
@@ -15,22 +15,6 @@ milestone_year,commodity_id,region_id,time_slice,price
2020,GASPRD,GBR,autumn.day,2.20452
2020,GASPRD,GBR,autumn.peak,2.20452
2020,GASPRD,GBR,autumn.evening,2.20452
-2020,BIOPRD,GBR,winter.night,4.249632460317461
-2020,BIOPRD,GBR,winter.day,4.249632460317461
-2020,BIOPRD,GBR,winter.peak,4.249632460317461
-2020,BIOPRD,GBR,winter.evening,4.249632460317461
-2020,BIOPRD,GBR,peak.night,4.249632460317461
-2020,BIOPRD,GBR,peak.day,4.249632460317461
-2020,BIOPRD,GBR,peak.peak,4.249632460317461
-2020,BIOPRD,GBR,peak.evening,4.249632460317461
-2020,BIOPRD,GBR,summer.night,0.25
-2020,BIOPRD,GBR,summer.day,0.25
-2020,BIOPRD,GBR,summer.peak,0.25
-2020,BIOPRD,GBR,summer.evening,0.25
-2020,BIOPRD,GBR,autumn.night,4.249632460317461
-2020,BIOPRD,GBR,autumn.day,4.249632460317461
-2020,BIOPRD,GBR,autumn.peak,4.249632460317461
-2020,BIOPRD,GBR,autumn.evening,4.249632460317461
2020,GASNAT,GBR,winter.night,2.9170059999999998
2020,GASNAT,GBR,winter.day,2.9170059999999998
2020,GASNAT,GBR,winter.peak,2.9170059999999998
@@ -47,6 +31,22 @@ milestone_year,commodity_id,region_id,time_slice,price
2020,GASNAT,GBR,autumn.day,2.9170059999999998
2020,GASNAT,GBR,autumn.peak,2.9170059999999998
2020,GASNAT,GBR,autumn.evening,2.9170059999999998
+2020,BIOPRD,GBR,winter.night,4.249632460317461
+2020,BIOPRD,GBR,winter.day,4.249632460317461
+2020,BIOPRD,GBR,winter.peak,4.249632460317461
+2020,BIOPRD,GBR,winter.evening,4.249632460317461
+2020,BIOPRD,GBR,peak.night,4.249632460317461
+2020,BIOPRD,GBR,peak.day,4.249632460317461
+2020,BIOPRD,GBR,peak.peak,4.249632460317461
+2020,BIOPRD,GBR,peak.evening,4.249632460317461
+2020,BIOPRD,GBR,summer.night,0.25
+2020,BIOPRD,GBR,summer.day,0.25
+2020,BIOPRD,GBR,summer.peak,0.25
+2020,BIOPRD,GBR,summer.evening,0.25
+2020,BIOPRD,GBR,autumn.night,4.249632460317461
+2020,BIOPRD,GBR,autumn.day,4.249632460317461
+2020,BIOPRD,GBR,autumn.peak,4.249632460317461
+2020,BIOPRD,GBR,autumn.evening,4.249632460317461
2020,BIOPEL,GBR,winter.night,4.7221140833333335
2020,BIOPEL,GBR,winter.day,4.7221140833333335
2020,BIOPEL,GBR,winter.peak,4.7221140833333335
@@ -111,22 +111,6 @@ milestone_year,commodity_id,region_id,time_slice,price
2030,GASPRD,GBR,autumn.day,2.20452
2030,GASPRD,GBR,autumn.peak,2.20452
2030,GASPRD,GBR,autumn.evening,2.20452
-2030,BIOPRD,GBR,winter.night,4.249632460317461
-2030,BIOPRD,GBR,winter.day,4.249632460317461
-2030,BIOPRD,GBR,winter.peak,4.249632460317461
-2030,BIOPRD,GBR,winter.evening,4.249632460317461
-2030,BIOPRD,GBR,peak.night,1.822057119047619
-2030,BIOPRD,GBR,peak.day,1.822057119047619
-2030,BIOPRD,GBR,peak.peak,1.822057119047619
-2030,BIOPRD,GBR,peak.evening,1.822057119047619
-2030,BIOPRD,GBR,summer.night,0.25
-2030,BIOPRD,GBR,summer.day,0.25
-2030,BIOPRD,GBR,summer.peak,0.25
-2030,BIOPRD,GBR,summer.evening,0.25
-2030,BIOPRD,GBR,autumn.night,1.822057119047619
-2030,BIOPRD,GBR,autumn.day,1.822057119047619
-2030,BIOPRD,GBR,autumn.peak,1.822057119047619
-2030,BIOPRD,GBR,autumn.evening,1.822057119047619
2030,GASNAT,GBR,winter.night,2.9170059999999998
2030,GASNAT,GBR,winter.day,2.9170059999999998
2030,GASNAT,GBR,winter.peak,2.9170059999999998
@@ -143,6 +127,22 @@ milestone_year,commodity_id,region_id,time_slice,price
2030,GASNAT,GBR,autumn.day,2.9170059999999998
2030,GASNAT,GBR,autumn.peak,2.9170059999999998
2030,GASNAT,GBR,autumn.evening,2.9170059999999998
+2030,BIOPRD,GBR,winter.night,4.249632460317461
+2030,BIOPRD,GBR,winter.day,4.249632460317461
+2030,BIOPRD,GBR,winter.peak,4.249632460317461
+2030,BIOPRD,GBR,winter.evening,4.249632460317461
+2030,BIOPRD,GBR,peak.night,1.822057119047619
+2030,BIOPRD,GBR,peak.day,1.822057119047619
+2030,BIOPRD,GBR,peak.peak,1.822057119047619
+2030,BIOPRD,GBR,peak.evening,1.822057119047619
+2030,BIOPRD,GBR,summer.night,0.25
+2030,BIOPRD,GBR,summer.day,0.25
+2030,BIOPRD,GBR,summer.peak,0.25
+2030,BIOPRD,GBR,summer.evening,0.25
+2030,BIOPRD,GBR,autumn.night,1.822057119047619
+2030,BIOPRD,GBR,autumn.day,1.822057119047619
+2030,BIOPRD,GBR,autumn.peak,1.822057119047619
+2030,BIOPRD,GBR,autumn.evening,1.822057119047619
2030,BIOPEL,GBR,winter.night,4.7221140833333335
2030,BIOPEL,GBR,winter.day,4.7221140833333335
2030,BIOPEL,GBR,winter.peak,4.7221140833333335
diff --git a/tests/data/muse1_default/commodity_prices.csv b/tests/data/muse1_default/commodity_prices.csv
index c476f1f02..097e21a2d 100644
--- a/tests/data/muse1_default/commodity_prices.csv
+++ b/tests/data/muse1_default/commodity_prices.csv
@@ -1,106 +1,106 @@
milestone_year,commodity_id,region_id,time_slice,price
-2020,gas,R1,all-year.night,2.55
-2020,gas,R1,all-year.morning,2.55
-2020,gas,R1,all-year.afternoon,2.55
-2020,gas,R1,all-year.early-peak,2.55
-2020,gas,R1,all-year.late-peak,2.55
-2020,gas,R1,all-year.evening,2.55
2020,electricity,R1,all-year.night,-0.0
2020,electricity,R1,all-year.morning,-0.0
2020,electricity,R1,all-year.afternoon,-0.0
2020,electricity,R1,all-year.early-peak,-0.0
2020,electricity,R1,all-year.late-peak,-0.0
2020,electricity,R1,all-year.evening,-0.0
+2020,gas,R1,all-year.night,2.55
+2020,gas,R1,all-year.morning,2.55
+2020,gas,R1,all-year.afternoon,2.55
+2020,gas,R1,all-year.early-peak,2.55
+2020,gas,R1,all-year.late-peak,2.55
+2020,gas,R1,all-year.evening,2.55
2020,heat,R1,all-year.night,8.3380664049
2020,heat,R1,all-year.morning,8.3380664049
2020,heat,R1,all-year.afternoon,8.3380664049
2020,heat,R1,all-year.early-peak,8.3380664049
2020,heat,R1,all-year.late-peak,8.3380664049
2020,heat,R1,all-year.evening,8.3380664049
-2025,gas,R1,all-year.night,2.55
-2025,gas,R1,all-year.morning,2.55
-2025,gas,R1,all-year.afternoon,2.55
-2025,gas,R1,all-year.early-peak,2.55
-2025,gas,R1,all-year.late-peak,2.55
-2025,gas,R1,all-year.evening,2.55
2025,electricity,R1,all-year.night,15.26529810765
2025,electricity,R1,all-year.morning,15.26529810765
2025,electricity,R1,all-year.afternoon,15.26529810765
2025,electricity,R1,all-year.early-peak,15.26529810765
2025,electricity,R1,all-year.late-peak,15.26529810765
2025,electricity,R1,all-year.evening,15.26529810765
+2025,gas,R1,all-year.night,2.55
+2025,gas,R1,all-year.morning,2.55
+2025,gas,R1,all-year.afternoon,2.55
+2025,gas,R1,all-year.early-peak,2.55
+2025,gas,R1,all-year.late-peak,2.55
+2025,gas,R1,all-year.evening,2.55
2025,heat,R1,all-year.night,6.10611924306
2025,heat,R1,all-year.morning,6.10611924306
2025,heat,R1,all-year.afternoon,6.10611924306
2025,heat,R1,all-year.early-peak,6.10611924306
2025,heat,R1,all-year.late-peak,10.727716434449999
2025,heat,R1,all-year.evening,6.10611924306
-2030,gas,R1,all-year.night,-0.0
-2030,gas,R1,all-year.morning,-0.0
-2030,gas,R1,all-year.afternoon,-0.0
-2030,gas,R1,all-year.early-peak,-0.0
-2030,gas,R1,all-year.late-peak,-0.0
-2030,gas,R1,all-year.evening,-0.0
2030,electricity,R1,all-year.night,-0.0
2030,electricity,R1,all-year.morning,-0.0
2030,electricity,R1,all-year.afternoon,-0.0
2030,electricity,R1,all-year.early-peak,-0.0
2030,electricity,R1,all-year.late-peak,-0.0
2030,electricity,R1,all-year.evening,-0.0
+2030,gas,R1,all-year.night,-0.0
+2030,gas,R1,all-year.morning,-0.0
+2030,gas,R1,all-year.afternoon,-0.0
+2030,gas,R1,all-year.early-peak,-0.0
+2030,gas,R1,all-year.late-peak,-0.0
+2030,gas,R1,all-year.evening,-0.0
2030,heat,R1,all-year.night,-0.0
2030,heat,R1,all-year.morning,-0.0
2030,heat,R1,all-year.afternoon,-0.0
2030,heat,R1,all-year.early-peak,-0.0
2030,heat,R1,all-year.late-peak,-0.0
2030,heat,R1,all-year.evening,-0.0
-2035,gas,R1,all-year.night,-0.0
-2035,gas,R1,all-year.morning,-0.0
-2035,gas,R1,all-year.afternoon,-0.0
-2035,gas,R1,all-year.early-peak,-0.0
-2035,gas,R1,all-year.late-peak,-0.0
-2035,gas,R1,all-year.evening,-0.0
2035,electricity,R1,all-year.night,-0.0
2035,electricity,R1,all-year.morning,-0.0
2035,electricity,R1,all-year.afternoon,-0.0
2035,electricity,R1,all-year.early-peak,-0.0
2035,electricity,R1,all-year.late-peak,-0.0
2035,electricity,R1,all-year.evening,-0.0
+2035,gas,R1,all-year.night,-0.0
+2035,gas,R1,all-year.morning,-0.0
+2035,gas,R1,all-year.afternoon,-0.0
+2035,gas,R1,all-year.early-peak,-0.0
+2035,gas,R1,all-year.late-peak,-0.0
+2035,gas,R1,all-year.evening,-0.0
2035,heat,R1,all-year.night,-0.0
2035,heat,R1,all-year.morning,-0.0
2035,heat,R1,all-year.afternoon,-0.0
2035,heat,R1,all-year.early-peak,-0.0
2035,heat,R1,all-year.late-peak,-0.0
2035,heat,R1,all-year.evening,-0.0
-2040,gas,R1,all-year.night,-0.0
-2040,gas,R1,all-year.morning,-0.0
-2040,gas,R1,all-year.afternoon,-0.0
-2040,gas,R1,all-year.early-peak,-0.0
-2040,gas,R1,all-year.late-peak,-0.0
-2040,gas,R1,all-year.evening,-0.0
2040,electricity,R1,all-year.night,-0.0
2040,electricity,R1,all-year.morning,-0.0
2040,electricity,R1,all-year.afternoon,-0.0
2040,electricity,R1,all-year.early-peak,-0.0
2040,electricity,R1,all-year.late-peak,-0.0
2040,electricity,R1,all-year.evening,-0.0
+2040,gas,R1,all-year.night,-0.0
+2040,gas,R1,all-year.morning,-0.0
+2040,gas,R1,all-year.afternoon,-0.0
+2040,gas,R1,all-year.early-peak,-0.0
+2040,gas,R1,all-year.late-peak,-0.0
+2040,gas,R1,all-year.evening,-0.0
2040,heat,R1,all-year.night,-0.0
2040,heat,R1,all-year.morning,-0.0
2040,heat,R1,all-year.afternoon,-0.0
2040,heat,R1,all-year.early-peak,-0.0
2040,heat,R1,all-year.late-peak,-0.0
2040,heat,R1,all-year.evening,-0.0
-2045,gas,R1,all-year.night,-0.0
-2045,gas,R1,all-year.morning,-0.0
-2045,gas,R1,all-year.afternoon,-0.0
-2045,gas,R1,all-year.early-peak,-0.0
-2045,gas,R1,all-year.late-peak,-0.0
-2045,gas,R1,all-year.evening,-0.0
2045,electricity,R1,all-year.night,-0.0
2045,electricity,R1,all-year.morning,-0.0
2045,electricity,R1,all-year.afternoon,-0.0
2045,electricity,R1,all-year.early-peak,-0.0
2045,electricity,R1,all-year.late-peak,-0.0
2045,electricity,R1,all-year.evening,-0.0
+2045,gas,R1,all-year.night,-0.0
+2045,gas,R1,all-year.morning,-0.0
+2045,gas,R1,all-year.afternoon,-0.0
+2045,gas,R1,all-year.early-peak,-0.0
+2045,gas,R1,all-year.late-peak,-0.0
+2045,gas,R1,all-year.evening,-0.0
2045,heat,R1,all-year.night,-0.0
2045,heat,R1,all-year.morning,-0.0
2045,heat,R1,all-year.afternoon,-0.0
diff --git a/tests/data/simple_full/assets.csv b/tests/data/simple_full/assets.csv
new file mode 100644
index 000000000..5f4559fa2
--- /dev/null
+++ b/tests/data/simple_full/assets.csv
@@ -0,0 +1,10 @@
+asset_id,process_id,region_id,agent_id,group_id,commission_year,decommission_year,capacity
+0,GASDRV,GBR,A0_GEX,,2020,,4002.26
+1,GASPRC,GBR,A0_GPR,,2020,,3782.13
+2,WNDFRM,GBR,A0_ELC,,2020,2040,3.964844
+3,GASCGT,GBR,A0_ELC,,2020,2040,2.43
+4,RGASBR,GBR,A0_RES,,2020,2035,2900.0
+5,RELCHP,GBR,A0_RES,,2020,2035,399.98
+6,RGASBR,GBR,A0_RES,,2030,,355.83840587648046
+7,GASCGT,GBR,A0_ELC,,2030,2040,0.5151564434825014
+8,RGASBR,GBR,A0_RES,,2040,,3655.8189696
diff --git a/tests/data/simple_full/commodity_flows.csv b/tests/data/simple_full/commodity_flows.csv
new file mode 100644
index 000000000..f7217b32f
--- /dev/null
+++ b/tests/data/simple_full/commodity_flows.csv
@@ -0,0 +1,721 @@
+milestone_year,asset_id,commodity_id,time_slice,flow
+2020,0,GASPRD,winter.night,0.0
+2020,0,CO2EMT,winter.night,0.0
+2020,0,GASPRD,winter.day,151.10360181069296
+2020,0,CO2EMT,winter.day,772.5927160580732
+2020,0,GASPRD,winter.peak,125.070625
+2020,0,CO2EMT,winter.peak,639.486105625
+2020,0,GASPRD,winter.evening,166.76083466742
+2020,0,CO2EMT,winter.evening,852.6481476545185
+2020,0,GASPRD,peak.night,0.0
+2020,0,CO2EMT,peak.night,0.0
+2020,0,GASPRD,peak.day,0.0
+2020,0,CO2EMT,peak.day,0.0
+2020,0,GASPRD,peak.peak,58.67582562255717
+2020,0,CO2EMT,peak.peak,300.00949640813485
+2020,0,GASPRD,peak.evening,166.76083466742
+2020,0,CO2EMT,peak.evening,852.6481476545185
+2020,0,GASPRD,summer.night,0.0
+2020,0,CO2EMT,summer.night,0.0
+2020,0,GASPRD,summer.day,0.0
+2020,0,CO2EMT,summer.day,0.0
+2020,0,GASPRD,summer.peak,0.0
+2020,0,CO2EMT,summer.peak,0.0
+2020,0,GASPRD,summer.evening,0.16861964795988574
+2020,0,CO2EMT,summer.evening,0.8621522600188959
+2020,0,GASPRD,autumn.night,0.0
+2020,0,CO2EMT,autumn.night,0.0
+2020,0,GASPRD,autumn.day,0.0
+2020,0,CO2EMT,autumn.day,0.0
+2020,0,GASPRD,autumn.peak,0.0
+2020,0,CO2EMT,autumn.peak,0.0
+2020,0,GASPRD,autumn.evening,163.3883025525992
+2020,0,CO2EMT,autumn.evening,835.4043909514398
+2020,1,GASPRD,winter.night,-0.0
+2020,1,GASNAT,winter.night,0.0
+2020,1,CO2EMT,winter.night,0.0
+2020,1,GASPRD,winter.day,-153.36573202936748
+2020,1,GASNAT,winter.day,146.06260193273093
+2020,1,CO2EMT,winter.day,373.40904184102664
+2020,1,GASPRD,winter.peak,-124.10114062500001
+2020,1,GASNAT,winter.peak,118.1915625
+2020,1,CO2EMT,winter.peak,302.15672953125005
+2020,1,GASPRD,winter.evening,-165.4681888237455
+2020,1,GASNAT,winter.evening,157.58875126070998
+2020,1,CO2EMT,winter.evening,402.87564259800513
+2020,1,GASPRD,peak.night,-0.0
+2020,1,GASNAT,peak.night,0.0
+2020,1,CO2EMT,peak.night,0.0
+2020,1,GASPRD,peak.day,-0.0
+2020,1,GASNAT,peak.day,0.0
+2020,1,CO2EMT,peak.day,0.0
+2020,1,GASPRD,peak.peak,-59.96847146623166
+2020,1,GASNAT,peak.peak,57.11282996783967
+2020,1,CO2EMT,peak.peak,146.00894981278213
+2020,1,GASPRD,peak.evening,-165.4681888237455
+2020,1,GASNAT,peak.evening,157.58875126070998
+2020,1,CO2EMT,peak.evening,402.87564259800513
+2020,1,GASPRD,summer.night,-0.0
+2020,1,GASNAT,summer.night,0.0
+2020,1,CO2EMT,summer.night,0.0
+2020,1,GASPRD,summer.day,-0.0
+2020,1,GASNAT,summer.day,0.0
+2020,1,CO2EMT,summer.day,0.0
+2020,1,GASPRD,summer.peak,-0.0
+2020,1,GASNAT,summer.peak,0.0
+2020,1,CO2EMT,summer.peak,0.0
+2020,1,GASPRD,summer.evening,-0.16861964795988574
+2020,1,GASNAT,summer.evening,0.1605901409141769
+2020,1,CO2EMT,summer.evening,0.41054869524709325
+2020,1,GASPRD,autumn.night,-0.0
+2020,1,GASNAT,autumn.night,0.0
+2020,1,CO2EMT,autumn.night,0.0
+2020,1,GASPRD,autumn.day,-0.0
+2020,1,GASNAT,autumn.day,0.0
+2020,1,CO2EMT,autumn.day,0.0
+2020,1,GASPRD,autumn.peak,-0.0
+2020,1,GASNAT,autumn.peak,0.0
+2020,1,CO2EMT,autumn.peak,0.0
+2020,1,GASPRD,autumn.evening,-163.3883025525992
+2020,1,GASNAT,autumn.evening,155.60790719295161
+2020,1,CO2EMT,autumn.evening,397.81161473878086
+2020,2,ELCTRI,winter.night,4.435312795545212
+2020,2,ELCTRI,winter.day,7.075379933645912
+2020,2,ELCTRI,winter.peak,1.9712501261051125
+2020,2,ELCTRI,winter.evening,2.5696653598405335
+2020,2,ELCTRI,peak.night,2.851272517283696
+2020,2,ELCTRI,peak.day,6.3713620320039785
+2020,2,ELCTRI,peak.peak,1.7776452018191917
+2020,2,ELCTRI,peak.evening,1.72484387381507
+2020,2,ELCTRI,summer.night,1.6368416242136155
+2020,2,ELCTRI,summer.day,2.9055524196533997
+2020,2,ELCTRI,summer.peak,0.9567924409494001
+2020,2,ELCTRI,summer.evening,0.7124084843502
+2020,2,ELCTRI,autumn.night,3.203281465982185
+2020,2,ELCTRI,autumn.day,6.001752635595889
+2020,2,ELCTRI,autumn.peak,1.5488393825638174
+2020,2,ELCTRI,autumn.evening,1.9008483513729915
+2020,3,GASNAT,winter.night,-7.783808997678887
+2020,3,ELCTRI,winter.night,5.189205998452591
+2020,3,CO2EMT,winter.night,397.9861540513214
+2020,3,GASNAT,winter.day,-10.010898915527838
+2020,3,ELCTRI,winter.day,6.6739326103518914
+2020,3,CO2EMT,winter.day,511.8572615509383
+2020,3,GASNAT,winter.peak,-3.2303154358423316
+2020,3,ELCTRI,winter.peak,2.1535436238948877
+2020,3,CO2EMT,winter.peak,165.1660282346184
+2020,3,GASNAT,winter.evening,-4.395089526235901
+2020,3,ELCTRI,winter.evening,2.9300596841572673
+2020,3,CO2EMT,winter.evening,224.7209274764416
+2020,3,GASNAT,peak.night,-8.382740663321101
+2020,3,ELCTRI,peak.night,5.5884937755474
+2020,3,CO2EMT,peak.night,428.6095301156078
+2020,3,GASNAT,peak.day,-11.066925767990737
+2020,3,ELCTRI,peak.day,7.3779505119938245
+2020,3,CO2EMT,peak.day,565.8519145173664
+2020,3,GASNAT,peak.peak,-3.520722822271213
+2020,3,ELCTRI,peak.peak,2.3471485481808085
+2020,3,CO2EMT,peak.peak,180.0145579027271
+2020,3,GASNAT,peak.evening,-4.790137538321099
+2020,3,ELCTRI,peak.evening,3.1934250255473997
+2020,3,CO2EMT,peak.evening,244.9197323343578
+2020,3,GASNAT,summer.night,-0.1605901409141769
+2020,3,ELCTRI,summer.night,0.1070600939427846
+2020,3,CO2EMT,summer.night,8.210973904941865
+2020,3,GASNAT,summer.day,-0.0
+2020,3,ELCTRI,summer.day,0.0
+2020,3,CO2EMT,summer.day,0.0
+2020,3,GASNAT,summer.peak,-0.0
+2020,3,ELCTRI,summer.peak,0.0
+2020,3,CO2EMT,summer.peak,0.0
+2020,3,GASNAT,summer.evening,-0.0
+2020,3,ELCTRI,summer.evening,0.0
+2020,3,CO2EMT,summer.evening,0.0
+2020,3,GASNAT,autumn.night,-8.382740663321101
+2020,3,ELCTRI,autumn.night,5.5884937755474
+2020,3,CO2EMT,autumn.night,428.6095301156078
+2020,3,GASNAT,autumn.day,-11.621339862602872
+2020,3,ELCTRI,autumn.day,7.747559908401914
+2020,3,CO2EMT,autumn.day,594.1991071748848
+2020,3,GASNAT,autumn.peak,-3.592603125
+2020,3,ELCTRI,autumn.peak,2.39506875
+2020,3,CO2EMT,autumn.peak,183.68979778124998
+2020,3,GASNAT,autumn.evening,-4.790137538321099
+2020,3,ELCTRI,autumn.evening,3.1934250255473997
+2020,3,CO2EMT,autumn.evening,244.9197323343578
+2020,4,GASNAT,winter.night,-36.31286857370999
+2020,4,RSHEAT,winter.night,31.576407455399995
+2020,4,CO2EMT,winter.night,1856.6769701737921
+2020,4,GASNAT,winter.day,-193.26973453857997
+2020,4,RSHEAT,winter.day,168.06063872919998
+2020,4,CO2EMT,winter.day,9881.881526957595
+2020,4,GASNAT,winter.peak,-104.21872974118598
+2020,4,RSHEAT,winter.peak,90.62498238363999
+2020,4,CO2EMT,winter.peak,5328.7036516668395
+2020,4,GASNAT,winter.evening,-62.62146996467999
+2020,4,RSHEAT,winter.evening,54.453452143199996
+2020,4,CO2EMT,winter.evening,3201.8357592940883
+2020,4,GASNAT,peak.night,-19.52227251698524
+2020,4,RSHEAT,peak.night,16.975889145204558
+2020,4,CO2EMT,peak.night,998.1737937934555
+2020,4,GASNAT,peak.day,-82.18418839575197
+2020,4,RSHEAT,peak.day,71.46451164847998
+2020,4,CO2EMT,peak.day,4202.077552674799
+2020,4,GASNAT,peak.peak,-58.291277616306004
+2020,4,RSHEAT,peak.peak,50.688067492440005
+2020,4,CO2EMT,peak.peak,2980.433024521726
+2020,4,GASNAT,peak.evening,-26.943315907602276
+2020,4,RSHEAT,peak.evening,23.428970354436764
+2020,4,CO2EMT,peak.evening,1377.6117423557046
+2020,4,GASNAT,summer.night,-0.0
+2020,4,RSHEAT,summer.night,0.0
+2020,4,CO2EMT,summer.night,0.0
+2020,4,GASNAT,summer.day,-0.0
+2020,4,RSHEAT,summer.day,0.0
+2020,4,CO2EMT,summer.day,0.0
+2020,4,GASNAT,summer.peak,-0.0
+2020,4,RSHEAT,summer.peak,0.0
+2020,4,CO2EMT,summer.peak,0.0
+2020,4,GASNAT,summer.evening,-0.0
+2020,4,RSHEAT,summer.evening,0.0
+2020,4,CO2EMT,summer.evening,0.0
+2020,4,GASNAT,autumn.night,-9.10896122967321
+2020,4,RSHEAT,autumn.night,7.920835851889748
+2020,4,CO2EMT,autumn.night,465.7411876731913
+2020,4,GASNAT,autumn.day,-56.69422338503598
+2020,4,RSHEAT,autumn.day,49.29932468263999
+2020,4,CO2EMT,autumn.day,2898.7756416768902
+2020,4,GASNAT,autumn.peak,-44.38022605095635
+2020,4,RSHEAT,autumn.peak,38.591500913875095
+2020,4,CO2EMT,autumn.peak,2269.160957985399
+2020,4,GASNAT,autumn.evening,-17.037675338040994
+2020,4,RSHEAT,autumn.evening,14.815369859166085
+2020,4,CO2EMT,autumn.evening,871.1363400340363
+2020,5,ELCTRI,winter.night,-9.624518793997803
+2020,5,RSHEAT,winter.night,29.165208466660005
+2020,5,ELCTRI,winter.day,-13.749312543997803
+2020,5,RSHEAT,winter.day,41.664583466660005
+2020,5,ELCTRI,winter.peak,-4.12479375
+2020,5,RSHEAT,winter.peak,12.499375
+2020,5,ELCTRI,winter.evening,-5.499725043997801
+2020,5,RSHEAT,winter.evening,16.66583346666
+2020,5,ELCTRI,peak.night,-8.439766292831097
+2020,5,RSHEAT,peak.night,25.575049372215442
+2020,5,ELCTRI,peak.day,-13.749312543997803
+2020,5,RSHEAT,peak.day,41.664583466660005
+2020,5,ELCTRI,peak.peak,-4.12479375
+2020,5,RSHEAT,peak.peak,12.499375
+2020,5,ELCTRI,peak.evening,-4.91826889936247
+2020,5,RSHEAT,peak.evening,14.903845149583242
+2020,5,ELCTRI,summer.night,-1.7439017181564
+2020,5,RSHEAT,summer.night,5.28455066108
+2020,5,ELCTRI,summer.day,-2.9055524196533997
+2020,5,RSHEAT,summer.day,8.80470430198
+2020,5,ELCTRI,summer.peak,-0.9567924409494001
+2020,5,RSHEAT,summer.peak,2.89937103318
+2020,5,ELCTRI,summer.evening,-0.7124084843502
+2020,5,RSHEAT,summer.evening,2.1588135889399998
+2020,5,ELCTRI,autumn.night,-8.791775241529585
+2020,5,RSHEAT,autumn.night,26.641743156150255
+2020,5,ELCTRI,autumn.day,-13.749312543997803
+2020,5,RSHEAT,autumn.day,41.664583466660005
+2020,5,ELCTRI,autumn.peak,-3.9439081325638172
+2020,5,RSHEAT,autumn.peak,11.9512367653449
+2020,5,ELCTRI,autumn.evening,-5.094273376920391
+2020,5,RSHEAT,autumn.evening,15.437192051273913
+2030,0,GASPRD,winter.night,0.0
+2030,0,CO2EMT,winter.night,0.0
+2030,0,GASPRD,winter.day,209.007363584943
+2030,0,CO2EMT,winter.day,1068.6546500098136
+2030,0,GASPRD,winter.peak,125.070625
+2030,0,CO2EMT,winter.peak,639.486105625
+2030,0,GASPRD,winter.evening,166.76083466742
+2030,0,CO2EMT,winter.evening,852.6481476545185
+2030,0,GASPRD,peak.night,0.0
+2030,0,CO2EMT,peak.night,0.0
+2030,0,GASPRD,peak.day,0.0
+2030,0,CO2EMT,peak.day,0.0
+2030,0,GASPRD,peak.peak,88.48378530453337
+2030,0,CO2EMT,peak.peak,452.41759426207915
+2030,0,GASPRD,peak.evening,166.76083466742
+2030,0,CO2EMT,peak.evening,852.6481476545185
+2030,0,GASPRD,summer.night,0.0
+2030,0,CO2EMT,summer.night,0.0
+2030,0,GASPRD,summer.day,0.0
+2030,0,CO2EMT,summer.day,0.0
+2030,0,GASPRD,summer.peak,0.0
+2030,0,CO2EMT,summer.peak,0.0
+2030,0,GASPRD,summer.evening,0.464792220809886
+2030,0,CO2EMT,summer.evening,2.3764826250009476
+2030,0,GASPRD,autumn.night,0.0
+2030,0,CO2EMT,autumn.night,0.0
+2030,0,GASPRD,autumn.day,0.0
+2030,0,CO2EMT,autumn.day,0.0
+2030,0,GASPRD,autumn.peak,20.534211026060376
+2030,0,CO2EMT,autumn.peak,104.99142097624672
+2030,0,GASPRD,autumn.evening,166.76083466742
+2030,0,CO2EMT,autumn.evening,852.6481476545185
+2030,1,GASPRD,winter.night,-0.0
+2030,1,GASNAT,winter.night,0.0
+2030,1,CO2EMT,winter.night,0.0
+2030,1,GASPRD,winter.day,-211.26949380361748
+2030,1,GASNAT,winter.day,201.2090417177309
+2030,1,CO2EMT,winter.day,514.3909151513791
+2030,1,GASPRD,winter.peak,-124.10114062500001
+2030,1,GASNAT,winter.peak,118.1915625
+2030,1,CO2EMT,winter.peak,302.15672953125005
+2030,1,GASPRD,winter.evening,-165.4681888237455
+2030,1,GASNAT,winter.evening,157.58875126070998
+2030,1,CO2EMT,winter.evening,402.87564259800513
+2030,1,GASPRD,peak.night,-0.0
+2030,1,GASNAT,peak.night,0.0
+2030,1,CO2EMT,peak.night,0.0
+2030,1,GASPRD,peak.day,-0.0
+2030,1,GASNAT,peak.day,0.0
+2030,1,CO2EMT,peak.day,0.0
+2030,1,GASPRD,peak.peak,-89.77643114820788
+2030,1,GASNAT,peak.peak,85.50136299829322
+2030,1,CO2EMT,peak.peak,218.58423450513664
+2030,1,GASPRD,peak.evening,-165.4681888237455
+2030,1,GASNAT,peak.evening,157.58875126070998
+2030,1,CO2EMT,peak.evening,402.87564259800513
+2030,1,GASPRD,summer.night,-0.0
+2030,1,GASNAT,summer.night,0.0
+2030,1,CO2EMT,summer.night,0.0
+2030,1,GASPRD,summer.day,-0.0
+2030,1,GASNAT,summer.day,0.0
+2030,1,CO2EMT,summer.day,0.0
+2030,1,GASPRD,summer.peak,-0.0
+2030,1,GASNAT,summer.peak,0.0
+2030,1,CO2EMT,summer.peak,0.0
+2030,1,GASPRD,summer.evening,-0.464792220809886
+2030,1,GASNAT,summer.evening,0.44265925791417715
+2030,1,CO2EMT,summer.evening,1.131658392857594
+2030,1,GASPRD,autumn.night,-0.0
+2030,1,GASNAT,autumn.night,0.0
+2030,1,CO2EMT,autumn.night,0.0
+2030,1,GASPRD,autumn.day,-0.0
+2030,1,GASNAT,autumn.day,0.0
+2030,1,CO2EMT,autumn.day,0.0
+2030,1,GASPRD,autumn.peak,-21.826856869734893
+2030,1,GASNAT,autumn.peak,20.78748273308085
+2030,1,CO2EMT,autumn.peak,53.1431996071212
+2030,1,GASPRD,autumn.evening,-165.4681888237455
+2030,1,GASNAT,autumn.evening,157.58875126070998
+2030,1,CO2EMT,autumn.evening,402.87564259800513
+2030,2,ELCTRI,winter.night,4.435312795545212
+2030,2,ELCTRI,winter.day,7.075379933645912
+2030,2,ELCTRI,winter.peak,1.9712501261051125
+2030,2,ELCTRI,winter.evening,2.5696653598405335
+2030,2,ELCTRI,peak.night,2.851272517283696
+2030,2,ELCTRI,peak.day,6.3713620320039785
+2030,2,ELCTRI,peak.peak,1.7776452018191917
+2030,2,ELCTRI,peak.evening,1.72484387381507
+2030,2,ELCTRI,summer.night,1.6368416242136155
+2030,2,ELCTRI,summer.day,3.2188600626534005
+2030,2,ELCTRI,summer.peak,1.0599640039494003
+2030,2,ELCTRI,summer.evening,0.7892279633502001
+2030,2,ELCTRI,autumn.night,3.203281465982185
+2030,2,ELCTRI,autumn.day,6.001752635595889
+2030,2,ELCTRI,autumn.peak,1.5488393825638174
+2030,2,ELCTRI,autumn.evening,1.9008483513729915
+2030,3,GASNAT,winter.night,-7.783808997678887
+2030,3,ELCTRI,winter.night,5.189205998452591
+2030,3,CO2EMT,winter.night,397.9861540513214
+2030,3,GASNAT,winter.day,-10.010898915527838
+2030,3,ELCTRI,winter.day,6.6739326103518914
+2030,3,CO2EMT,winter.day,511.8572615509383
+2030,3,GASNAT,winter.peak,-2.468688831431171
+2030,3,ELCTRI,winter.peak,1.6457925542874472
+2030,3,CO2EMT,winter.peak,126.22405995107574
+2030,3,GASNAT,winter.evening,-3.3795873788970026
+2030,3,ELCTRI,winter.evening,2.253058252598002
+2030,3,CO2EMT,winter.evening,172.79830268300373
+2030,3,GASNAT,peak.night,-8.382740663321101
+2030,3,ELCTRI,peak.night,5.588493775547401
+2030,3,CO2EMT,peak.night,428.60953011560787
+2030,3,GASNAT,peak.day,-11.066925767990737
+2030,3,ELCTRI,peak.day,7.3779505119938245
+2030,3,CO2EMT,peak.day,565.8519145173664
+2030,3,GASNAT,peak.peak,-3.520722822271213
+2030,3,ELCTRI,peak.peak,2.3471485481808085
+2030,3,CO2EMT,peak.peak,180.0145579027271
+2030,3,GASNAT,peak.evening,-4.646819607935198
+2030,3,ELCTRI,peak.evening,3.0978797386234653
+2030,3,CO2EMT,peak.evening,237.59188655372665
+2030,3,GASNAT,summer.night,-0.0
+2030,3,ELCTRI,summer.night,0.0
+2030,3,CO2EMT,summer.night,0.0
+2030,3,GASNAT,summer.day,-0.0
+2030,3,ELCTRI,summer.day,0.0
+2030,3,CO2EMT,summer.day,0.0
+2030,3,GASNAT,summer.peak,-0.0
+2030,3,ELCTRI,summer.peak,0.0
+2030,3,CO2EMT,summer.peak,0.0
+2030,3,GASNAT,summer.evening,-0.0
+2030,3,ELCTRI,summer.evening,0.0
+2030,3,CO2EMT,summer.evening,0.0
+2030,3,GASNAT,autumn.night,-7.854727240273368
+2030,3,ELCTRI,autumn.night,5.236484826848912
+2030,3,CO2EMT,autumn.night,401.61220379517727
+2030,3,GASNAT,autumn.day,-11.621339862602872
+2030,3,ELCTRI,autumn.day,7.747559908401914
+2030,3,CO2EMT,autumn.day,594.1991071748848
+2030,3,GASNAT,autumn.peak,-3.592603125
+2030,3,ELCTRI,autumn.peak,2.39506875
+2030,3,CO2EMT,autumn.peak,183.68979778124998
+2030,3,GASNAT,autumn.evening,-4.3828128915983156
+2030,3,ELCTRI,autumn.evening,2.921875261065544
+2030,3,CO2EMT,autumn.evening,224.09322314742187
+2030,4,GASNAT,winter.night,-43.845148578709995
+2030,4,RSHEAT,winter.night,38.1262161554
+2030,4,CO2EMT,winter.night,2241.8024468294425
+2030,4,GASNAT,winter.day,-219.27676569358
+2030,4,RSHEAT,winter.day,190.6754484292
+2030,4,CO2EMT,winter.day,11211.621029912745
+2030,4,GASNAT,winter.peak,-104.21874999999999
+2030,4,RSHEAT,winter.peak,90.625
+2030,4,CO2EMT,winter.peak,5328.704687500001
+2030,4,GASNAT,winter.evening,-71.44063561968
+2030,4,RSHEAT,winter.evening,62.1222918432
+2030,4,CO2EMT,winter.evening,3652.7596992342387
+2030,4,GASNAT,peak.night,-0.0
+2030,4,RSHEAT,peak.night,0.0
+2030,4,CO2EMT,peak.night,0.0
+2030,4,GASNAT,peak.day,-96.21279149075198
+2030,4,RSHEAT,peak.day,83.66329694848
+2030,4,CO2EMT,peak.day,4919.3600289221495
+2030,4,GASNAT,peak.peak,-66.12685298630602
+2030,4,RSHEAT,peak.peak,57.50161129244002
+2030,4,CO2EMT,peak.peak,3381.0659931898267
+2030,4,GASNAT,peak.evening,-29.670500177964005
+2030,4,RSHEAT,peak.evening,25.800434937360006
+2030,4,CO2EMT,peak.evening,1517.0526740992998
+2030,4,GASNAT,summer.night,-0.0
+2030,4,RSHEAT,summer.night,0.0
+2030,4,CO2EMT,summer.night,0.0
+2030,4,GASNAT,summer.day,-0.0
+2030,4,RSHEAT,summer.day,0.0
+2030,4,CO2EMT,summer.day,0.0
+2030,4,GASNAT,summer.peak,-0.0
+2030,4,RSHEAT,summer.peak,0.0
+2030,4,CO2EMT,summer.peak,0.0
+2030,4,GASNAT,summer.evening,-0.0
+2030,4,RSHEAT,summer.evening,0.0
+2030,4,CO2EMT,summer.evening,0.0
+2030,4,GASNAT,autumn.night,-0.0
+2030,4,RSHEAT,autumn.night,0.0
+2030,4,CO2EMT,autumn.night,0.0
+2030,4,GASNAT,autumn.day,-67.97422666003601
+2030,4,RSHEAT,autumn.day,59.10802318264001
+2030,4,CO2EMT,autumn.day,3475.5222091276414
+2030,4,GASNAT,autumn.peak,-50.017432516103
+2030,4,RSHEAT,autumn.peak,43.49341957922
+2030,4,CO2EMT,autumn.peak,2557.391324548347
+2030,4,GASNAT,autumn.evening,-19.376214580347
+2030,4,RSHEAT,autumn.evening,16.84888224378
+2030,4,CO2EMT,autumn.evening,990.7058514931422
+2030,5,ELCTRI,winter.night,-9.624518793997803
+2030,5,RSHEAT,winter.night,29.165208466660005
+2030,5,ELCTRI,winter.day,-13.749312543997803
+2030,5,RSHEAT,winter.day,41.664583466660005
+2030,5,ELCTRI,winter.peak,-4.12479375
+2030,5,RSHEAT,winter.peak,12.499375
+2030,5,ELCTRI,winter.evening,-5.499725043997801
+2030,5,RSHEAT,winter.evening,16.66583346666
+2030,5,ELCTRI,peak.night,-9.624518793997803
+2030,5,RSHEAT,peak.night,29.165208466660005
+2030,5,ELCTRI,peak.day,-13.749312543997803
+2030,5,RSHEAT,peak.day,41.664583466660005
+2030,5,ELCTRI,peak.peak,-4.12479375
+2030,5,RSHEAT,peak.peak,12.499375
+2030,5,ELCTRI,peak.evening,-5.499725043997801
+2030,5,RSHEAT,peak.evening,16.66583346666
+2030,5,ELCTRI,summer.night,-1.9319477961564002
+2030,5,RSHEAT,summer.night,5.85438726108
+2030,5,ELCTRI,summer.day,-3.2188600626534005
+2030,5,RSHEAT,summer.day,9.75412140198
+2030,5,ELCTRI,summer.peak,-1.0599640039494003
+2030,5,RSHEAT,summer.peak,3.2120121331800005
+2030,5,ELCTRI,summer.evening,-0.7892279633502001
+2030,5,RSHEAT,summer.evening,2.39159988894
+2030,5,ELCTRI,autumn.night,-9.624518793997803
+2030,5,RSHEAT,autumn.night,29.165208466660005
+2030,5,ELCTRI,autumn.day,-13.749312543997803
+2030,5,RSHEAT,autumn.day,41.664583466660005
+2030,5,ELCTRI,autumn.peak,-4.12479375
+2030,5,RSHEAT,autumn.peak,12.499375
+2030,5,ELCTRI,autumn.evening,-5.499725043997801
+2030,5,RSHEAT,autumn.evening,16.66583346666
+2030,6,GASNAT,winter.night,-0.0
+2030,6,RSHEAT,winter.night,0.0
+2030,6,CO2EMT,winter.night,0.0
+2030,6,GASNAT,winter.day,-0.0
+2030,6,RSHEAT,winter.day,0.0
+2030,6,CO2EMT,winter.day,0.0
+2030,6,GASNAT,winter.peak,-12.787942711186016
+2030,6,RSHEAT,winter.peak,11.119950183640015
+2030,6,CO2EMT,winter.peak,653.8475108229411
+2030,6,GASNAT,winter.evening,-0.0
+2030,6,RSHEAT,winter.evening,0.0
+2030,6,CO2EMT,winter.evening,0.0
+2030,6,GASNAT,peak.night,-20.670129843374
+2030,6,RSHEAT,peak.night,17.97402595076
+2030,6,CO2EMT,peak.night,1056.8637388917127
+2030,6,GASNAT,peak.day,-0.0
+2030,6,RSHEAT,peak.day,0.0
+2030,6,CO2EMT,peak.day,0.0
+2030,6,GASNAT,peak.peak,-0.0
+2030,6,RSHEAT,peak.peak,0.0
+2030,6,CO2EMT,peak.peak,0.0
+2030,6,GASNAT,peak.evening,-0.0
+2030,6,RSHEAT,peak.evening,0.0
+2030,6,CO2EMT,peak.evening,0.0
+2030,6,GASNAT,summer.night,-0.0
+2030,6,RSHEAT,summer.night,0.0
+2030,6,CO2EMT,summer.night,0.0
+2030,6,GASNAT,summer.day,-0.0
+2030,6,RSHEAT,summer.day,0.0
+2030,6,CO2EMT,summer.day,0.0
+2030,6,GASNAT,summer.peak,-0.0
+2030,6,RSHEAT,summer.peak,0.0
+2030,6,CO2EMT,summer.peak,0.0
+2030,6,GASNAT,summer.evening,-0.0
+2030,6,RSHEAT,summer.evening,0.0
+2030,6,CO2EMT,summer.evening,0.0
+2030,6,GASNAT,autumn.night,-10.492917792587003
+2030,6,RSHEAT,autumn.night,9.124276341380003
+2030,6,CO2EMT,autumn.night,536.5028867349736
+2030,6,GASNAT,autumn.day,-0.0
+2030,6,RSHEAT,autumn.day,0.0
+2030,6,CO2EMT,autumn.day,0.0
+2030,6,GASNAT,autumn.peak,-0.0
+2030,6,RSHEAT,autumn.peak,0.0
+2030,6,CO2EMT,autumn.peak,0.0
+2030,6,GASNAT,autumn.evening,-0.0
+2030,6,RSHEAT,autumn.evening,0.0
+2030,6,CO2EMT,autumn.evening,0.0
+2030,7,GASNAT,winter.night,-0.0
+2030,7,ELCTRI,winter.night,0.0
+2030,7,CO2EMT,winter.night,0.0
+2030,7,GASNAT,winter.day,-0.0
+2030,7,ELCTRI,winter.day,0.0
+2030,7,CO2EMT,winter.day,0.0
+2030,7,GASNAT,winter.peak,-0.7616266044111606
+2030,7,ELCTRI,winter.peak,0.5077510696074404
+2030,7,CO2EMT,winter.peak,38.94196828354264
+2030,7,GASNAT,winter.evening,-1.0155021473388979
+2030,7,ELCTRI,winter.evening,0.6770014315592653
+2030,7,CO2EMT,winter.evening,51.92262479343785
+2030,7,GASNAT,peak.night,-1.7771287517500585
+2030,7,ELCTRI,peak.night,1.1847525011667057
+2030,7,CO2EMT,peak.night,90.86459307698048
+2030,7,GASNAT,peak.day,-0.0
+2030,7,ELCTRI,peak.day,0.0
+2030,7,CO2EMT,peak.day,0.0
+2030,7,GASNAT,peak.peak,-0.0
+2030,7,ELCTRI,peak.peak,0.0
+2030,7,CO2EMT,peak.peak,0.0
+2030,7,GASNAT,peak.evening,-1.0155021473388979
+2030,7,ELCTRI,peak.evening,0.6770014315592653
+2030,7,CO2EMT,peak.evening,51.92262479343785
+2030,7,GASNAT,summer.night,-0.44265925791417715
+2030,7,ELCTRI,summer.night,0.29510617194278477
+2030,7,CO2EMT,summer.night,22.633167857151875
+2030,7,GASNAT,summer.day,-0.0
+2030,7,ELCTRI,summer.day,0.0
+2030,7,CO2EMT,summer.day,0.0
+2030,7,GASNAT,summer.peak,-0.0
+2030,7,ELCTRI,summer.peak,0.0
+2030,7,CO2EMT,summer.peak,0.0
+2030,7,GASNAT,summer.evening,-0.0
+2030,7,ELCTRI,summer.evening,0.0
+2030,7,CO2EMT,summer.evening,0.0
+2030,7,GASNAT,autumn.night,-1.7771287517500585
+2030,7,ELCTRI,autumn.night,1.1847525011667057
+2030,7,CO2EMT,autumn.night,90.86459307698048
+2030,7,GASNAT,autumn.day,-0.0
+2030,7,ELCTRI,autumn.day,0.0
+2030,7,CO2EMT,autumn.day,0.0
+2030,7,GASNAT,autumn.peak,-0.27132842615427455
+2030,7,ELCTRI,autumn.peak,0.18088561743618303
+2030,7,CO2EMT,autumn.peak,13.873022429268056
+2030,7,GASNAT,autumn.evening,-1.0155021473388979
+2030,7,ELCTRI,autumn.evening,0.6770014315592653
+2030,7,CO2EMT,autumn.evening,51.92262479343785
+2040,0,GASPRD,winter.night,0.0
+2040,0,CO2EMT,winter.night,0.0
+2040,0,GASPRD,winter.day,360.96396982311984
+2040,0,CO2EMT,winter.day,1845.6087777056118
+2040,0,GASPRD,winter.peak,125.070625
+2040,0,CO2EMT,winter.peak,639.486105625
+2040,0,GASPRD,winter.evening,166.76083466742
+2040,0,CO2EMT,winter.evening,852.6481476545185
+2040,0,GASPRD,peak.night,0.0
+2040,0,CO2EMT,peak.night,0.0
+2040,0,GASPRD,peak.day,85.71569060312174
+2040,0,CO2EMT,peak.day,438.2643260537615
+2040,0,GASPRD,peak.peak,125.070625
+2040,0,CO2EMT,peak.peak,639.486105625
+2040,0,GASPRD,peak.evening,166.76083466742
+2040,0,CO2EMT,peak.evening,852.6481476545185
+2040,0,GASPRD,summer.night,0.0
+2040,0,CO2EMT,summer.night,0.0
+2040,0,GASPRD,summer.day,0.0
+2040,0,CO2EMT,summer.day,0.0
+2040,0,GASPRD,summer.peak,0.0
+2040,0,CO2EMT,summer.peak,0.0
+2040,0,GASPRD,summer.evening,28.106738155604855
+2040,0,CO2EMT,summer.evening,143.70975218960763
+2040,0,GASPRD,autumn.night,0.0
+2040,0,CO2EMT,autumn.night,0.0
+2040,0,GASPRD,autumn.day,11.030571054582595
+2040,0,CO2EMT,autumn.day,56.39930980208081
+2040,0,GASPRD,autumn.peak,125.070625
+2040,0,CO2EMT,autumn.peak,639.486105625
+2040,0,GASPRD,autumn.evening,166.76083466742
+2040,0,CO2EMT,autumn.evening,852.6481476545185
+2040,1,GASPRD,winter.night,-289.5693294487456
+2040,1,GASNAT,winter.night,275.78031376071004
+2040,1,CO2EMT,winter.night,705.0323721292552
+2040,1,GASPRD,winter.day,-73.65677059304869
+2040,1,GASNAT,winter.day,70.14930532671303
+2040,1,CO2EMT,winter.day,179.33669906774188
+2040,1,GASPRD,winter.peak,-124.10114062500001
+2040,1,GASNAT,winter.peak,118.1915625
+2040,1,CO2EMT,winter.peak,302.15672953125005
+2040,1,GASPRD,winter.evening,-165.4681888237455
+2040,1,GASNAT,winter.evening,157.58875126070998
+2040,1,CO2EMT,winter.evening,402.87564259800513
+2040,1,GASPRD,peak.night,-0.0
+2040,1,GASNAT,peak.night,0.0
+2040,1,CO2EMT,peak.night,0.0
+2040,1,GASPRD,peak.day,-87.97782082179623
+2040,1,GASNAT,peak.day,83.78840078266308
+2040,1,CO2EMT,peak.day,214.20504660087818
+2040,1,GASPRD,peak.peak,-124.10114062500001
+2040,1,GASNAT,peak.peak,118.1915625
+2040,1,CO2EMT,peak.peak,302.15672953125005
+2040,1,GASPRD,peak.evening,-165.4681888237455
+2040,1,GASNAT,peak.evening,157.58875126070998
+2040,1,CO2EMT,peak.evening,402.87564259800513
+2040,1,GASPRD,summer.night,-0.0
+2040,1,GASNAT,summer.night,0.0
+2040,1,CO2EMT,summer.night,0.0
+2040,1,GASPRD,summer.day,-0.0
+2040,1,GASNAT,summer.day,0.0
+2040,1,CO2EMT,summer.day,0.0
+2040,1,GASPRD,summer.peak,-0.0
+2040,1,GASNAT,summer.peak,0.0
+2040,1,CO2EMT,summer.peak,0.0
+2040,1,GASPRD,summer.evening,-28.106738155604855
+2040,1,GASNAT,summer.evening,26.768322052957004
+2040,1,CO2EMT,summer.evening,68.43321532838459
+2040,1,GASPRD,autumn.night,-0.0
+2040,1,GASNAT,autumn.night,0.0
+2040,1,CO2EMT,autumn.night,0.0
+2040,1,GASPRD,autumn.day,-13.292701273257066
+2040,1,GASNAT,autumn.day,12.659715498340063
+2040,1,CO2EMT,autumn.day,32.36456267150637
+2040,1,GASPRD,autumn.peak,-124.10114062500001
+2040,1,GASNAT,autumn.peak,118.1915625
+2040,1,CO2EMT,autumn.peak,302.15672953125005
+2040,1,GASPRD,autumn.evening,-165.4681888237455
+2040,1,GASNAT,autumn.evening,157.58875126070998
+2040,1,CO2EMT,autumn.evening,402.87564259800513
+2040,6,GASNAT,winter.night,-29.838533129172095
+2040,6,RSHEAT,winter.night,25.94655054710617
+2040,6,CO2EMT,winter.night,1525.6441988945694
+2040,6,GASNAT,winter.day,-42.62647584035811
+2040,6,RSHEAT,winter.day,37.066500730746185
+2040,6,CO2EMT,winter.day,2179.4917097175103
+2040,6,GASNAT,winter.peak,-12.787942711186016
+2040,6,RSHEAT,winter.peak,11.119950183640015
+2040,6,CO2EMT,winter.peak,653.8475108229411
+2040,6,GASNAT,winter.evening,-17.050590417986076
+2040,6,RSHEAT,winter.evening,14.826600363466154
+2040,6,CO2EMT,winter.evening,871.7966880716282
+2040,6,GASNAT,peak.night,-29.838533129172095
+2040,6,RSHEAT,peak.night,25.94655054710617
+2040,6,CO2EMT,peak.night,1525.6441988945694
+2040,6,GASNAT,peak.day,-42.62647584035811
+2040,6,RSHEAT,peak.day,37.066500730746185
+2040,6,CO2EMT,peak.day,2179.4917097175103
+2040,6,GASNAT,peak.peak,-12.787942711186016
+2040,6,RSHEAT,peak.peak,11.119950183640015
+2040,6,CO2EMT,peak.peak,653.8475108229411
+2040,6,GASNAT,peak.evening,-17.050590417986076
+2040,6,RSHEAT,peak.evening,14.826600363466154
+2040,6,CO2EMT,peak.evening,871.7966880716282
+2040,6,GASNAT,summer.night,-0.0
+2040,6,RSHEAT,summer.night,0.0
+2040,6,CO2EMT,summer.night,0.0
+2040,6,GASNAT,summer.day,-0.0
+2040,6,RSHEAT,summer.day,0.0
+2040,6,CO2EMT,summer.day,0.0
+2040,6,GASNAT,summer.peak,-0.0
+2040,6,RSHEAT,summer.peak,0.0
+2040,6,CO2EMT,summer.peak,0.0
+2040,6,GASNAT,summer.evening,-0.0
+2040,6,RSHEAT,summer.evening,0.0
+2040,6,CO2EMT,summer.evening,0.0
+2040,6,GASNAT,autumn.night,-29.838533129172095
+2040,6,RSHEAT,autumn.night,25.94655054710617
+2040,6,CO2EMT,autumn.night,1525.6441988945694
+2040,6,GASNAT,autumn.day,-42.62647584035811
+2040,6,RSHEAT,autumn.day,37.066500730746185
+2040,6,CO2EMT,autumn.day,2179.4917097175103
+2040,6,GASNAT,autumn.peak,-12.787942711186016
+2040,6,RSHEAT,autumn.peak,11.119950183640015
+2040,6,CO2EMT,autumn.peak,653.8475108229411
+2040,6,GASNAT,autumn.evening,-17.050590417986076
+2040,6,RSHEAT,autumn.evening,14.826600363466154
+2040,6,CO2EMT,autumn.evening,871.7966880716282
+2040,8,GASNAT,winter.night,-55.07888519119691
+2040,8,RSHEAT,winter.night,47.894682774953836
+2040,8,CO2EMT,winter.night,2816.183399825898
+2040,8,GASNAT,winter.day,-250.5715919948809
+2040,8,RSHEAT,winter.day,217.88834086511383
+2040,8,CO2EMT,winter.day,12811.72549869826
+2040,8,GASNAT,winter.peak,-131.38099422
+2040,8,RSHEAT,winter.peak,114.2443428
+2040,8,CO2EMT,winter.peak,6717.5102344686
+2040,8,GASNAT,winter.evening,-82.37491934335293
+2040,8,RSHEAT,winter.evening,71.63036464639386
+2040,8,CO2EMT,winter.evening,4211.829626025636
+2040,8,GASNAT,peak.night,-29.648126735860906
+2040,8,RSHEAT,peak.night,25.780979770313834
+2040,8,CO2EMT,peak.night,1515.9087200045683
+2040,8,GASNAT,peak.day,-115.5291897320529
+2040,8,RSHEAT,peak.day,100.46016498439383
+2040,8,CO2EMT,peak.day,5907.007470999865
+2040,8,GASNAT,peak.peak,-75.54876689511998
+2040,8,RSHEAT,peak.peak,65.6945799088
+2040,8,CO2EMT,peak.peak,3862.8084513474855
+2040,8,GASNAT,peak.evening,-36.53908908163693
+2040,8,RSHEAT,peak.evening,31.773120940553852
+2040,8,CO2EMT,peak.evening,1868.2436247440962
+2040,8,GASNAT,summer.night,-7.3878574402420005
+2040,8,RSHEAT,summer.night,6.424223861080001
+2040,8,CO2EMT,summer.night,377.7411509195735
+2040,8,GASNAT,summer.day,-12.309069277277
+2040,8,RSHEAT,summer.day,10.70353850198
+2040,8,CO2EMT,summer.day,629.362712147173
+2040,8,GASNAT,summer.peak,-4.053351218157
+2040,8,RSHEAT,summer.peak,3.5246532331800005
+2040,8,CO2EMT,summer.peak,207.24784778436745
+2040,8,GASNAT,summer.evening,-3.0180441172809997
+2040,8,RSHEAT,summer.evening,2.62438618894
+2040,8,CO2EMT,summer.evening,154.31259571657753
+2040,8,GASNAT,autumn.night,-18.480316070073908
+2040,8,RSHEAT,autumn.night,16.069840060933835
+2040,8,CO2EMT,autumn.night,944.898560662879
+2040,8,GASNAT,autumn.day,-84.54202508133689
+2040,8,RSHEAT,autumn.day,73.51480441855382
+2040,8,CO2EMT,autumn.day,4322.633742408756
+2040,8,GASNAT,autumn.peak,-57.871336489916985
+2040,8,RSHEAT,autumn.peak,50.32290129557999
+2040,8,CO2EMT,autumn.peak,2958.9614347294555
+2040,8,GASNAT,autumn.evening,-25.24280951901992
+2040,8,RSHEAT,autumn.evening,21.950269146973845
+2040,8,CO2EMT,autumn.evening,1290.6648507074885
diff --git a/tests/data/simple_full/commodity_prices.csv b/tests/data/simple_full/commodity_prices.csv
new file mode 100644
index 000000000..9667a6a19
--- /dev/null
+++ b/tests/data/simple_full/commodity_prices.csv
@@ -0,0 +1,166 @@
+milestone_year,commodity_id,region_id,time_slice,price
+2020,ELCTRI,GBR,winter.night,7.993308999999999
+2020,ELCTRI,GBR,winter.day,7.993308999999999
+2020,ELCTRI,GBR,winter.peak,7.993308999999999
+2020,ELCTRI,GBR,winter.evening,7.993308999999999
+2020,ELCTRI,GBR,peak.night,17.26223303030303
+2020,ELCTRI,GBR,peak.day,7.993308999999999
+2020,ELCTRI,GBR,peak.peak,7.993308999999999
+2020,ELCTRI,GBR,peak.evening,17.26223303030303
+2020,ELCTRI,GBR,summer.night,7.993308999999999
+2020,ELCTRI,GBR,summer.day,0.4
+2020,ELCTRI,GBR,summer.peak,0.4
+2020,ELCTRI,GBR,summer.evening,0.4
+2020,ELCTRI,GBR,autumn.night,17.26223303030303
+2020,ELCTRI,GBR,autumn.day,7.993308999999999
+2020,ELCTRI,GBR,autumn.peak,17.26223303030303
+2020,ELCTRI,GBR,autumn.evening,17.26223303030303
+2020,RSHEAT,GBR,winter.night,5.8665369
+2020,RSHEAT,GBR,winter.day,5.8665369
+2020,RSHEAT,GBR,winter.peak,5.8665369
+2020,RSHEAT,GBR,winter.evening,5.8665369
+2020,RSHEAT,GBR,peak.night,5.8665369
+2020,RSHEAT,GBR,peak.day,5.8665369
+2020,RSHEAT,GBR,peak.peak,5.8665369
+2020,RSHEAT,GBR,peak.evening,5.8665369
+2020,RSHEAT,GBR,summer.night,2.80779197
+2020,RSHEAT,GBR,summer.day,0.30200000000000005
+2020,RSHEAT,GBR,summer.peak,0.30200000000000005
+2020,RSHEAT,GBR,summer.evening,0.30200000000000005
+2020,RSHEAT,GBR,autumn.night,5.8665369
+2020,RSHEAT,GBR,autumn.day,5.8665369
+2020,RSHEAT,GBR,autumn.peak,5.8665369
+2020,RSHEAT,GBR,autumn.evening,5.8665369
+2020,GASPRD,GBR,winter.day,7.504534967942034
+2020,GASPRD,GBR,winter.peak,7.504534967942034
+2020,GASPRD,GBR,winter.evening,7.504534967942034
+2020,GASPRD,GBR,peak.peak,7.504534967942034
+2020,GASPRD,GBR,peak.evening,7.504534967942034
+2020,GASPRD,GBR,summer.evening,7.504534967942034
+2020,GASPRD,GBR,autumn.evening,7.504534967942034
+2020,GASNAT,GBR,winter.day,6.5982585862326335
+2020,GASNAT,GBR,winter.peak,6.5982585862326335
+2020,GASNAT,GBR,winter.evening,6.5982585862326335
+2020,GASNAT,GBR,peak.peak,6.5982585862326335
+2020,GASNAT,GBR,peak.evening,6.5982585862326335
+2020,GASNAT,GBR,summer.evening,6.5982585862326335
+2020,GASNAT,GBR,autumn.evening,6.5982585862326335
+2020,GASPRD,GBR,winter.night,3.428609686103873
+2020,GASPRD,GBR,peak.night,3.428609686103873
+2020,GASPRD,GBR,peak.day,3.428609686103873
+2020,GASPRD,GBR,summer.night,3.428609686103873
+2020,GASPRD,GBR,summer.day,3.428609686103873
+2020,GASPRD,GBR,summer.peak,3.428609686103873
+2020,GASPRD,GBR,autumn.night,3.428609686103873
+2020,GASPRD,GBR,autumn.day,3.428609686103873
+2020,GASPRD,GBR,autumn.peak,3.428609686103873
+2020,GASNAT,GBR,winter.night,3.7738687802727107
+2020,GASNAT,GBR,peak.night,3.7738687802727107
+2020,GASNAT,GBR,peak.day,3.7738687802727107
+2020,GASNAT,GBR,summer.night,3.7738687802727107
+2020,GASNAT,GBR,summer.day,3.7738687802727107
+2020,GASNAT,GBR,summer.peak,3.7738687802727107
+2020,GASNAT,GBR,autumn.night,3.7738687802727107
+2020,GASNAT,GBR,autumn.day,3.7738687802727107
+2020,GASNAT,GBR,autumn.peak,3.7738687802727107
+2030,ELCTRI,GBR,winter.night,7.993308999999999
+2030,ELCTRI,GBR,winter.day,7.993308999999999
+2030,ELCTRI,GBR,winter.peak,7.993308999999999
+2030,ELCTRI,GBR,winter.evening,7.993308999999999
+2030,ELCTRI,GBR,peak.night,7.993308999999999
+2030,ELCTRI,GBR,peak.day,7.993308999999999
+2030,ELCTRI,GBR,peak.peak,7.993308999999999
+2030,ELCTRI,GBR,peak.evening,7.993308999999999
+2030,ELCTRI,GBR,summer.night,7.993308999999999
+2030,ELCTRI,GBR,summer.day,0.4
+2030,ELCTRI,GBR,summer.peak,0.4
+2030,ELCTRI,GBR,summer.evening,0.4
+2030,ELCTRI,GBR,autumn.night,7.993308999999999
+2030,ELCTRI,GBR,autumn.day,7.993308999999999
+2030,ELCTRI,GBR,autumn.peak,7.993308999999999
+2030,ELCTRI,GBR,autumn.evening,7.993308999999999
+2030,RSHEAT,GBR,winter.night,5.8665369
+2030,RSHEAT,GBR,winter.day,5.8665369
+2030,RSHEAT,GBR,winter.peak,5.8665369
+2030,RSHEAT,GBR,winter.evening,5.8665369
+2030,RSHEAT,GBR,peak.night,5.8665369
+2030,RSHEAT,GBR,peak.day,5.8665369
+2030,RSHEAT,GBR,peak.peak,5.8665369
+2030,RSHEAT,GBR,peak.evening,5.8665369
+2030,RSHEAT,GBR,summer.night,2.80779197
+2030,RSHEAT,GBR,summer.day,0.30200000000000005
+2030,RSHEAT,GBR,summer.peak,0.30200000000000005
+2030,RSHEAT,GBR,summer.evening,0.30200000000000005
+2030,RSHEAT,GBR,autumn.night,5.8665369
+2030,RSHEAT,GBR,autumn.day,5.8665369
+2030,RSHEAT,GBR,autumn.peak,5.8665369
+2030,RSHEAT,GBR,autumn.evening,5.8665369
+2030,GASPRD,GBR,winter.day,6.876080672233131
+2030,GASPRD,GBR,winter.peak,6.876080672233131
+2030,GASPRD,GBR,winter.evening,6.876080672233131
+2030,GASPRD,GBR,peak.peak,6.876080672233131
+2030,GASPRD,GBR,peak.evening,6.876080672233131
+2030,GASPRD,GBR,summer.evening,6.876080672233131
+2030,GASPRD,GBR,autumn.peak,6.876080672233131
+2030,GASPRD,GBR,autumn.evening,6.876080672233131
+2030,GASNAT,GBR,winter.day,6.161750462640322
+2030,GASNAT,GBR,winter.peak,6.161750462640322
+2030,GASNAT,GBR,winter.evening,6.161750462640322
+2030,GASNAT,GBR,peak.peak,6.161750462640322
+2030,GASNAT,GBR,peak.evening,6.161750462640322
+2030,GASNAT,GBR,summer.evening,6.161750462640322
+2030,GASNAT,GBR,autumn.peak,6.161750462640322
+2030,GASNAT,GBR,autumn.evening,6.161750462640322
+2030,GASPRD,GBR,winter.night,3.428609686103873
+2030,GASPRD,GBR,peak.night,3.428609686103873
+2030,GASPRD,GBR,peak.day,3.428609686103873
+2030,GASPRD,GBR,summer.night,3.428609686103873
+2030,GASPRD,GBR,summer.day,3.428609686103873
+2030,GASPRD,GBR,summer.peak,3.428609686103873
+2030,GASPRD,GBR,autumn.night,3.428609686103873
+2030,GASPRD,GBR,autumn.day,3.428609686103873
+2030,GASNAT,GBR,winter.night,3.7738687802727107
+2030,GASNAT,GBR,peak.night,3.7738687802727107
+2030,GASNAT,GBR,peak.day,3.7738687802727107
+2030,GASNAT,GBR,summer.night,3.7738687802727107
+2030,GASNAT,GBR,summer.day,3.7738687802727107
+2030,GASNAT,GBR,summer.peak,3.7738687802727107
+2030,GASNAT,GBR,autumn.night,3.7738687802727107
+2030,GASNAT,GBR,autumn.day,3.7738687802727107
+2040,RSHEAT,GBR,winter.night,5.8665369
+2040,RSHEAT,GBR,winter.day,5.8665369
+2040,RSHEAT,GBR,winter.peak,5.8665369
+2040,RSHEAT,GBR,winter.evening,5.8665369
+2040,RSHEAT,GBR,peak.night,5.8665369
+2040,RSHEAT,GBR,peak.day,5.8665369
+2040,RSHEAT,GBR,peak.peak,5.8665369
+2040,RSHEAT,GBR,peak.evening,5.8665369
+2040,RSHEAT,GBR,summer.night,5.8665369
+2040,RSHEAT,GBR,summer.day,5.8665369
+2040,RSHEAT,GBR,summer.peak,5.8665369
+2040,RSHEAT,GBR,summer.evening,5.8665369
+2040,RSHEAT,GBR,autumn.night,5.8665369
+2040,RSHEAT,GBR,autumn.day,5.8665369
+2040,RSHEAT,GBR,autumn.peak,5.8665369
+2040,RSHEAT,GBR,autumn.evening,5.8665369
+2040,GASPRD,GBR,winter.day,5.443465073396721
+2040,GASPRD,GBR,winter.peak,5.443465073396721
+2040,GASPRD,GBR,winter.evening,5.443465073396721
+2040,GASPRD,GBR,peak.day,5.443465073396721
+2040,GASPRD,GBR,peak.peak,5.443465073396721
+2040,GASPRD,GBR,peak.evening,5.443465073396721
+2040,GASPRD,GBR,summer.evening,5.443465073396721
+2040,GASPRD,GBR,autumn.day,5.443465073396721
+2040,GASPRD,GBR,autumn.peak,5.443465073396721
+2040,GASPRD,GBR,autumn.evening,5.443465073396721
+2040,GASNAT,GBR,winter.night,5.166692883880049
+2040,GASNAT,GBR,winter.day,5.166692883880049
+2040,GASNAT,GBR,winter.peak,5.166692883880049
+2040,GASNAT,GBR,winter.evening,5.166692883880049
+2040,GASNAT,GBR,peak.day,5.166692883880049
+2040,GASNAT,GBR,peak.peak,5.166692883880049
+2040,GASNAT,GBR,peak.evening,5.166692883880049
+2040,GASNAT,GBR,summer.evening,5.166692883880049
+2040,GASNAT,GBR,autumn.day,5.166692883880049
+2040,GASNAT,GBR,autumn.peak,5.166692883880049
+2040,GASNAT,GBR,autumn.evening,5.166692883880049
diff --git a/tests/data/simple_marginal/assets.csv b/tests/data/simple_marginal/assets.csv
new file mode 100644
index 000000000..5f4559fa2
--- /dev/null
+++ b/tests/data/simple_marginal/assets.csv
@@ -0,0 +1,10 @@
+asset_id,process_id,region_id,agent_id,group_id,commission_year,decommission_year,capacity
+0,GASDRV,GBR,A0_GEX,,2020,,4002.26
+1,GASPRC,GBR,A0_GPR,,2020,,3782.13
+2,WNDFRM,GBR,A0_ELC,,2020,2040,3.964844
+3,GASCGT,GBR,A0_ELC,,2020,2040,2.43
+4,RGASBR,GBR,A0_RES,,2020,2035,2900.0
+5,RELCHP,GBR,A0_RES,,2020,2035,399.98
+6,RGASBR,GBR,A0_RES,,2030,,355.83840587648046
+7,GASCGT,GBR,A0_ELC,,2030,2040,0.5151564434825014
+8,RGASBR,GBR,A0_RES,,2040,,3655.8189696
diff --git a/tests/data/simple_marginal/commodity_flows.csv b/tests/data/simple_marginal/commodity_flows.csv
new file mode 100644
index 000000000..f7217b32f
--- /dev/null
+++ b/tests/data/simple_marginal/commodity_flows.csv
@@ -0,0 +1,721 @@
+milestone_year,asset_id,commodity_id,time_slice,flow
+2020,0,GASPRD,winter.night,0.0
+2020,0,CO2EMT,winter.night,0.0
+2020,0,GASPRD,winter.day,151.10360181069296
+2020,0,CO2EMT,winter.day,772.5927160580732
+2020,0,GASPRD,winter.peak,125.070625
+2020,0,CO2EMT,winter.peak,639.486105625
+2020,0,GASPRD,winter.evening,166.76083466742
+2020,0,CO2EMT,winter.evening,852.6481476545185
+2020,0,GASPRD,peak.night,0.0
+2020,0,CO2EMT,peak.night,0.0
+2020,0,GASPRD,peak.day,0.0
+2020,0,CO2EMT,peak.day,0.0
+2020,0,GASPRD,peak.peak,58.67582562255717
+2020,0,CO2EMT,peak.peak,300.00949640813485
+2020,0,GASPRD,peak.evening,166.76083466742
+2020,0,CO2EMT,peak.evening,852.6481476545185
+2020,0,GASPRD,summer.night,0.0
+2020,0,CO2EMT,summer.night,0.0
+2020,0,GASPRD,summer.day,0.0
+2020,0,CO2EMT,summer.day,0.0
+2020,0,GASPRD,summer.peak,0.0
+2020,0,CO2EMT,summer.peak,0.0
+2020,0,GASPRD,summer.evening,0.16861964795988574
+2020,0,CO2EMT,summer.evening,0.8621522600188959
+2020,0,GASPRD,autumn.night,0.0
+2020,0,CO2EMT,autumn.night,0.0
+2020,0,GASPRD,autumn.day,0.0
+2020,0,CO2EMT,autumn.day,0.0
+2020,0,GASPRD,autumn.peak,0.0
+2020,0,CO2EMT,autumn.peak,0.0
+2020,0,GASPRD,autumn.evening,163.3883025525992
+2020,0,CO2EMT,autumn.evening,835.4043909514398
+2020,1,GASPRD,winter.night,-0.0
+2020,1,GASNAT,winter.night,0.0
+2020,1,CO2EMT,winter.night,0.0
+2020,1,GASPRD,winter.day,-153.36573202936748
+2020,1,GASNAT,winter.day,146.06260193273093
+2020,1,CO2EMT,winter.day,373.40904184102664
+2020,1,GASPRD,winter.peak,-124.10114062500001
+2020,1,GASNAT,winter.peak,118.1915625
+2020,1,CO2EMT,winter.peak,302.15672953125005
+2020,1,GASPRD,winter.evening,-165.4681888237455
+2020,1,GASNAT,winter.evening,157.58875126070998
+2020,1,CO2EMT,winter.evening,402.87564259800513
+2020,1,GASPRD,peak.night,-0.0
+2020,1,GASNAT,peak.night,0.0
+2020,1,CO2EMT,peak.night,0.0
+2020,1,GASPRD,peak.day,-0.0
+2020,1,GASNAT,peak.day,0.0
+2020,1,CO2EMT,peak.day,0.0
+2020,1,GASPRD,peak.peak,-59.96847146623166
+2020,1,GASNAT,peak.peak,57.11282996783967
+2020,1,CO2EMT,peak.peak,146.00894981278213
+2020,1,GASPRD,peak.evening,-165.4681888237455
+2020,1,GASNAT,peak.evening,157.58875126070998
+2020,1,CO2EMT,peak.evening,402.87564259800513
+2020,1,GASPRD,summer.night,-0.0
+2020,1,GASNAT,summer.night,0.0
+2020,1,CO2EMT,summer.night,0.0
+2020,1,GASPRD,summer.day,-0.0
+2020,1,GASNAT,summer.day,0.0
+2020,1,CO2EMT,summer.day,0.0
+2020,1,GASPRD,summer.peak,-0.0
+2020,1,GASNAT,summer.peak,0.0
+2020,1,CO2EMT,summer.peak,0.0
+2020,1,GASPRD,summer.evening,-0.16861964795988574
+2020,1,GASNAT,summer.evening,0.1605901409141769
+2020,1,CO2EMT,summer.evening,0.41054869524709325
+2020,1,GASPRD,autumn.night,-0.0
+2020,1,GASNAT,autumn.night,0.0
+2020,1,CO2EMT,autumn.night,0.0
+2020,1,GASPRD,autumn.day,-0.0
+2020,1,GASNAT,autumn.day,0.0
+2020,1,CO2EMT,autumn.day,0.0
+2020,1,GASPRD,autumn.peak,-0.0
+2020,1,GASNAT,autumn.peak,0.0
+2020,1,CO2EMT,autumn.peak,0.0
+2020,1,GASPRD,autumn.evening,-163.3883025525992
+2020,1,GASNAT,autumn.evening,155.60790719295161
+2020,1,CO2EMT,autumn.evening,397.81161473878086
+2020,2,ELCTRI,winter.night,4.435312795545212
+2020,2,ELCTRI,winter.day,7.075379933645912
+2020,2,ELCTRI,winter.peak,1.9712501261051125
+2020,2,ELCTRI,winter.evening,2.5696653598405335
+2020,2,ELCTRI,peak.night,2.851272517283696
+2020,2,ELCTRI,peak.day,6.3713620320039785
+2020,2,ELCTRI,peak.peak,1.7776452018191917
+2020,2,ELCTRI,peak.evening,1.72484387381507
+2020,2,ELCTRI,summer.night,1.6368416242136155
+2020,2,ELCTRI,summer.day,2.9055524196533997
+2020,2,ELCTRI,summer.peak,0.9567924409494001
+2020,2,ELCTRI,summer.evening,0.7124084843502
+2020,2,ELCTRI,autumn.night,3.203281465982185
+2020,2,ELCTRI,autumn.day,6.001752635595889
+2020,2,ELCTRI,autumn.peak,1.5488393825638174
+2020,2,ELCTRI,autumn.evening,1.9008483513729915
+2020,3,GASNAT,winter.night,-7.783808997678887
+2020,3,ELCTRI,winter.night,5.189205998452591
+2020,3,CO2EMT,winter.night,397.9861540513214
+2020,3,GASNAT,winter.day,-10.010898915527838
+2020,3,ELCTRI,winter.day,6.6739326103518914
+2020,3,CO2EMT,winter.day,511.8572615509383
+2020,3,GASNAT,winter.peak,-3.2303154358423316
+2020,3,ELCTRI,winter.peak,2.1535436238948877
+2020,3,CO2EMT,winter.peak,165.1660282346184
+2020,3,GASNAT,winter.evening,-4.395089526235901
+2020,3,ELCTRI,winter.evening,2.9300596841572673
+2020,3,CO2EMT,winter.evening,224.7209274764416
+2020,3,GASNAT,peak.night,-8.382740663321101
+2020,3,ELCTRI,peak.night,5.5884937755474
+2020,3,CO2EMT,peak.night,428.6095301156078
+2020,3,GASNAT,peak.day,-11.066925767990737
+2020,3,ELCTRI,peak.day,7.3779505119938245
+2020,3,CO2EMT,peak.day,565.8519145173664
+2020,3,GASNAT,peak.peak,-3.520722822271213
+2020,3,ELCTRI,peak.peak,2.3471485481808085
+2020,3,CO2EMT,peak.peak,180.0145579027271
+2020,3,GASNAT,peak.evening,-4.790137538321099
+2020,3,ELCTRI,peak.evening,3.1934250255473997
+2020,3,CO2EMT,peak.evening,244.9197323343578
+2020,3,GASNAT,summer.night,-0.1605901409141769
+2020,3,ELCTRI,summer.night,0.1070600939427846
+2020,3,CO2EMT,summer.night,8.210973904941865
+2020,3,GASNAT,summer.day,-0.0
+2020,3,ELCTRI,summer.day,0.0
+2020,3,CO2EMT,summer.day,0.0
+2020,3,GASNAT,summer.peak,-0.0
+2020,3,ELCTRI,summer.peak,0.0
+2020,3,CO2EMT,summer.peak,0.0
+2020,3,GASNAT,summer.evening,-0.0
+2020,3,ELCTRI,summer.evening,0.0
+2020,3,CO2EMT,summer.evening,0.0
+2020,3,GASNAT,autumn.night,-8.382740663321101
+2020,3,ELCTRI,autumn.night,5.5884937755474
+2020,3,CO2EMT,autumn.night,428.6095301156078
+2020,3,GASNAT,autumn.day,-11.621339862602872
+2020,3,ELCTRI,autumn.day,7.747559908401914
+2020,3,CO2EMT,autumn.day,594.1991071748848
+2020,3,GASNAT,autumn.peak,-3.592603125
+2020,3,ELCTRI,autumn.peak,2.39506875
+2020,3,CO2EMT,autumn.peak,183.68979778124998
+2020,3,GASNAT,autumn.evening,-4.790137538321099
+2020,3,ELCTRI,autumn.evening,3.1934250255473997
+2020,3,CO2EMT,autumn.evening,244.9197323343578
+2020,4,GASNAT,winter.night,-36.31286857370999
+2020,4,RSHEAT,winter.night,31.576407455399995
+2020,4,CO2EMT,winter.night,1856.6769701737921
+2020,4,GASNAT,winter.day,-193.26973453857997
+2020,4,RSHEAT,winter.day,168.06063872919998
+2020,4,CO2EMT,winter.day,9881.881526957595
+2020,4,GASNAT,winter.peak,-104.21872974118598
+2020,4,RSHEAT,winter.peak,90.62498238363999
+2020,4,CO2EMT,winter.peak,5328.7036516668395
+2020,4,GASNAT,winter.evening,-62.62146996467999
+2020,4,RSHEAT,winter.evening,54.453452143199996
+2020,4,CO2EMT,winter.evening,3201.8357592940883
+2020,4,GASNAT,peak.night,-19.52227251698524
+2020,4,RSHEAT,peak.night,16.975889145204558
+2020,4,CO2EMT,peak.night,998.1737937934555
+2020,4,GASNAT,peak.day,-82.18418839575197
+2020,4,RSHEAT,peak.day,71.46451164847998
+2020,4,CO2EMT,peak.day,4202.077552674799
+2020,4,GASNAT,peak.peak,-58.291277616306004
+2020,4,RSHEAT,peak.peak,50.688067492440005
+2020,4,CO2EMT,peak.peak,2980.433024521726
+2020,4,GASNAT,peak.evening,-26.943315907602276
+2020,4,RSHEAT,peak.evening,23.428970354436764
+2020,4,CO2EMT,peak.evening,1377.6117423557046
+2020,4,GASNAT,summer.night,-0.0
+2020,4,RSHEAT,summer.night,0.0
+2020,4,CO2EMT,summer.night,0.0
+2020,4,GASNAT,summer.day,-0.0
+2020,4,RSHEAT,summer.day,0.0
+2020,4,CO2EMT,summer.day,0.0
+2020,4,GASNAT,summer.peak,-0.0
+2020,4,RSHEAT,summer.peak,0.0
+2020,4,CO2EMT,summer.peak,0.0
+2020,4,GASNAT,summer.evening,-0.0
+2020,4,RSHEAT,summer.evening,0.0
+2020,4,CO2EMT,summer.evening,0.0
+2020,4,GASNAT,autumn.night,-9.10896122967321
+2020,4,RSHEAT,autumn.night,7.920835851889748
+2020,4,CO2EMT,autumn.night,465.7411876731913
+2020,4,GASNAT,autumn.day,-56.69422338503598
+2020,4,RSHEAT,autumn.day,49.29932468263999
+2020,4,CO2EMT,autumn.day,2898.7756416768902
+2020,4,GASNAT,autumn.peak,-44.38022605095635
+2020,4,RSHEAT,autumn.peak,38.591500913875095
+2020,4,CO2EMT,autumn.peak,2269.160957985399
+2020,4,GASNAT,autumn.evening,-17.037675338040994
+2020,4,RSHEAT,autumn.evening,14.815369859166085
+2020,4,CO2EMT,autumn.evening,871.1363400340363
+2020,5,ELCTRI,winter.night,-9.624518793997803
+2020,5,RSHEAT,winter.night,29.165208466660005
+2020,5,ELCTRI,winter.day,-13.749312543997803
+2020,5,RSHEAT,winter.day,41.664583466660005
+2020,5,ELCTRI,winter.peak,-4.12479375
+2020,5,RSHEAT,winter.peak,12.499375
+2020,5,ELCTRI,winter.evening,-5.499725043997801
+2020,5,RSHEAT,winter.evening,16.66583346666
+2020,5,ELCTRI,peak.night,-8.439766292831097
+2020,5,RSHEAT,peak.night,25.575049372215442
+2020,5,ELCTRI,peak.day,-13.749312543997803
+2020,5,RSHEAT,peak.day,41.664583466660005
+2020,5,ELCTRI,peak.peak,-4.12479375
+2020,5,RSHEAT,peak.peak,12.499375
+2020,5,ELCTRI,peak.evening,-4.91826889936247
+2020,5,RSHEAT,peak.evening,14.903845149583242
+2020,5,ELCTRI,summer.night,-1.7439017181564
+2020,5,RSHEAT,summer.night,5.28455066108
+2020,5,ELCTRI,summer.day,-2.9055524196533997
+2020,5,RSHEAT,summer.day,8.80470430198
+2020,5,ELCTRI,summer.peak,-0.9567924409494001
+2020,5,RSHEAT,summer.peak,2.89937103318
+2020,5,ELCTRI,summer.evening,-0.7124084843502
+2020,5,RSHEAT,summer.evening,2.1588135889399998
+2020,5,ELCTRI,autumn.night,-8.791775241529585
+2020,5,RSHEAT,autumn.night,26.641743156150255
+2020,5,ELCTRI,autumn.day,-13.749312543997803
+2020,5,RSHEAT,autumn.day,41.664583466660005
+2020,5,ELCTRI,autumn.peak,-3.9439081325638172
+2020,5,RSHEAT,autumn.peak,11.9512367653449
+2020,5,ELCTRI,autumn.evening,-5.094273376920391
+2020,5,RSHEAT,autumn.evening,15.437192051273913
+2030,0,GASPRD,winter.night,0.0
+2030,0,CO2EMT,winter.night,0.0
+2030,0,GASPRD,winter.day,209.007363584943
+2030,0,CO2EMT,winter.day,1068.6546500098136
+2030,0,GASPRD,winter.peak,125.070625
+2030,0,CO2EMT,winter.peak,639.486105625
+2030,0,GASPRD,winter.evening,166.76083466742
+2030,0,CO2EMT,winter.evening,852.6481476545185
+2030,0,GASPRD,peak.night,0.0
+2030,0,CO2EMT,peak.night,0.0
+2030,0,GASPRD,peak.day,0.0
+2030,0,CO2EMT,peak.day,0.0
+2030,0,GASPRD,peak.peak,88.48378530453337
+2030,0,CO2EMT,peak.peak,452.41759426207915
+2030,0,GASPRD,peak.evening,166.76083466742
+2030,0,CO2EMT,peak.evening,852.6481476545185
+2030,0,GASPRD,summer.night,0.0
+2030,0,CO2EMT,summer.night,0.0
+2030,0,GASPRD,summer.day,0.0
+2030,0,CO2EMT,summer.day,0.0
+2030,0,GASPRD,summer.peak,0.0
+2030,0,CO2EMT,summer.peak,0.0
+2030,0,GASPRD,summer.evening,0.464792220809886
+2030,0,CO2EMT,summer.evening,2.3764826250009476
+2030,0,GASPRD,autumn.night,0.0
+2030,0,CO2EMT,autumn.night,0.0
+2030,0,GASPRD,autumn.day,0.0
+2030,0,CO2EMT,autumn.day,0.0
+2030,0,GASPRD,autumn.peak,20.534211026060376
+2030,0,CO2EMT,autumn.peak,104.99142097624672
+2030,0,GASPRD,autumn.evening,166.76083466742
+2030,0,CO2EMT,autumn.evening,852.6481476545185
+2030,1,GASPRD,winter.night,-0.0
+2030,1,GASNAT,winter.night,0.0
+2030,1,CO2EMT,winter.night,0.0
+2030,1,GASPRD,winter.day,-211.26949380361748
+2030,1,GASNAT,winter.day,201.2090417177309
+2030,1,CO2EMT,winter.day,514.3909151513791
+2030,1,GASPRD,winter.peak,-124.10114062500001
+2030,1,GASNAT,winter.peak,118.1915625
+2030,1,CO2EMT,winter.peak,302.15672953125005
+2030,1,GASPRD,winter.evening,-165.4681888237455
+2030,1,GASNAT,winter.evening,157.58875126070998
+2030,1,CO2EMT,winter.evening,402.87564259800513
+2030,1,GASPRD,peak.night,-0.0
+2030,1,GASNAT,peak.night,0.0
+2030,1,CO2EMT,peak.night,0.0
+2030,1,GASPRD,peak.day,-0.0
+2030,1,GASNAT,peak.day,0.0
+2030,1,CO2EMT,peak.day,0.0
+2030,1,GASPRD,peak.peak,-89.77643114820788
+2030,1,GASNAT,peak.peak,85.50136299829322
+2030,1,CO2EMT,peak.peak,218.58423450513664
+2030,1,GASPRD,peak.evening,-165.4681888237455
+2030,1,GASNAT,peak.evening,157.58875126070998
+2030,1,CO2EMT,peak.evening,402.87564259800513
+2030,1,GASPRD,summer.night,-0.0
+2030,1,GASNAT,summer.night,0.0
+2030,1,CO2EMT,summer.night,0.0
+2030,1,GASPRD,summer.day,-0.0
+2030,1,GASNAT,summer.day,0.0
+2030,1,CO2EMT,summer.day,0.0
+2030,1,GASPRD,summer.peak,-0.0
+2030,1,GASNAT,summer.peak,0.0
+2030,1,CO2EMT,summer.peak,0.0
+2030,1,GASPRD,summer.evening,-0.464792220809886
+2030,1,GASNAT,summer.evening,0.44265925791417715
+2030,1,CO2EMT,summer.evening,1.131658392857594
+2030,1,GASPRD,autumn.night,-0.0
+2030,1,GASNAT,autumn.night,0.0
+2030,1,CO2EMT,autumn.night,0.0
+2030,1,GASPRD,autumn.day,-0.0
+2030,1,GASNAT,autumn.day,0.0
+2030,1,CO2EMT,autumn.day,0.0
+2030,1,GASPRD,autumn.peak,-21.826856869734893
+2030,1,GASNAT,autumn.peak,20.78748273308085
+2030,1,CO2EMT,autumn.peak,53.1431996071212
+2030,1,GASPRD,autumn.evening,-165.4681888237455
+2030,1,GASNAT,autumn.evening,157.58875126070998
+2030,1,CO2EMT,autumn.evening,402.87564259800513
+2030,2,ELCTRI,winter.night,4.435312795545212
+2030,2,ELCTRI,winter.day,7.075379933645912
+2030,2,ELCTRI,winter.peak,1.9712501261051125
+2030,2,ELCTRI,winter.evening,2.5696653598405335
+2030,2,ELCTRI,peak.night,2.851272517283696
+2030,2,ELCTRI,peak.day,6.3713620320039785
+2030,2,ELCTRI,peak.peak,1.7776452018191917
+2030,2,ELCTRI,peak.evening,1.72484387381507
+2030,2,ELCTRI,summer.night,1.6368416242136155
+2030,2,ELCTRI,summer.day,3.2188600626534005
+2030,2,ELCTRI,summer.peak,1.0599640039494003
+2030,2,ELCTRI,summer.evening,0.7892279633502001
+2030,2,ELCTRI,autumn.night,3.203281465982185
+2030,2,ELCTRI,autumn.day,6.001752635595889
+2030,2,ELCTRI,autumn.peak,1.5488393825638174
+2030,2,ELCTRI,autumn.evening,1.9008483513729915
+2030,3,GASNAT,winter.night,-7.783808997678887
+2030,3,ELCTRI,winter.night,5.189205998452591
+2030,3,CO2EMT,winter.night,397.9861540513214
+2030,3,GASNAT,winter.day,-10.010898915527838
+2030,3,ELCTRI,winter.day,6.6739326103518914
+2030,3,CO2EMT,winter.day,511.8572615509383
+2030,3,GASNAT,winter.peak,-2.468688831431171
+2030,3,ELCTRI,winter.peak,1.6457925542874472
+2030,3,CO2EMT,winter.peak,126.22405995107574
+2030,3,GASNAT,winter.evening,-3.3795873788970026
+2030,3,ELCTRI,winter.evening,2.253058252598002
+2030,3,CO2EMT,winter.evening,172.79830268300373
+2030,3,GASNAT,peak.night,-8.382740663321101
+2030,3,ELCTRI,peak.night,5.588493775547401
+2030,3,CO2EMT,peak.night,428.60953011560787
+2030,3,GASNAT,peak.day,-11.066925767990737
+2030,3,ELCTRI,peak.day,7.3779505119938245
+2030,3,CO2EMT,peak.day,565.8519145173664
+2030,3,GASNAT,peak.peak,-3.520722822271213
+2030,3,ELCTRI,peak.peak,2.3471485481808085
+2030,3,CO2EMT,peak.peak,180.0145579027271
+2030,3,GASNAT,peak.evening,-4.646819607935198
+2030,3,ELCTRI,peak.evening,3.0978797386234653
+2030,3,CO2EMT,peak.evening,237.59188655372665
+2030,3,GASNAT,summer.night,-0.0
+2030,3,ELCTRI,summer.night,0.0
+2030,3,CO2EMT,summer.night,0.0
+2030,3,GASNAT,summer.day,-0.0
+2030,3,ELCTRI,summer.day,0.0
+2030,3,CO2EMT,summer.day,0.0
+2030,3,GASNAT,summer.peak,-0.0
+2030,3,ELCTRI,summer.peak,0.0
+2030,3,CO2EMT,summer.peak,0.0
+2030,3,GASNAT,summer.evening,-0.0
+2030,3,ELCTRI,summer.evening,0.0
+2030,3,CO2EMT,summer.evening,0.0
+2030,3,GASNAT,autumn.night,-7.854727240273368
+2030,3,ELCTRI,autumn.night,5.236484826848912
+2030,3,CO2EMT,autumn.night,401.61220379517727
+2030,3,GASNAT,autumn.day,-11.621339862602872
+2030,3,ELCTRI,autumn.day,7.747559908401914
+2030,3,CO2EMT,autumn.day,594.1991071748848
+2030,3,GASNAT,autumn.peak,-3.592603125
+2030,3,ELCTRI,autumn.peak,2.39506875
+2030,3,CO2EMT,autumn.peak,183.68979778124998
+2030,3,GASNAT,autumn.evening,-4.3828128915983156
+2030,3,ELCTRI,autumn.evening,2.921875261065544
+2030,3,CO2EMT,autumn.evening,224.09322314742187
+2030,4,GASNAT,winter.night,-43.845148578709995
+2030,4,RSHEAT,winter.night,38.1262161554
+2030,4,CO2EMT,winter.night,2241.8024468294425
+2030,4,GASNAT,winter.day,-219.27676569358
+2030,4,RSHEAT,winter.day,190.6754484292
+2030,4,CO2EMT,winter.day,11211.621029912745
+2030,4,GASNAT,winter.peak,-104.21874999999999
+2030,4,RSHEAT,winter.peak,90.625
+2030,4,CO2EMT,winter.peak,5328.704687500001
+2030,4,GASNAT,winter.evening,-71.44063561968
+2030,4,RSHEAT,winter.evening,62.1222918432
+2030,4,CO2EMT,winter.evening,3652.7596992342387
+2030,4,GASNAT,peak.night,-0.0
+2030,4,RSHEAT,peak.night,0.0
+2030,4,CO2EMT,peak.night,0.0
+2030,4,GASNAT,peak.day,-96.21279149075198
+2030,4,RSHEAT,peak.day,83.66329694848
+2030,4,CO2EMT,peak.day,4919.3600289221495
+2030,4,GASNAT,peak.peak,-66.12685298630602
+2030,4,RSHEAT,peak.peak,57.50161129244002
+2030,4,CO2EMT,peak.peak,3381.0659931898267
+2030,4,GASNAT,peak.evening,-29.670500177964005
+2030,4,RSHEAT,peak.evening,25.800434937360006
+2030,4,CO2EMT,peak.evening,1517.0526740992998
+2030,4,GASNAT,summer.night,-0.0
+2030,4,RSHEAT,summer.night,0.0
+2030,4,CO2EMT,summer.night,0.0
+2030,4,GASNAT,summer.day,-0.0
+2030,4,RSHEAT,summer.day,0.0
+2030,4,CO2EMT,summer.day,0.0
+2030,4,GASNAT,summer.peak,-0.0
+2030,4,RSHEAT,summer.peak,0.0
+2030,4,CO2EMT,summer.peak,0.0
+2030,4,GASNAT,summer.evening,-0.0
+2030,4,RSHEAT,summer.evening,0.0
+2030,4,CO2EMT,summer.evening,0.0
+2030,4,GASNAT,autumn.night,-0.0
+2030,4,RSHEAT,autumn.night,0.0
+2030,4,CO2EMT,autumn.night,0.0
+2030,4,GASNAT,autumn.day,-67.97422666003601
+2030,4,RSHEAT,autumn.day,59.10802318264001
+2030,4,CO2EMT,autumn.day,3475.5222091276414
+2030,4,GASNAT,autumn.peak,-50.017432516103
+2030,4,RSHEAT,autumn.peak,43.49341957922
+2030,4,CO2EMT,autumn.peak,2557.391324548347
+2030,4,GASNAT,autumn.evening,-19.376214580347
+2030,4,RSHEAT,autumn.evening,16.84888224378
+2030,4,CO2EMT,autumn.evening,990.7058514931422
+2030,5,ELCTRI,winter.night,-9.624518793997803
+2030,5,RSHEAT,winter.night,29.165208466660005
+2030,5,ELCTRI,winter.day,-13.749312543997803
+2030,5,RSHEAT,winter.day,41.664583466660005
+2030,5,ELCTRI,winter.peak,-4.12479375
+2030,5,RSHEAT,winter.peak,12.499375
+2030,5,ELCTRI,winter.evening,-5.499725043997801
+2030,5,RSHEAT,winter.evening,16.66583346666
+2030,5,ELCTRI,peak.night,-9.624518793997803
+2030,5,RSHEAT,peak.night,29.165208466660005
+2030,5,ELCTRI,peak.day,-13.749312543997803
+2030,5,RSHEAT,peak.day,41.664583466660005
+2030,5,ELCTRI,peak.peak,-4.12479375
+2030,5,RSHEAT,peak.peak,12.499375
+2030,5,ELCTRI,peak.evening,-5.499725043997801
+2030,5,RSHEAT,peak.evening,16.66583346666
+2030,5,ELCTRI,summer.night,-1.9319477961564002
+2030,5,RSHEAT,summer.night,5.85438726108
+2030,5,ELCTRI,summer.day,-3.2188600626534005
+2030,5,RSHEAT,summer.day,9.75412140198
+2030,5,ELCTRI,summer.peak,-1.0599640039494003
+2030,5,RSHEAT,summer.peak,3.2120121331800005
+2030,5,ELCTRI,summer.evening,-0.7892279633502001
+2030,5,RSHEAT,summer.evening,2.39159988894
+2030,5,ELCTRI,autumn.night,-9.624518793997803
+2030,5,RSHEAT,autumn.night,29.165208466660005
+2030,5,ELCTRI,autumn.day,-13.749312543997803
+2030,5,RSHEAT,autumn.day,41.664583466660005
+2030,5,ELCTRI,autumn.peak,-4.12479375
+2030,5,RSHEAT,autumn.peak,12.499375
+2030,5,ELCTRI,autumn.evening,-5.499725043997801
+2030,5,RSHEAT,autumn.evening,16.66583346666
+2030,6,GASNAT,winter.night,-0.0
+2030,6,RSHEAT,winter.night,0.0
+2030,6,CO2EMT,winter.night,0.0
+2030,6,GASNAT,winter.day,-0.0
+2030,6,RSHEAT,winter.day,0.0
+2030,6,CO2EMT,winter.day,0.0
+2030,6,GASNAT,winter.peak,-12.787942711186016
+2030,6,RSHEAT,winter.peak,11.119950183640015
+2030,6,CO2EMT,winter.peak,653.8475108229411
+2030,6,GASNAT,winter.evening,-0.0
+2030,6,RSHEAT,winter.evening,0.0
+2030,6,CO2EMT,winter.evening,0.0
+2030,6,GASNAT,peak.night,-20.670129843374
+2030,6,RSHEAT,peak.night,17.97402595076
+2030,6,CO2EMT,peak.night,1056.8637388917127
+2030,6,GASNAT,peak.day,-0.0
+2030,6,RSHEAT,peak.day,0.0
+2030,6,CO2EMT,peak.day,0.0
+2030,6,GASNAT,peak.peak,-0.0
+2030,6,RSHEAT,peak.peak,0.0
+2030,6,CO2EMT,peak.peak,0.0
+2030,6,GASNAT,peak.evening,-0.0
+2030,6,RSHEAT,peak.evening,0.0
+2030,6,CO2EMT,peak.evening,0.0
+2030,6,GASNAT,summer.night,-0.0
+2030,6,RSHEAT,summer.night,0.0
+2030,6,CO2EMT,summer.night,0.0
+2030,6,GASNAT,summer.day,-0.0
+2030,6,RSHEAT,summer.day,0.0
+2030,6,CO2EMT,summer.day,0.0
+2030,6,GASNAT,summer.peak,-0.0
+2030,6,RSHEAT,summer.peak,0.0
+2030,6,CO2EMT,summer.peak,0.0
+2030,6,GASNAT,summer.evening,-0.0
+2030,6,RSHEAT,summer.evening,0.0
+2030,6,CO2EMT,summer.evening,0.0
+2030,6,GASNAT,autumn.night,-10.492917792587003
+2030,6,RSHEAT,autumn.night,9.124276341380003
+2030,6,CO2EMT,autumn.night,536.5028867349736
+2030,6,GASNAT,autumn.day,-0.0
+2030,6,RSHEAT,autumn.day,0.0
+2030,6,CO2EMT,autumn.day,0.0
+2030,6,GASNAT,autumn.peak,-0.0
+2030,6,RSHEAT,autumn.peak,0.0
+2030,6,CO2EMT,autumn.peak,0.0
+2030,6,GASNAT,autumn.evening,-0.0
+2030,6,RSHEAT,autumn.evening,0.0
+2030,6,CO2EMT,autumn.evening,0.0
+2030,7,GASNAT,winter.night,-0.0
+2030,7,ELCTRI,winter.night,0.0
+2030,7,CO2EMT,winter.night,0.0
+2030,7,GASNAT,winter.day,-0.0
+2030,7,ELCTRI,winter.day,0.0
+2030,7,CO2EMT,winter.day,0.0
+2030,7,GASNAT,winter.peak,-0.7616266044111606
+2030,7,ELCTRI,winter.peak,0.5077510696074404
+2030,7,CO2EMT,winter.peak,38.94196828354264
+2030,7,GASNAT,winter.evening,-1.0155021473388979
+2030,7,ELCTRI,winter.evening,0.6770014315592653
+2030,7,CO2EMT,winter.evening,51.92262479343785
+2030,7,GASNAT,peak.night,-1.7771287517500585
+2030,7,ELCTRI,peak.night,1.1847525011667057
+2030,7,CO2EMT,peak.night,90.86459307698048
+2030,7,GASNAT,peak.day,-0.0
+2030,7,ELCTRI,peak.day,0.0
+2030,7,CO2EMT,peak.day,0.0
+2030,7,GASNAT,peak.peak,-0.0
+2030,7,ELCTRI,peak.peak,0.0
+2030,7,CO2EMT,peak.peak,0.0
+2030,7,GASNAT,peak.evening,-1.0155021473388979
+2030,7,ELCTRI,peak.evening,0.6770014315592653
+2030,7,CO2EMT,peak.evening,51.92262479343785
+2030,7,GASNAT,summer.night,-0.44265925791417715
+2030,7,ELCTRI,summer.night,0.29510617194278477
+2030,7,CO2EMT,summer.night,22.633167857151875
+2030,7,GASNAT,summer.day,-0.0
+2030,7,ELCTRI,summer.day,0.0
+2030,7,CO2EMT,summer.day,0.0
+2030,7,GASNAT,summer.peak,-0.0
+2030,7,ELCTRI,summer.peak,0.0
+2030,7,CO2EMT,summer.peak,0.0
+2030,7,GASNAT,summer.evening,-0.0
+2030,7,ELCTRI,summer.evening,0.0
+2030,7,CO2EMT,summer.evening,0.0
+2030,7,GASNAT,autumn.night,-1.7771287517500585
+2030,7,ELCTRI,autumn.night,1.1847525011667057
+2030,7,CO2EMT,autumn.night,90.86459307698048
+2030,7,GASNAT,autumn.day,-0.0
+2030,7,ELCTRI,autumn.day,0.0
+2030,7,CO2EMT,autumn.day,0.0
+2030,7,GASNAT,autumn.peak,-0.27132842615427455
+2030,7,ELCTRI,autumn.peak,0.18088561743618303
+2030,7,CO2EMT,autumn.peak,13.873022429268056
+2030,7,GASNAT,autumn.evening,-1.0155021473388979
+2030,7,ELCTRI,autumn.evening,0.6770014315592653
+2030,7,CO2EMT,autumn.evening,51.92262479343785
+2040,0,GASPRD,winter.night,0.0
+2040,0,CO2EMT,winter.night,0.0
+2040,0,GASPRD,winter.day,360.96396982311984
+2040,0,CO2EMT,winter.day,1845.6087777056118
+2040,0,GASPRD,winter.peak,125.070625
+2040,0,CO2EMT,winter.peak,639.486105625
+2040,0,GASPRD,winter.evening,166.76083466742
+2040,0,CO2EMT,winter.evening,852.6481476545185
+2040,0,GASPRD,peak.night,0.0
+2040,0,CO2EMT,peak.night,0.0
+2040,0,GASPRD,peak.day,85.71569060312174
+2040,0,CO2EMT,peak.day,438.2643260537615
+2040,0,GASPRD,peak.peak,125.070625
+2040,0,CO2EMT,peak.peak,639.486105625
+2040,0,GASPRD,peak.evening,166.76083466742
+2040,0,CO2EMT,peak.evening,852.6481476545185
+2040,0,GASPRD,summer.night,0.0
+2040,0,CO2EMT,summer.night,0.0
+2040,0,GASPRD,summer.day,0.0
+2040,0,CO2EMT,summer.day,0.0
+2040,0,GASPRD,summer.peak,0.0
+2040,0,CO2EMT,summer.peak,0.0
+2040,0,GASPRD,summer.evening,28.106738155604855
+2040,0,CO2EMT,summer.evening,143.70975218960763
+2040,0,GASPRD,autumn.night,0.0
+2040,0,CO2EMT,autumn.night,0.0
+2040,0,GASPRD,autumn.day,11.030571054582595
+2040,0,CO2EMT,autumn.day,56.39930980208081
+2040,0,GASPRD,autumn.peak,125.070625
+2040,0,CO2EMT,autumn.peak,639.486105625
+2040,0,GASPRD,autumn.evening,166.76083466742
+2040,0,CO2EMT,autumn.evening,852.6481476545185
+2040,1,GASPRD,winter.night,-289.5693294487456
+2040,1,GASNAT,winter.night,275.78031376071004
+2040,1,CO2EMT,winter.night,705.0323721292552
+2040,1,GASPRD,winter.day,-73.65677059304869
+2040,1,GASNAT,winter.day,70.14930532671303
+2040,1,CO2EMT,winter.day,179.33669906774188
+2040,1,GASPRD,winter.peak,-124.10114062500001
+2040,1,GASNAT,winter.peak,118.1915625
+2040,1,CO2EMT,winter.peak,302.15672953125005
+2040,1,GASPRD,winter.evening,-165.4681888237455
+2040,1,GASNAT,winter.evening,157.58875126070998
+2040,1,CO2EMT,winter.evening,402.87564259800513
+2040,1,GASPRD,peak.night,-0.0
+2040,1,GASNAT,peak.night,0.0
+2040,1,CO2EMT,peak.night,0.0
+2040,1,GASPRD,peak.day,-87.97782082179623
+2040,1,GASNAT,peak.day,83.78840078266308
+2040,1,CO2EMT,peak.day,214.20504660087818
+2040,1,GASPRD,peak.peak,-124.10114062500001
+2040,1,GASNAT,peak.peak,118.1915625
+2040,1,CO2EMT,peak.peak,302.15672953125005
+2040,1,GASPRD,peak.evening,-165.4681888237455
+2040,1,GASNAT,peak.evening,157.58875126070998
+2040,1,CO2EMT,peak.evening,402.87564259800513
+2040,1,GASPRD,summer.night,-0.0
+2040,1,GASNAT,summer.night,0.0
+2040,1,CO2EMT,summer.night,0.0
+2040,1,GASPRD,summer.day,-0.0
+2040,1,GASNAT,summer.day,0.0
+2040,1,CO2EMT,summer.day,0.0
+2040,1,GASPRD,summer.peak,-0.0
+2040,1,GASNAT,summer.peak,0.0
+2040,1,CO2EMT,summer.peak,0.0
+2040,1,GASPRD,summer.evening,-28.106738155604855
+2040,1,GASNAT,summer.evening,26.768322052957004
+2040,1,CO2EMT,summer.evening,68.43321532838459
+2040,1,GASPRD,autumn.night,-0.0
+2040,1,GASNAT,autumn.night,0.0
+2040,1,CO2EMT,autumn.night,0.0
+2040,1,GASPRD,autumn.day,-13.292701273257066
+2040,1,GASNAT,autumn.day,12.659715498340063
+2040,1,CO2EMT,autumn.day,32.36456267150637
+2040,1,GASPRD,autumn.peak,-124.10114062500001
+2040,1,GASNAT,autumn.peak,118.1915625
+2040,1,CO2EMT,autumn.peak,302.15672953125005
+2040,1,GASPRD,autumn.evening,-165.4681888237455
+2040,1,GASNAT,autumn.evening,157.58875126070998
+2040,1,CO2EMT,autumn.evening,402.87564259800513
+2040,6,GASNAT,winter.night,-29.838533129172095
+2040,6,RSHEAT,winter.night,25.94655054710617
+2040,6,CO2EMT,winter.night,1525.6441988945694
+2040,6,GASNAT,winter.day,-42.62647584035811
+2040,6,RSHEAT,winter.day,37.066500730746185
+2040,6,CO2EMT,winter.day,2179.4917097175103
+2040,6,GASNAT,winter.peak,-12.787942711186016
+2040,6,RSHEAT,winter.peak,11.119950183640015
+2040,6,CO2EMT,winter.peak,653.8475108229411
+2040,6,GASNAT,winter.evening,-17.050590417986076
+2040,6,RSHEAT,winter.evening,14.826600363466154
+2040,6,CO2EMT,winter.evening,871.7966880716282
+2040,6,GASNAT,peak.night,-29.838533129172095
+2040,6,RSHEAT,peak.night,25.94655054710617
+2040,6,CO2EMT,peak.night,1525.6441988945694
+2040,6,GASNAT,peak.day,-42.62647584035811
+2040,6,RSHEAT,peak.day,37.066500730746185
+2040,6,CO2EMT,peak.day,2179.4917097175103
+2040,6,GASNAT,peak.peak,-12.787942711186016
+2040,6,RSHEAT,peak.peak,11.119950183640015
+2040,6,CO2EMT,peak.peak,653.8475108229411
+2040,6,GASNAT,peak.evening,-17.050590417986076
+2040,6,RSHEAT,peak.evening,14.826600363466154
+2040,6,CO2EMT,peak.evening,871.7966880716282
+2040,6,GASNAT,summer.night,-0.0
+2040,6,RSHEAT,summer.night,0.0
+2040,6,CO2EMT,summer.night,0.0
+2040,6,GASNAT,summer.day,-0.0
+2040,6,RSHEAT,summer.day,0.0
+2040,6,CO2EMT,summer.day,0.0
+2040,6,GASNAT,summer.peak,-0.0
+2040,6,RSHEAT,summer.peak,0.0
+2040,6,CO2EMT,summer.peak,0.0
+2040,6,GASNAT,summer.evening,-0.0
+2040,6,RSHEAT,summer.evening,0.0
+2040,6,CO2EMT,summer.evening,0.0
+2040,6,GASNAT,autumn.night,-29.838533129172095
+2040,6,RSHEAT,autumn.night,25.94655054710617
+2040,6,CO2EMT,autumn.night,1525.6441988945694
+2040,6,GASNAT,autumn.day,-42.62647584035811
+2040,6,RSHEAT,autumn.day,37.066500730746185
+2040,6,CO2EMT,autumn.day,2179.4917097175103
+2040,6,GASNAT,autumn.peak,-12.787942711186016
+2040,6,RSHEAT,autumn.peak,11.119950183640015
+2040,6,CO2EMT,autumn.peak,653.8475108229411
+2040,6,GASNAT,autumn.evening,-17.050590417986076
+2040,6,RSHEAT,autumn.evening,14.826600363466154
+2040,6,CO2EMT,autumn.evening,871.7966880716282
+2040,8,GASNAT,winter.night,-55.07888519119691
+2040,8,RSHEAT,winter.night,47.894682774953836
+2040,8,CO2EMT,winter.night,2816.183399825898
+2040,8,GASNAT,winter.day,-250.5715919948809
+2040,8,RSHEAT,winter.day,217.88834086511383
+2040,8,CO2EMT,winter.day,12811.72549869826
+2040,8,GASNAT,winter.peak,-131.38099422
+2040,8,RSHEAT,winter.peak,114.2443428
+2040,8,CO2EMT,winter.peak,6717.5102344686
+2040,8,GASNAT,winter.evening,-82.37491934335293
+2040,8,RSHEAT,winter.evening,71.63036464639386
+2040,8,CO2EMT,winter.evening,4211.829626025636
+2040,8,GASNAT,peak.night,-29.648126735860906
+2040,8,RSHEAT,peak.night,25.780979770313834
+2040,8,CO2EMT,peak.night,1515.9087200045683
+2040,8,GASNAT,peak.day,-115.5291897320529
+2040,8,RSHEAT,peak.day,100.46016498439383
+2040,8,CO2EMT,peak.day,5907.007470999865
+2040,8,GASNAT,peak.peak,-75.54876689511998
+2040,8,RSHEAT,peak.peak,65.6945799088
+2040,8,CO2EMT,peak.peak,3862.8084513474855
+2040,8,GASNAT,peak.evening,-36.53908908163693
+2040,8,RSHEAT,peak.evening,31.773120940553852
+2040,8,CO2EMT,peak.evening,1868.2436247440962
+2040,8,GASNAT,summer.night,-7.3878574402420005
+2040,8,RSHEAT,summer.night,6.424223861080001
+2040,8,CO2EMT,summer.night,377.7411509195735
+2040,8,GASNAT,summer.day,-12.309069277277
+2040,8,RSHEAT,summer.day,10.70353850198
+2040,8,CO2EMT,summer.day,629.362712147173
+2040,8,GASNAT,summer.peak,-4.053351218157
+2040,8,RSHEAT,summer.peak,3.5246532331800005
+2040,8,CO2EMT,summer.peak,207.24784778436745
+2040,8,GASNAT,summer.evening,-3.0180441172809997
+2040,8,RSHEAT,summer.evening,2.62438618894
+2040,8,CO2EMT,summer.evening,154.31259571657753
+2040,8,GASNAT,autumn.night,-18.480316070073908
+2040,8,RSHEAT,autumn.night,16.069840060933835
+2040,8,CO2EMT,autumn.night,944.898560662879
+2040,8,GASNAT,autumn.day,-84.54202508133689
+2040,8,RSHEAT,autumn.day,73.51480441855382
+2040,8,CO2EMT,autumn.day,4322.633742408756
+2040,8,GASNAT,autumn.peak,-57.871336489916985
+2040,8,RSHEAT,autumn.peak,50.32290129557999
+2040,8,CO2EMT,autumn.peak,2958.9614347294555
+2040,8,GASNAT,autumn.evening,-25.24280951901992
+2040,8,RSHEAT,autumn.evening,21.950269146973845
+2040,8,CO2EMT,autumn.evening,1290.6648507074885
diff --git a/tests/data/simple_marginal/commodity_prices.csv b/tests/data/simple_marginal/commodity_prices.csv
new file mode 100644
index 000000000..a9b8ee0fc
--- /dev/null
+++ b/tests/data/simple_marginal/commodity_prices.csv
@@ -0,0 +1,177 @@
+milestone_year,commodity_id,region_id,time_slice,price
+2020,GASPRD,GBR,winter.night,2.20452
+2020,GASPRD,GBR,winter.day,2.20452
+2020,GASPRD,GBR,winter.peak,2.20452
+2020,GASPRD,GBR,winter.evening,2.20452
+2020,GASPRD,GBR,peak.night,2.20452
+2020,GASPRD,GBR,peak.day,2.20452
+2020,GASPRD,GBR,peak.peak,2.20452
+2020,GASPRD,GBR,peak.evening,2.20452
+2020,GASPRD,GBR,summer.night,2.20452
+2020,GASPRD,GBR,summer.day,2.20452
+2020,GASPRD,GBR,summer.peak,2.20452
+2020,GASPRD,GBR,summer.evening,2.20452
+2020,GASPRD,GBR,autumn.night,2.20452
+2020,GASPRD,GBR,autumn.day,2.20452
+2020,GASPRD,GBR,autumn.peak,2.20452
+2020,GASPRD,GBR,autumn.evening,2.20452
+2020,GASNAT,GBR,winter.night,2.9170059999999998
+2020,GASNAT,GBR,winter.day,2.9170059999999998
+2020,GASNAT,GBR,winter.peak,2.9170059999999998
+2020,GASNAT,GBR,winter.evening,2.9170059999999998
+2020,GASNAT,GBR,peak.night,2.9170059999999998
+2020,GASNAT,GBR,peak.day,2.9170059999999998
+2020,GASNAT,GBR,peak.peak,2.9170059999999998
+2020,GASNAT,GBR,peak.evening,2.9170059999999998
+2020,GASNAT,GBR,summer.night,2.9170059999999998
+2020,GASNAT,GBR,summer.day,2.9170059999999998
+2020,GASNAT,GBR,summer.peak,2.9170059999999998
+2020,GASNAT,GBR,summer.evening,2.9170059999999998
+2020,GASNAT,GBR,autumn.night,2.9170059999999998
+2020,GASNAT,GBR,autumn.day,2.9170059999999998
+2020,GASNAT,GBR,autumn.peak,2.9170059999999998
+2020,GASNAT,GBR,autumn.evening,2.9170059999999998
+2020,RSHEAT,GBR,winter.night,5.8665369
+2020,RSHEAT,GBR,winter.day,5.8665369
+2020,RSHEAT,GBR,winter.peak,5.8665369
+2020,RSHEAT,GBR,winter.evening,5.8665369
+2020,RSHEAT,GBR,peak.night,5.8665369
+2020,RSHEAT,GBR,peak.day,5.8665369
+2020,RSHEAT,GBR,peak.peak,5.8665369
+2020,RSHEAT,GBR,peak.evening,5.8665369
+2020,RSHEAT,GBR,summer.night,2.80779197
+2020,RSHEAT,GBR,summer.day,0.30200000000000005
+2020,RSHEAT,GBR,summer.peak,0.30200000000000005
+2020,RSHEAT,GBR,summer.evening,0.30200000000000005
+2020,RSHEAT,GBR,autumn.night,5.8665369
+2020,RSHEAT,GBR,autumn.day,5.8665369
+2020,RSHEAT,GBR,autumn.peak,5.8665369
+2020,RSHEAT,GBR,autumn.evening,5.8665369
+2020,ELCTRI,GBR,winter.night,7.993308999999999
+2020,ELCTRI,GBR,winter.day,7.993308999999999
+2020,ELCTRI,GBR,winter.peak,7.993308999999999
+2020,ELCTRI,GBR,winter.evening,7.993308999999999
+2020,ELCTRI,GBR,peak.night,7.993308999999999
+2020,ELCTRI,GBR,peak.day,7.993308999999999
+2020,ELCTRI,GBR,peak.peak,7.993308999999999
+2020,ELCTRI,GBR,peak.evening,7.993308999999999
+2020,ELCTRI,GBR,summer.night,7.993308999999999
+2020,ELCTRI,GBR,summer.day,0.4
+2020,ELCTRI,GBR,summer.peak,0.4
+2020,ELCTRI,GBR,summer.evening,0.4
+2020,ELCTRI,GBR,autumn.night,7.993308999999999
+2020,ELCTRI,GBR,autumn.day,7.993308999999999
+2020,ELCTRI,GBR,autumn.peak,7.993308999999999
+2020,ELCTRI,GBR,autumn.evening,7.993308999999999
+2030,GASPRD,GBR,winter.night,2.20452
+2030,GASPRD,GBR,winter.day,2.20452
+2030,GASPRD,GBR,winter.peak,2.20452
+2030,GASPRD,GBR,winter.evening,2.20452
+2030,GASPRD,GBR,peak.night,2.20452
+2030,GASPRD,GBR,peak.day,2.20452
+2030,GASPRD,GBR,peak.peak,2.20452
+2030,GASPRD,GBR,peak.evening,2.20452
+2030,GASPRD,GBR,summer.night,2.20452
+2030,GASPRD,GBR,summer.day,2.20452
+2030,GASPRD,GBR,summer.peak,2.20452
+2030,GASPRD,GBR,summer.evening,2.20452
+2030,GASPRD,GBR,autumn.night,2.20452
+2030,GASPRD,GBR,autumn.day,2.20452
+2030,GASPRD,GBR,autumn.peak,2.20452
+2030,GASPRD,GBR,autumn.evening,2.20452
+2030,GASNAT,GBR,winter.night,2.9170059999999998
+2030,GASNAT,GBR,winter.day,2.9170059999999998
+2030,GASNAT,GBR,winter.peak,2.9170059999999998
+2030,GASNAT,GBR,winter.evening,2.9170059999999998
+2030,GASNAT,GBR,peak.night,2.9170059999999998
+2030,GASNAT,GBR,peak.day,2.9170059999999998
+2030,GASNAT,GBR,peak.peak,2.9170059999999998
+2030,GASNAT,GBR,peak.evening,2.9170059999999998
+2030,GASNAT,GBR,summer.night,2.9170059999999998
+2030,GASNAT,GBR,summer.day,2.9170059999999998
+2030,GASNAT,GBR,summer.peak,2.9170059999999998
+2030,GASNAT,GBR,summer.evening,2.9170059999999998
+2030,GASNAT,GBR,autumn.night,2.9170059999999998
+2030,GASNAT,GBR,autumn.day,2.9170059999999998
+2030,GASNAT,GBR,autumn.peak,2.9170059999999998
+2030,GASNAT,GBR,autumn.evening,2.9170059999999998
+2030,RSHEAT,GBR,winter.night,5.8665369
+2030,RSHEAT,GBR,winter.day,5.8665369
+2030,RSHEAT,GBR,winter.peak,5.8665369
+2030,RSHEAT,GBR,winter.evening,5.8665369
+2030,RSHEAT,GBR,peak.night,5.8665369
+2030,RSHEAT,GBR,peak.day,5.8665369
+2030,RSHEAT,GBR,peak.peak,5.8665369
+2030,RSHEAT,GBR,peak.evening,5.8665369
+2030,RSHEAT,GBR,summer.night,2.80779197
+2030,RSHEAT,GBR,summer.day,0.30200000000000005
+2030,RSHEAT,GBR,summer.peak,0.30200000000000005
+2030,RSHEAT,GBR,summer.evening,0.30200000000000005
+2030,RSHEAT,GBR,autumn.night,5.8665369
+2030,RSHEAT,GBR,autumn.day,5.8665369
+2030,RSHEAT,GBR,autumn.peak,5.8665369
+2030,RSHEAT,GBR,autumn.evening,5.8665369
+2030,ELCTRI,GBR,winter.night,7.993308999999999
+2030,ELCTRI,GBR,winter.day,7.993308999999999
+2030,ELCTRI,GBR,winter.peak,7.993308999999999
+2030,ELCTRI,GBR,winter.evening,7.993308999999999
+2030,ELCTRI,GBR,peak.night,7.993308999999999
+2030,ELCTRI,GBR,peak.day,7.993308999999999
+2030,ELCTRI,GBR,peak.peak,7.993308999999999
+2030,ELCTRI,GBR,peak.evening,7.993308999999999
+2030,ELCTRI,GBR,summer.night,7.993308999999999
+2030,ELCTRI,GBR,summer.day,0.4
+2030,ELCTRI,GBR,summer.peak,0.4
+2030,ELCTRI,GBR,summer.evening,0.4
+2030,ELCTRI,GBR,autumn.night,7.993308999999999
+2030,ELCTRI,GBR,autumn.day,7.993308999999999
+2030,ELCTRI,GBR,autumn.peak,7.993308999999999
+2030,ELCTRI,GBR,autumn.evening,7.993308999999999
+2040,GASPRD,GBR,winter.night,2.20452
+2040,GASPRD,GBR,winter.day,2.20452
+2040,GASPRD,GBR,winter.peak,2.20452
+2040,GASPRD,GBR,winter.evening,2.20452
+2040,GASPRD,GBR,peak.night,2.20452
+2040,GASPRD,GBR,peak.day,2.20452
+2040,GASPRD,GBR,peak.peak,2.20452
+2040,GASPRD,GBR,peak.evening,2.20452
+2040,GASPRD,GBR,summer.night,2.20452
+2040,GASPRD,GBR,summer.day,2.20452
+2040,GASPRD,GBR,summer.peak,2.20452
+2040,GASPRD,GBR,summer.evening,2.20452
+2040,GASPRD,GBR,autumn.night,2.20452
+2040,GASPRD,GBR,autumn.day,2.20452
+2040,GASPRD,GBR,autumn.peak,2.20452
+2040,GASPRD,GBR,autumn.evening,2.20452
+2040,GASNAT,GBR,winter.night,2.9170059999999998
+2040,GASNAT,GBR,winter.day,2.9170059999999998
+2040,GASNAT,GBR,winter.peak,2.9170059999999998
+2040,GASNAT,GBR,winter.evening,2.9170059999999998
+2040,GASNAT,GBR,peak.night,2.9170059999999998
+2040,GASNAT,GBR,peak.day,2.9170059999999998
+2040,GASNAT,GBR,peak.peak,2.9170059999999998
+2040,GASNAT,GBR,peak.evening,2.9170059999999998
+2040,GASNAT,GBR,summer.night,2.9170059999999998
+2040,GASNAT,GBR,summer.day,2.9170059999999998
+2040,GASNAT,GBR,summer.peak,2.9170059999999998
+2040,GASNAT,GBR,summer.evening,2.9170059999999998
+2040,GASNAT,GBR,autumn.night,2.9170059999999998
+2040,GASNAT,GBR,autumn.day,2.9170059999999998
+2040,GASNAT,GBR,autumn.peak,2.9170059999999998
+2040,GASNAT,GBR,autumn.evening,2.9170059999999998
+2040,RSHEAT,GBR,winter.night,5.8665369
+2040,RSHEAT,GBR,winter.day,5.8665369
+2040,RSHEAT,GBR,winter.peak,5.8665369
+2040,RSHEAT,GBR,winter.evening,5.8665369
+2040,RSHEAT,GBR,peak.night,5.8665369
+2040,RSHEAT,GBR,peak.day,5.8665369
+2040,RSHEAT,GBR,peak.peak,5.8665369
+2040,RSHEAT,GBR,peak.evening,5.8665369
+2040,RSHEAT,GBR,summer.night,5.8665369
+2040,RSHEAT,GBR,summer.day,5.8665369
+2040,RSHEAT,GBR,summer.peak,5.8665369
+2040,RSHEAT,GBR,summer.evening,5.8665369
+2040,RSHEAT,GBR,autumn.night,5.8665369
+2040,RSHEAT,GBR,autumn.day,5.8665369
+2040,RSHEAT,GBR,autumn.peak,5.8665369
+2040,RSHEAT,GBR,autumn.evening,5.8665369
diff --git a/tests/data/two_outputs/commodity_prices.csv b/tests/data/two_outputs/commodity_prices.csv
index dd04d0b45..90b7e851e 100644
--- a/tests/data/two_outputs/commodity_prices.csv
+++ b/tests/data/two_outputs/commodity_prices.csv
@@ -31,22 +31,6 @@ milestone_year,commodity_id,region_id,time_slice,price
2020,GASPRD,GBR,autumn.day,2.20452
2020,GASPRD,GBR,autumn.peak,2.20452
2020,GASPRD,GBR,autumn.evening,2.20452
-2020,BIOPRD,GBR,winter.night,3.5869398412698414
-2020,BIOPRD,GBR,winter.day,3.5869398412698414
-2020,BIOPRD,GBR,winter.peak,3.5869398412698414
-2020,BIOPRD,GBR,winter.evening,3.5869398412698414
-2020,BIOPRD,GBR,peak.night,3.5869398412698414
-2020,BIOPRD,GBR,peak.day,3.5869398412698414
-2020,BIOPRD,GBR,peak.peak,3.5869398412698414
-2020,BIOPRD,GBR,peak.evening,3.5869398412698414
-2020,BIOPRD,GBR,summer.night,0.25
-2020,BIOPRD,GBR,summer.day,0.25
-2020,BIOPRD,GBR,summer.peak,0.25
-2020,BIOPRD,GBR,summer.evening,0.25
-2020,BIOPRD,GBR,autumn.night,3.586939841269842
-2020,BIOPRD,GBR,autumn.day,3.586939841269842
-2020,BIOPRD,GBR,autumn.peak,3.586939841269842
-2020,BIOPRD,GBR,autumn.evening,3.586939841269842
2020,GASOLI,GBR,winter.night,10.570299353027398
2020,GASOLI,GBR,winter.day,10.570299353027398
2020,GASOLI,GBR,winter.peak,10.570299353027398
@@ -95,22 +79,6 @@ milestone_year,commodity_id,region_id,time_slice,price
2020,GASNAT,GBR,autumn.day,2.9170059999999998
2020,GASNAT,GBR,autumn.peak,2.9170059999999998
2020,GASNAT,GBR,autumn.evening,2.9170059999999998
-2020,BIOPEL,GBR,winter.night,4.7221140833333335
-2020,BIOPEL,GBR,winter.day,4.7221140833333335
-2020,BIOPEL,GBR,winter.peak,4.7221140833333335
-2020,BIOPEL,GBR,winter.evening,4.7221140833333335
-2020,BIOPEL,GBR,peak.night,4.7221140833333335
-2020,BIOPEL,GBR,peak.day,4.7221140833333335
-2020,BIOPEL,GBR,peak.peak,4.7221140833333335
-2020,BIOPEL,GBR,peak.evening,4.7221140833333335
-2020,BIOPEL,GBR,summer.night,1.21832725
-2020,BIOPEL,GBR,summer.day,1.21832725
-2020,BIOPEL,GBR,summer.peak,1.21832725
-2020,BIOPEL,GBR,summer.evening,1.21832725
-2020,BIOPEL,GBR,autumn.night,4.7221140833333335
-2020,BIOPEL,GBR,autumn.day,4.7221140833333335
-2020,BIOPEL,GBR,autumn.peak,4.7221140833333335
-2020,BIOPEL,GBR,autumn.evening,4.7221140833333335
2020,ELCTRI,GBR,winter.night,7.993308999999999
2020,ELCTRI,GBR,winter.day,17.26223303030303
2020,ELCTRI,GBR,winter.peak,20.410312516918875
@@ -159,6 +127,38 @@ milestone_year,commodity_id,region_id,time_slice,price
2020,RSHEAT,GBR,autumn.day,5.8665369
2020,RSHEAT,GBR,autumn.peak,5.8665369
2020,RSHEAT,GBR,autumn.evening,5.8665369
+2020,BIOPRD,GBR,winter.night,3.5869398412698414
+2020,BIOPRD,GBR,winter.day,3.5869398412698414
+2020,BIOPRD,GBR,winter.peak,3.5869398412698414
+2020,BIOPRD,GBR,winter.evening,3.5869398412698414
+2020,BIOPRD,GBR,peak.night,3.5869398412698414
+2020,BIOPRD,GBR,peak.day,3.5869398412698414
+2020,BIOPRD,GBR,peak.peak,3.5869398412698414
+2020,BIOPRD,GBR,peak.evening,3.5869398412698414
+2020,BIOPRD,GBR,summer.night,0.25
+2020,BIOPRD,GBR,summer.day,0.25
+2020,BIOPRD,GBR,summer.peak,0.25
+2020,BIOPRD,GBR,summer.evening,0.25
+2020,BIOPRD,GBR,autumn.night,3.586939841269842
+2020,BIOPRD,GBR,autumn.day,3.586939841269842
+2020,BIOPRD,GBR,autumn.peak,3.586939841269842
+2020,BIOPRD,GBR,autumn.evening,3.586939841269842
+2020,BIOPEL,GBR,winter.night,4.7221140833333335
+2020,BIOPEL,GBR,winter.day,4.7221140833333335
+2020,BIOPEL,GBR,winter.peak,4.7221140833333335
+2020,BIOPEL,GBR,winter.evening,4.7221140833333335
+2020,BIOPEL,GBR,peak.night,4.7221140833333335
+2020,BIOPEL,GBR,peak.day,4.7221140833333335
+2020,BIOPEL,GBR,peak.peak,4.7221140833333335
+2020,BIOPEL,GBR,peak.evening,4.7221140833333335
+2020,BIOPEL,GBR,summer.night,1.21832725
+2020,BIOPEL,GBR,summer.day,1.21832725
+2020,BIOPEL,GBR,summer.peak,1.21832725
+2020,BIOPEL,GBR,summer.evening,1.21832725
+2020,BIOPEL,GBR,autumn.night,4.7221140833333335
+2020,BIOPEL,GBR,autumn.day,4.7221140833333335
+2020,BIOPEL,GBR,autumn.peak,4.7221140833333335
+2020,BIOPEL,GBR,autumn.evening,4.7221140833333335
2030,OILCRD,GBR,winter.night,3.072868
2030,OILCRD,GBR,winter.day,3.072868
2030,OILCRD,GBR,winter.peak,3.072868
@@ -191,22 +191,6 @@ milestone_year,commodity_id,region_id,time_slice,price
2030,GASPRD,GBR,autumn.day,2.20452
2030,GASPRD,GBR,autumn.peak,2.20452
2030,GASPRD,GBR,autumn.evening,2.20452
-2030,BIOPRD,GBR,winter.night,4.249632460317461
-2030,BIOPRD,GBR,winter.day,4.249632460317461
-2030,BIOPRD,GBR,winter.peak,4.249632460317461
-2030,BIOPRD,GBR,winter.evening,4.249632460317461
-2030,BIOPRD,GBR,peak.night,1.822057119047619
-2030,BIOPRD,GBR,peak.day,1.822057119047619
-2030,BIOPRD,GBR,peak.peak,1.822057119047619
-2030,BIOPRD,GBR,peak.evening,1.822057119047619
-2030,BIOPRD,GBR,summer.night,0.25
-2030,BIOPRD,GBR,summer.day,0.25
-2030,BIOPRD,GBR,summer.peak,0.25
-2030,BIOPRD,GBR,summer.evening,0.25
-2030,BIOPRD,GBR,autumn.night,1.822057119047619
-2030,BIOPRD,GBR,autumn.day,1.822057119047619
-2030,BIOPRD,GBR,autumn.peak,1.822057119047619
-2030,BIOPRD,GBR,autumn.evening,1.822057119047619
2030,GASOLI,GBR,winter.night,5.585457080000001
2030,GASOLI,GBR,winter.day,5.585457080000001
2030,GASOLI,GBR,winter.peak,5.585457080000001
@@ -255,22 +239,6 @@ milestone_year,commodity_id,region_id,time_slice,price
2030,GASNAT,GBR,autumn.day,2.9170059999999998
2030,GASNAT,GBR,autumn.peak,2.9170059999999998
2030,GASNAT,GBR,autumn.evening,2.9170059999999998
-2030,BIOPEL,GBR,winter.night,4.7221140833333335
-2030,BIOPEL,GBR,winter.day,4.7221140833333335
-2030,BIOPEL,GBR,winter.peak,4.7221140833333335
-2030,BIOPEL,GBR,winter.evening,4.7221140833333335
-2030,BIOPEL,GBR,peak.night,2.173159975
-2030,BIOPEL,GBR,peak.day,2.173159975
-2030,BIOPEL,GBR,peak.peak,2.173159975
-2030,BIOPEL,GBR,peak.evening,2.173159975
-2030,BIOPEL,GBR,summer.night,0.5225
-2030,BIOPEL,GBR,summer.day,0.5225
-2030,BIOPEL,GBR,summer.peak,0.5225
-2030,BIOPEL,GBR,summer.evening,0.5225
-2030,BIOPEL,GBR,autumn.night,2.173159975
-2030,BIOPEL,GBR,autumn.day,2.173159975
-2030,BIOPEL,GBR,autumn.peak,2.173159975
-2030,BIOPEL,GBR,autumn.evening,2.173159975
2030,ELCTRI,GBR,winter.night,7.993308999999999
2030,ELCTRI,GBR,winter.day,7.993308999999999
2030,ELCTRI,GBR,winter.peak,7.993308999999999
@@ -319,6 +287,38 @@ milestone_year,commodity_id,region_id,time_slice,price
2030,RSHEAT,GBR,autumn.day,5.8665369
2030,RSHEAT,GBR,autumn.peak,5.8665369
2030,RSHEAT,GBR,autumn.evening,5.8665369
+2030,BIOPRD,GBR,winter.night,4.249632460317461
+2030,BIOPRD,GBR,winter.day,4.249632460317461
+2030,BIOPRD,GBR,winter.peak,4.249632460317461
+2030,BIOPRD,GBR,winter.evening,4.249632460317461
+2030,BIOPRD,GBR,peak.night,1.822057119047619
+2030,BIOPRD,GBR,peak.day,1.822057119047619
+2030,BIOPRD,GBR,peak.peak,1.822057119047619
+2030,BIOPRD,GBR,peak.evening,1.822057119047619
+2030,BIOPRD,GBR,summer.night,0.25
+2030,BIOPRD,GBR,summer.day,0.25
+2030,BIOPRD,GBR,summer.peak,0.25
+2030,BIOPRD,GBR,summer.evening,0.25
+2030,BIOPRD,GBR,autumn.night,1.822057119047619
+2030,BIOPRD,GBR,autumn.day,1.822057119047619
+2030,BIOPRD,GBR,autumn.peak,1.822057119047619
+2030,BIOPRD,GBR,autumn.evening,1.822057119047619
+2030,BIOPEL,GBR,winter.night,4.7221140833333335
+2030,BIOPEL,GBR,winter.day,4.7221140833333335
+2030,BIOPEL,GBR,winter.peak,4.7221140833333335
+2030,BIOPEL,GBR,winter.evening,4.7221140833333335
+2030,BIOPEL,GBR,peak.night,2.173159975
+2030,BIOPEL,GBR,peak.day,2.173159975
+2030,BIOPEL,GBR,peak.peak,2.173159975
+2030,BIOPEL,GBR,peak.evening,2.173159975
+2030,BIOPEL,GBR,summer.night,0.5225
+2030,BIOPEL,GBR,summer.day,0.5225
+2030,BIOPEL,GBR,summer.peak,0.5225
+2030,BIOPEL,GBR,summer.evening,0.5225
+2030,BIOPEL,GBR,autumn.night,2.173159975
+2030,BIOPEL,GBR,autumn.day,2.173159975
+2030,BIOPEL,GBR,autumn.peak,2.173159975
+2030,BIOPEL,GBR,autumn.evening,2.173159975
2040,OILCRD,GBR,winter.night,3.072868
2040,OILCRD,GBR,winter.day,3.072868
2040,OILCRD,GBR,winter.peak,3.072868
@@ -351,22 +351,6 @@ milestone_year,commodity_id,region_id,time_slice,price
2040,GASPRD,GBR,autumn.day,2.20452
2040,GASPRD,GBR,autumn.peak,2.20452
2040,GASPRD,GBR,autumn.evening,2.20452
-2040,BIOPRD,GBR,winter.night,0.25
-2040,BIOPRD,GBR,winter.day,0.25
-2040,BIOPRD,GBR,winter.peak,0.25
-2040,BIOPRD,GBR,winter.evening,0.25
-2040,BIOPRD,GBR,peak.night,0.25
-2040,BIOPRD,GBR,peak.day,0.25
-2040,BIOPRD,GBR,peak.peak,0.25
-2040,BIOPRD,GBR,peak.evening,0.25
-2040,BIOPRD,GBR,summer.night,0.25
-2040,BIOPRD,GBR,summer.day,0.25
-2040,BIOPRD,GBR,summer.peak,0.25
-2040,BIOPRD,GBR,summer.evening,0.25
-2040,BIOPRD,GBR,autumn.night,0.25
-2040,BIOPRD,GBR,autumn.day,0.25
-2040,BIOPRD,GBR,autumn.peak,0.25
-2040,BIOPRD,GBR,autumn.evening,0.25
2040,GASOLI,GBR,winter.night,5.585457080000001
2040,GASOLI,GBR,winter.day,5.585457080000001
2040,GASOLI,GBR,winter.peak,5.585457080000001
@@ -399,22 +383,6 @@ milestone_year,commodity_id,region_id,time_slice,price
2040,GASNAT,GBR,autumn.day,2.9170059999999998
2040,GASNAT,GBR,autumn.peak,2.9170059999999998
2040,GASNAT,GBR,autumn.evening,2.9170059999999998
-2040,BIOPEL,GBR,winter.night,0.5225
-2040,BIOPEL,GBR,winter.day,0.5225
-2040,BIOPEL,GBR,winter.peak,0.5225
-2040,BIOPEL,GBR,winter.evening,0.5225
-2040,BIOPEL,GBR,peak.night,0.5225
-2040,BIOPEL,GBR,peak.day,0.5225
-2040,BIOPEL,GBR,peak.peak,0.5225
-2040,BIOPEL,GBR,peak.evening,0.5225
-2040,BIOPEL,GBR,summer.night,0.5225
-2040,BIOPEL,GBR,summer.day,0.5225
-2040,BIOPEL,GBR,summer.peak,0.5225
-2040,BIOPEL,GBR,summer.evening,0.5225
-2040,BIOPEL,GBR,autumn.night,0.5225
-2040,BIOPEL,GBR,autumn.day,0.5225
-2040,BIOPEL,GBR,autumn.peak,0.5225
-2040,BIOPEL,GBR,autumn.evening,0.5225
2040,ELCTRI,GBR,winter.night,7.993308999999999
2040,ELCTRI,GBR,winter.day,7.993308999999999
2040,ELCTRI,GBR,winter.peak,7.993308999999999
@@ -463,3 +431,35 @@ milestone_year,commodity_id,region_id,time_slice,price
2040,RSHEAT,GBR,autumn.day,0.827
2040,RSHEAT,GBR,autumn.peak,0.827
2040,RSHEAT,GBR,autumn.evening,0.827
+2040,BIOPRD,GBR,winter.night,0.25
+2040,BIOPRD,GBR,winter.day,0.25
+2040,BIOPRD,GBR,winter.peak,0.25
+2040,BIOPRD,GBR,winter.evening,0.25
+2040,BIOPRD,GBR,peak.night,0.25
+2040,BIOPRD,GBR,peak.day,0.25
+2040,BIOPRD,GBR,peak.peak,0.25
+2040,BIOPRD,GBR,peak.evening,0.25
+2040,BIOPRD,GBR,summer.night,0.25
+2040,BIOPRD,GBR,summer.day,0.25
+2040,BIOPRD,GBR,summer.peak,0.25
+2040,BIOPRD,GBR,summer.evening,0.25
+2040,BIOPRD,GBR,autumn.night,0.25
+2040,BIOPRD,GBR,autumn.day,0.25
+2040,BIOPRD,GBR,autumn.peak,0.25
+2040,BIOPRD,GBR,autumn.evening,0.25
+2040,BIOPEL,GBR,winter.night,0.5225
+2040,BIOPEL,GBR,winter.day,0.5225
+2040,BIOPEL,GBR,winter.peak,0.5225
+2040,BIOPEL,GBR,winter.evening,0.5225
+2040,BIOPEL,GBR,peak.night,0.5225
+2040,BIOPEL,GBR,peak.day,0.5225
+2040,BIOPEL,GBR,peak.peak,0.5225
+2040,BIOPEL,GBR,peak.evening,0.5225
+2040,BIOPEL,GBR,summer.night,0.5225
+2040,BIOPEL,GBR,summer.day,0.5225
+2040,BIOPEL,GBR,summer.peak,0.5225
+2040,BIOPEL,GBR,summer.evening,0.5225
+2040,BIOPEL,GBR,autumn.night,0.5225
+2040,BIOPEL,GBR,autumn.day,0.5225
+2040,BIOPEL,GBR,autumn.peak,0.5225
+2040,BIOPEL,GBR,autumn.evening,0.5225
diff --git a/tests/data/two_regions/commodity_prices.csv b/tests/data/two_regions/commodity_prices.csv
index 267136d1f..11a49d2bf 100644
--- a/tests/data/two_regions/commodity_prices.csv
+++ b/tests/data/two_regions/commodity_prices.csv
@@ -1,16 +1,4 @@
milestone_year,commodity_id,region_id,time_slice,price
-2020,gas,R1,all-year.night,2.55
-2020,gas,R1,all-year.morning,2.55
-2020,gas,R1,all-year.afternoon,2.55
-2020,gas,R1,all-year.early-peak,2.55
-2020,gas,R1,all-year.late-peak,2.55
-2020,gas,R1,all-year.evening,2.55
-2020,gas,R2,all-year.night,2.55
-2020,gas,R2,all-year.morning,2.55
-2020,gas,R2,all-year.afternoon,2.55
-2020,gas,R2,all-year.early-peak,2.55
-2020,gas,R2,all-year.late-peak,2.55
-2020,gas,R2,all-year.evening,2.55
2020,electricity,R1,all-year.night,-0.0
2020,electricity,R1,all-year.morning,-0.0
2020,electricity,R1,all-year.afternoon,-0.0
@@ -23,6 +11,18 @@ milestone_year,commodity_id,region_id,time_slice,price
2020,electricity,R2,all-year.early-peak,-0.0
2020,electricity,R2,all-year.late-peak,-0.0
2020,electricity,R2,all-year.evening,-0.0
+2020,gas,R1,all-year.night,2.55
+2020,gas,R1,all-year.morning,2.55
+2020,gas,R1,all-year.afternoon,2.55
+2020,gas,R1,all-year.early-peak,2.55
+2020,gas,R1,all-year.late-peak,2.55
+2020,gas,R1,all-year.evening,2.55
+2020,gas,R2,all-year.night,2.55
+2020,gas,R2,all-year.morning,2.55
+2020,gas,R2,all-year.afternoon,2.55
+2020,gas,R2,all-year.early-peak,2.55
+2020,gas,R2,all-year.late-peak,2.55
+2020,gas,R2,all-year.evening,2.55
2020,heat,R1,all-year.night,8.3380664049
2020,heat,R1,all-year.morning,8.3380664049
2020,heat,R1,all-year.afternoon,8.3380664049
@@ -35,18 +35,6 @@ milestone_year,commodity_id,region_id,time_slice,price
2020,heat,R2,all-year.early-peak,2.9579999999999997
2020,heat,R2,all-year.late-peak,2.9579999999999997
2020,heat,R2,all-year.evening,2.9579999999999997
-2025,gas,R1,all-year.night,2.5500000000000007
-2025,gas,R1,all-year.morning,2.55
-2025,gas,R1,all-year.afternoon,2.55
-2025,gas,R1,all-year.early-peak,2.5500000000000007
-2025,gas,R1,all-year.late-peak,2.55
-2025,gas,R1,all-year.evening,2.5500000000000007
-2025,gas,R2,all-year.night,2.55
-2025,gas,R2,all-year.morning,2.55
-2025,gas,R2,all-year.afternoon,2.55
-2025,gas,R2,all-year.early-peak,2.55
-2025,gas,R2,all-year.late-peak,2.55
-2025,gas,R2,all-year.evening,2.55
2025,electricity,R1,all-year.night,15.26529810765
2025,electricity,R1,all-year.morning,15.26529810765
2025,electricity,R1,all-year.afternoon,15.26529810765
@@ -59,6 +47,18 @@ milestone_year,commodity_id,region_id,time_slice,price
2025,electricity,R2,all-year.early-peak,4.2585
2025,electricity,R2,all-year.late-peak,4.2585
2025,electricity,R2,all-year.evening,4.2585
+2025,gas,R1,all-year.night,2.5500000000000007
+2025,gas,R1,all-year.morning,2.55
+2025,gas,R1,all-year.afternoon,2.55
+2025,gas,R1,all-year.early-peak,2.5500000000000007
+2025,gas,R1,all-year.late-peak,2.55
+2025,gas,R1,all-year.evening,2.5500000000000007
+2025,gas,R2,all-year.night,2.55
+2025,gas,R2,all-year.morning,2.55
+2025,gas,R2,all-year.afternoon,2.55
+2025,gas,R2,all-year.early-peak,2.55
+2025,gas,R2,all-year.late-peak,2.55
+2025,gas,R2,all-year.evening,2.55
2025,heat,R1,all-year.night,6.10611924306
2025,heat,R1,all-year.morning,6.10611924306
2025,heat,R1,all-year.afternoon,6.10611924306
@@ -71,18 +71,6 @@ milestone_year,commodity_id,region_id,time_slice,price
2025,heat,R2,all-year.early-peak,2.9579999999999997
2025,heat,R2,all-year.late-peak,2.9579999999999997
2025,heat,R2,all-year.evening,2.9579999999999997
-2030,gas,R1,all-year.night,-0.0
-2030,gas,R1,all-year.morning,-0.0
-2030,gas,R1,all-year.afternoon,-0.0
-2030,gas,R1,all-year.early-peak,-0.0
-2030,gas,R1,all-year.late-peak,-0.0
-2030,gas,R1,all-year.evening,-0.0
-2030,gas,R2,all-year.night,2.55
-2030,gas,R2,all-year.morning,2.55
-2030,gas,R2,all-year.afternoon,2.55
-2030,gas,R2,all-year.early-peak,2.55
-2030,gas,R2,all-year.late-peak,2.5500000000000003
-2030,gas,R2,all-year.evening,2.55
2030,electricity,R1,all-year.night,-0.0
2030,electricity,R1,all-year.morning,-0.0
2030,electricity,R1,all-year.afternoon,-0.0
@@ -95,6 +83,18 @@ milestone_year,commodity_id,region_id,time_slice,price
2030,electricity,R2,all-year.early-peak,4.2585
2030,electricity,R2,all-year.late-peak,4.2585
2030,electricity,R2,all-year.evening,4.2585
+2030,gas,R1,all-year.night,-0.0
+2030,gas,R1,all-year.morning,-0.0
+2030,gas,R1,all-year.afternoon,-0.0
+2030,gas,R1,all-year.early-peak,-0.0
+2030,gas,R1,all-year.late-peak,-0.0
+2030,gas,R1,all-year.evening,-0.0
+2030,gas,R2,all-year.night,2.55
+2030,gas,R2,all-year.morning,2.55
+2030,gas,R2,all-year.afternoon,2.55
+2030,gas,R2,all-year.early-peak,2.55
+2030,gas,R2,all-year.late-peak,2.5500000000000003
+2030,gas,R2,all-year.evening,2.55
2030,heat,R1,all-year.night,-0.0
2030,heat,R1,all-year.morning,-0.0
2030,heat,R1,all-year.afternoon,-0.0
@@ -107,18 +107,6 @@ milestone_year,commodity_id,region_id,time_slice,price
2030,heat,R2,all-year.early-peak,2.9579999999999997
2030,heat,R2,all-year.late-peak,2.958
2030,heat,R2,all-year.evening,2.9579999999999997
-2035,gas,R1,all-year.night,-0.0
-2035,gas,R1,all-year.morning,-0.0
-2035,gas,R1,all-year.afternoon,-0.0
-2035,gas,R1,all-year.early-peak,-0.0
-2035,gas,R1,all-year.late-peak,-0.0
-2035,gas,R1,all-year.evening,-0.0
-2035,gas,R2,all-year.night,2.55
-2035,gas,R2,all-year.morning,2.55
-2035,gas,R2,all-year.afternoon,2.55
-2035,gas,R2,all-year.early-peak,2.55
-2035,gas,R2,all-year.late-peak,2.55
-2035,gas,R2,all-year.evening,2.55
2035,electricity,R1,all-year.night,-0.0
2035,electricity,R1,all-year.morning,-0.0
2035,electricity,R1,all-year.afternoon,-0.0
@@ -131,6 +119,18 @@ milestone_year,commodity_id,region_id,time_slice,price
2035,electricity,R2,all-year.early-peak,4.2585
2035,electricity,R2,all-year.late-peak,4.2585
2035,electricity,R2,all-year.evening,4.2585
+2035,gas,R1,all-year.night,-0.0
+2035,gas,R1,all-year.morning,-0.0
+2035,gas,R1,all-year.afternoon,-0.0
+2035,gas,R1,all-year.early-peak,-0.0
+2035,gas,R1,all-year.late-peak,-0.0
+2035,gas,R1,all-year.evening,-0.0
+2035,gas,R2,all-year.night,2.55
+2035,gas,R2,all-year.morning,2.55
+2035,gas,R2,all-year.afternoon,2.55
+2035,gas,R2,all-year.early-peak,2.55
+2035,gas,R2,all-year.late-peak,2.55
+2035,gas,R2,all-year.evening,2.55
2035,heat,R1,all-year.night,-0.0
2035,heat,R1,all-year.morning,-0.0
2035,heat,R1,all-year.afternoon,-0.0
@@ -143,18 +143,6 @@ milestone_year,commodity_id,region_id,time_slice,price
2035,heat,R2,all-year.early-peak,2.9579999999999997
2035,heat,R2,all-year.late-peak,2.9579999999999997
2035,heat,R2,all-year.evening,2.9579999999999997
-2040,gas,R1,all-year.night,-0.0
-2040,gas,R1,all-year.morning,-0.0
-2040,gas,R1,all-year.afternoon,-0.0
-2040,gas,R1,all-year.early-peak,-0.0
-2040,gas,R1,all-year.late-peak,-0.0
-2040,gas,R1,all-year.evening,-0.0
-2040,gas,R2,all-year.night,2.55
-2040,gas,R2,all-year.morning,2.55
-2040,gas,R2,all-year.afternoon,2.55
-2040,gas,R2,all-year.early-peak,2.55
-2040,gas,R2,all-year.late-peak,2.55
-2040,gas,R2,all-year.evening,2.55
2040,electricity,R1,all-year.night,-0.0
2040,electricity,R1,all-year.morning,-0.0
2040,electricity,R1,all-year.afternoon,-0.0
@@ -167,6 +155,18 @@ milestone_year,commodity_id,region_id,time_slice,price
2040,electricity,R2,all-year.early-peak,4.2585
2040,electricity,R2,all-year.late-peak,4.2585
2040,electricity,R2,all-year.evening,4.2585
+2040,gas,R1,all-year.night,-0.0
+2040,gas,R1,all-year.morning,-0.0
+2040,gas,R1,all-year.afternoon,-0.0
+2040,gas,R1,all-year.early-peak,-0.0
+2040,gas,R1,all-year.late-peak,-0.0
+2040,gas,R1,all-year.evening,-0.0
+2040,gas,R2,all-year.night,2.55
+2040,gas,R2,all-year.morning,2.55
+2040,gas,R2,all-year.afternoon,2.55
+2040,gas,R2,all-year.early-peak,2.55
+2040,gas,R2,all-year.late-peak,2.55
+2040,gas,R2,all-year.evening,2.55
2040,heat,R1,all-year.night,-0.0
2040,heat,R1,all-year.morning,-0.0
2040,heat,R1,all-year.afternoon,-0.0
@@ -179,18 +179,6 @@ milestone_year,commodity_id,region_id,time_slice,price
2040,heat,R2,all-year.early-peak,2.9579999999999997
2040,heat,R2,all-year.late-peak,2.9579999999999997
2040,heat,R2,all-year.evening,2.9579999999999997
-2045,gas,R1,all-year.night,-0.0
-2045,gas,R1,all-year.morning,-0.0
-2045,gas,R1,all-year.afternoon,-0.0
-2045,gas,R1,all-year.early-peak,-0.0
-2045,gas,R1,all-year.late-peak,-0.0
-2045,gas,R1,all-year.evening,-0.0
-2045,gas,R2,all-year.night,2.5500000000000003
-2045,gas,R2,all-year.morning,2.55
-2045,gas,R2,all-year.afternoon,2.55
-2045,gas,R2,all-year.early-peak,2.5500000000000003
-2045,gas,R2,all-year.late-peak,2.55
-2045,gas,R2,all-year.evening,2.55
2045,electricity,R1,all-year.night,-0.0
2045,electricity,R1,all-year.morning,-0.0
2045,electricity,R1,all-year.afternoon,-0.0
@@ -203,6 +191,18 @@ milestone_year,commodity_id,region_id,time_slice,price
2045,electricity,R2,all-year.early-peak,4.2585
2045,electricity,R2,all-year.late-peak,4.2585
2045,electricity,R2,all-year.evening,4.2585
+2045,gas,R1,all-year.night,-0.0
+2045,gas,R1,all-year.morning,-0.0
+2045,gas,R1,all-year.afternoon,-0.0
+2045,gas,R1,all-year.early-peak,-0.0
+2045,gas,R1,all-year.late-peak,-0.0
+2045,gas,R1,all-year.evening,-0.0
+2045,gas,R2,all-year.night,2.5500000000000003
+2045,gas,R2,all-year.morning,2.55
+2045,gas,R2,all-year.afternoon,2.55
+2045,gas,R2,all-year.early-peak,2.5500000000000003
+2045,gas,R2,all-year.late-peak,2.55
+2045,gas,R2,all-year.evening,2.55
2045,heat,R1,all-year.night,-0.0
2045,heat,R1,all-year.morning,-0.0
2045,heat,R1,all-year.afternoon,-0.0
@@ -215,12 +215,6 @@ milestone_year,commodity_id,region_id,time_slice,price
2045,heat,R2,all-year.early-peak,2.958
2045,heat,R2,all-year.late-peak,2.9579999999999997
2045,heat,R2,all-year.evening,2.9579999999999997
-2050,gas,R2,all-year.night,2.55
-2050,gas,R2,all-year.morning,2.55
-2050,gas,R2,all-year.afternoon,2.55
-2050,gas,R2,all-year.early-peak,2.55
-2050,gas,R2,all-year.late-peak,2.55
-2050,gas,R2,all-year.evening,2.55
2050,electricity,R1,all-year.night,-0.0
2050,electricity,R1,all-year.morning,-0.0
2050,electricity,R1,all-year.afternoon,-0.0
@@ -233,6 +227,12 @@ milestone_year,commodity_id,region_id,time_slice,price
2050,electricity,R2,all-year.early-peak,4.2585
2050,electricity,R2,all-year.late-peak,4.2585
2050,electricity,R2,all-year.evening,4.2585
+2050,gas,R2,all-year.night,2.55
+2050,gas,R2,all-year.morning,2.55
+2050,gas,R2,all-year.afternoon,2.55
+2050,gas,R2,all-year.early-peak,2.55
+2050,gas,R2,all-year.late-peak,2.55
+2050,gas,R2,all-year.evening,2.55
2050,heat,R1,all-year.night,-0.0
2050,heat,R1,all-year.morning,-0.0
2050,heat,R1,all-year.afternoon,-0.0
diff --git a/tests/regression.rs b/tests/regression.rs
index b60ac228d..254a41546 100644
--- a/tests/regression.rs
+++ b/tests/regression.rs
@@ -26,6 +26,8 @@ define_regression_test!(circularity);
// Patched examples
define_regression_test_with_patches!(simple_divisible);
define_regression_test_with_patches!(simple_npv);
+define_regression_test_with_patches!(simple_marginal);
+define_regression_test_with_patches!(simple_full);
// ------ END: regression tests ------
@@ -93,18 +95,18 @@ fn compare_lines(
// Check for different number of lines
if lines1.len() != lines2.len() {
errors.push(format!(
- "{}: Different number of lines: {} vs {}",
- file_name,
+ "{file_name}: Different number of lines: {} vs {}",
lines1.len(),
lines2.len()
));
}
// Compare each line
- for (num, (line1, line2)) in lines1.into_iter().zip(lines2).enumerate() {
- if !compare_line(num, &line1, &line2, file_name, errors) {
+ for (idx, (line1, line2)) in lines1.into_iter().zip(lines2).enumerate() {
+ let line_num = idx + 1; // (1-based) line number
+ if !compare_line(line_num, &line1, &line2, file_name, errors) {
errors.push(format!(
- "{file_name}: line {num}:\n + \"{line1}\"\n - \"{line2}\""
+ "{file_name}: line {line_num}:\n + \"{line1}\"\n - \"{line2}\""
));
}
}