From fd03a32bff58c5392ce56fd5e7d0ea326490d2c3 Mon Sep 17 00:00:00 2001 From: Tom Bland Date: Fri, 21 Aug 2026 13:46:00 +0100 Subject: [PATCH 1/3] Add currency parameter to model.toml --- examples/circularity/model.toml | 1 + examples/missing_commodity/model.toml | 1 + examples/muse1_default/model.toml | 1 + examples/simple/model.toml | 1 + examples/two_outputs/model.toml | 1 + examples/two_regions/model.toml | 1 + schemas/input/model.yaml | 4 ++++ src/model/parameters.rs | 20 ++++++++++++++++++++ 8 files changed, 30 insertions(+) diff --git a/examples/circularity/model.toml b/examples/circularity/model.toml index 6a8e606d0..98f9c745e 100644 --- a/examples/circularity/model.toml +++ b/examples/circularity/model.toml @@ -1,2 +1,3 @@ milestone_years = [2020, 2030, 2040] +currency = "MUSD2020" capacity_margin = 0.3 diff --git a/examples/missing_commodity/model.toml b/examples/missing_commodity/model.toml index 98be065bf..5d315ba44 100644 --- a/examples/missing_commodity/model.toml +++ b/examples/missing_commodity/model.toml @@ -1 +1,2 @@ milestone_years = [2020, 2030, 2040] +currency = "MUSD2020" diff --git a/examples/muse1_default/model.toml b/examples/muse1_default/model.toml index e0c7e2257..0d9899e78 100644 --- a/examples/muse1_default/model.toml +++ b/examples/muse1_default/model.toml @@ -1 +1,2 @@ milestone_years = [2020, 2025, 2030, 2035, 2040, 2045, 2050] +currency = "MUSD2020" diff --git a/examples/simple/model.toml b/examples/simple/model.toml index 98be065bf..5d315ba44 100644 --- a/examples/simple/model.toml +++ b/examples/simple/model.toml @@ -1 +1,2 @@ milestone_years = [2020, 2030, 2040] +currency = "MUSD2020" diff --git a/examples/two_outputs/model.toml b/examples/two_outputs/model.toml index 98be065bf..5d315ba44 100644 --- a/examples/two_outputs/model.toml +++ b/examples/two_outputs/model.toml @@ -1 +1,2 @@ milestone_years = [2020, 2030, 2040] +currency = "MUSD2020" diff --git a/examples/two_regions/model.toml b/examples/two_regions/model.toml index e0c7e2257..0d9899e78 100644 --- a/examples/two_regions/model.toml +++ b/examples/two_regions/model.toml @@ -1 +1,2 @@ milestone_years = [2020, 2025, 2030, 2035, 2040, 2045, 2050] +currency = "MUSD2020" diff --git a/schemas/input/model.yaml b/schemas/input/model.yaml index 1e3b7a50f..97f7fe905 100644 --- a/schemas/input/model.yaml +++ b/schemas/input/model.yaml @@ -9,6 +9,10 @@ properties: type: integer description: The milestone years for the simulation notes: Must be positive integers that are sorted and unique + currency: + type: string + description: Optional currency label for monetary model inputs + notes: This is metadata only and does not affect model calculations or perform currency conversion candidate_asset_capacity: type: number description: Capacity to give to candidate assets in dispatch optimisation diff --git a/src/model/parameters.rs b/src/model/parameters.rs index a8de59264..e6e53668c 100644 --- a/src/model/parameters.rs +++ b/src/model/parameters.rs @@ -71,6 +71,9 @@ fn set_dangerous_model_options_flag(enabled: bool) { pub struct ModelParameters { /// Milestone years pub milestone_years: Vec, + /// Optional currency label for monetary model inputs. This is metadata only and does not affect + /// model calculations. + pub currency: Option, /// Allow potentially dangerous options to be enabled. #[serde(rename = "please_give_me_broken_results")] // Can't use constant here :-( pub allow_dangerous_options: bool, @@ -137,6 +140,7 @@ impl Default for ModelParameters { milestone_years: Vec::default(), // Default values for optional parameters + currency: None, allow_dangerous_options: false, candidate_asset_capacity: Capacity(1e-4), commodity_balance_epsilon: Flow(1e-6), @@ -425,6 +429,22 @@ mod tests { assert_eq!(model_params.milestone_years, [2020, 2100]); } + #[test] + fn model_params_currency_is_optional_metadata() { + let with_currency: ModelParameters = toml::from_str( + " + milestone_years = [2020, 2100] + currency = \"MUSD2010\" + ", + ) + .unwrap(); + assert_eq!(with_currency.currency.as_deref(), Some("MUSD2010")); + + let without_currency: ModelParameters = + toml::from_str("milestone_years = [2020, 2100]").unwrap(); + assert_eq!(without_currency.currency, None); + } + #[test] fn model_params_deserialisation_copies_highs_global_options() { let model_params: ModelParameters = toml::from_str( From d97713a90d562bfb2e2e2cced3dcdba01a8e5907 Mon Sep 17 00:00:00 2001 From: Tom Bland Date: Fri, 21 Aug 2026 13:46:58 +0100 Subject: [PATCH 2/3] Add Units and Dimensions page to documentation --- docs/SUMMARY.md | 1 + docs/model/README.md | 4 + docs/model/prices.md | 5 +- docs/model/units_and_dimensions.md | 136 +++++++++++++++++++++++++++++ 4 files changed, 144 insertions(+), 2 deletions(-) create mode 100644 docs/model/units_and_dimensions.md diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 75c8e99be..3898a9877 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -10,6 +10,7 @@ - [Input Files](file_formats/input_files.md) - [Output Files](file_formats/output_files.md) - [Model Description](model/README.md) + - [Units and Dimensions](model/units_and_dimensions.md) - [Dispatch Optimisation](model/dispatch_optimisation.md) - [Investment Appraisal](model/investment.md) - [Commodity Prices](model/prices.md) diff --git a/docs/model/README.md b/docs/model/README.md index a0ec5ae3d..01dcf8c9c 100644 --- a/docs/model/README.md +++ b/docs/model/README.md @@ -22,6 +22,10 @@ user-defined data is at the heart of how the model behaves. It is also "bottom-u means that it requires users to characterise each individual process that produces or consumes each commodity, along with a range of other physical, economic and agent parameters. +MUSE2 does not require a single set of physical units, but the units used within each model must be +consistent. See [Units and Dimensions](units_and_dimensions.md) for how capacity, activity, +commodity flows, and monetary values are related. + At a high level, the user defines: 1) The overall temporal arrangements, including the base time period, milestone time periods and diff --git a/docs/model/prices.md b/docs/model/prices.md index 70e3b415e..a641b7fb5 100644 --- a/docs/model/prices.md +++ b/docs/model/prices.md @@ -78,8 +78,9 @@ This is divided again by the sum of SED/SVD output coefficients to get a cost pe \frac{\mathrm{AnnualFixedCostPerActivity}}{\sum_{c \in \mathrm{SED,\,SVD}} \mathrm{OutputCoefficient}_c} \\] -> Note: this only works if all output commodities are measured in the same energy units (e.g. PJ). -> For this reason, MUSE2 disallows processes that have output commodities with differing units. +> Note: this only works if all SED/SVD output commodities use the same unit label (e.g. `PJ`). +> For this reason, MUSE2 disallows processes that have SED/SVD outputs with differing +> unit labels. MUSE2 compares the labels but does not interpret or convert them. The final full cost of output commodity \\( c \\) is: \\[ diff --git a/docs/model/units_and_dimensions.md b/docs/model/units_and_dimensions.md new file mode 100644 index 000000000..020ea83e1 --- /dev/null +++ b/docs/model/units_and_dimensions.md @@ -0,0 +1,136 @@ +# Units and Dimensions + +MUSE2 does not impose one universal system of physical units. Instead, the input files define the +unit conventions used by each model. Commodity units are provided in the `units` field of +`commodities.csv`, while the optional `currency` field in `model.toml` documents the currency and, +if needed, its reference year. Process flow coefficients and `capacity_to_activity` then define the +relationships between activity, commodity flows, and capacity. + +MUSE2 requires commodity unit labels to be present and uses them to check consistency, but it does +not interpret their contents, convert between units, or perform general dimensional analysis. For +example, `PJ`, `tonnes`, and `ktCO2` are simply labels to MUSE2; their physical meanings come from +the conventions chosen by the modeller. Similarly, the `currency` value is just a label: MUSE2 +does not perform currency conversions or adjust monetary values for inflation. + +The main relationships are: + +- **Activity** describes how much a *Process* operates. +- **Commodity units** label the quantities produced and consumed by a *Process*. +- **Flow coefficients** convert activity into input and output flows of each *Commodity*. +- **Capacity-to-activity** relates installed capacity to maximum annual activity. +- **Capacity** limits the activity an *Asset* can perform. +- **Monetary units** describe how costs and prices are expressed. + +For the example models, energy commodities are measured in PJ, generation capacity is expressed in +GW, emissions are measured in ktCO2, and monetary values are expressed in millions of US dollars. + +## Commodity flows and flow coefficients + +Every commodity must provide a unit label in the `units` field of `commodities.csv`. The +label is useful for documentation purposes, and is only used by MUSE2 for consistency checks; MUSE2 +does not parse it or convert between labels. The simple example uses: + +| Commodity | Type | Unit in the simple example | +| --- | --- | --- | +| GASPRD | Supply Equals Demand | PJ | +| GASNAT | Supply Equals Demand | PJ | +| ELCTRI | Supply Equals Demand | PJ | +| RSHEAT | Service Demand | PJ | +| CO2EMT | Other | ktCO2 | + +> If any process has multiple SED or SVD output commodities, these commodities **must** all use +> exactly the same unit label - this is checked and enforced during input file loading. +> Inputs and OTH outputs are not subject to this check. + +A *process flow coefficient* converts one activity unit into the corresponding commodity flow. +Positive coefficients represent production and negative coefficients represent consumption. A flow +coefficient therefore maps a dimensionless activity quantity to the corresponding commodity-flow +quantity. The coefficient must be interpreted together with the commodity's unit label. + +For example, the simple example defines the gas combined-cycle turbine with these flows: + +| Flow | Coefficient | Units and meaning | +| --- | ---: | --- | +| Natural gas input | `-1.5` | -1.5 PJ/activity unit | +| Electricity output | `1.0` | 1.0 PJ/activity unit | +| CO2 emissions | `76.695` | 76.695 ktCO2/activity unit | + +## Activity + +Activity is a dimensionless, process-specific quantity used by MUSE2 to describe how much an asset +operates in a time slice. It has no intrinsic physical unit. Its physical interpretation is supplied +by the process's flow coefficients and commodity-unit labels, while its maximum annual amount is +constrained by the asset's capacity and `capacity_to_activity`. + +## Capacity and capacity-to-activity + +Capacity is the installed size of an *Asset*. Its unit depends on the technology and the model. +For example, a power station might have capacity in MW or GW, while a material-processing process +might use tonnes per year. + +The `capacity_to_activity` factor gives the maximum annual activity per unit of capacity. The factor, +flow coefficients, and commodity-unit labels work together to define what the capacity unit +represents physically. + +In the simple example, the wind-farm and gas-turbine processes use `capacity_to_activity = 31.54`. +Since electricity output is labelled `PJ` and has a coefficient of `1.0`, this value was chosen so that +one capacity unit corresponds to 1 GW of electricity-generating capacity, able to produce up to +31.54 PJ of electricity per year. This follows from: + +`1 GW x 31,536,000 seconds per year = 31,536,000,000,000,000 J per year = 31.536 PJ per year` + +The model's value of `31.54` is rounded from `31.536`. At full activity, the gas-turbine coefficients +(above) then imply 47.31 PJ of natural-gas consumption per year, and approximately 2,419 ktCO2 of +emissions. In practice, availability limits may reduce actual activity below this maximum. + +Conversely, a coal-producing process where coal is measured in tonnes, with an output +coefficient of `1.0`, might use `capacity_to_activity = 1`, so that one capacity unit represents an +amount of capacity able to produce up to one tonne of coal per year. + +## Monetary units + +MUSE2 does not convert currencies or adjust monetary values for inflation. Therefore, all monetary +inputs and outputs must be expressed using a consistent currency and reference year. For +documentation purposes, the currency and reference year can be included in the `currency` label in +`model.toml`, such as: + +```toml +currency = "MUSD2020" +``` + +This is a free-text label for documenting the monetary +convention used by a model. It has no prescribed format, so modellers can use it to record a +currency, reference year, or any other relevant information. + +Here, `MUSD2020` means million US dollars expressed in 2020 terms. The label is descriptive metadata +only; it does not determine the units of monetary data or affect model calculations. + +For example, with `currency = "MUSD2020"` and capacity measured in GW, the monetary quantities would +be expressed as follows: + +| Quantity | Units | +| --- | --- | +| Capital cost | MUSD2020/GW | +| Fixed operating cost | MUSD2020/GW/year | +| Variable operating cost | MUSD2020 per unit of activity | +| Flow cost or levy | MUSD2020 per unit of commodity flow (e.g. MUSD2020/PJ) | +| Commodity price | MUSD2020 per unit of commodity flow (e.g. MUSD2020/PJ) | + +For example, a `variable_operating_cost` of `0.55` in a model defined with `currency = "MUSD2020"` +means that one activity unit incurs a variable operating cost of `0.55 MUSD2020` (i.e. $550,000 in +2020 terms). + +## Choosing consistent units + +When building a model: + +1. Choose units for each commodity, such as PJ for energy and ktCO2 for emissions. +2. Choose a capacity unit for each process, such as GW for a power plant. +3. Set `capacity_to_activity` so that capacity converts to the appropriate annual activity scale. +4. Define flow coefficients so that activity produces and consumes the intended commodity quantities. +5. Express all costs and prices using the same currency convention and the relevant capacity, + activity, or commodity-flow denominator. + +MUSE2 validates some unit relationships, but it does not perform general unit conversion. A model +can use different physical units for different commodities, provided that each process's capacity, +activity, flow, and cost data are dimensionally consistent. From 9a28da5b22b2c810c7fce022fe3e1380e3c79971 Mon Sep 17 00:00:00 2001 From: Tom Bland Date: Fri, 21 Aug 2026 13:54:06 +0100 Subject: [PATCH 3/3] Small improvements --- docs/model/prices.md | 6 +++--- schemas/input/model.yaml | 7 ++++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/model/prices.md b/docs/model/prices.md index a641b7fb5..3ffb25abc 100644 --- a/docs/model/prices.md +++ b/docs/model/prices.md @@ -78,9 +78,9 @@ This is divided again by the sum of SED/SVD output coefficients to get a cost pe \frac{\mathrm{AnnualFixedCostPerActivity}}{\sum_{c \in \mathrm{SED,\,SVD}} \mathrm{OutputCoefficient}_c} \\] -> Note: this only works if all SED/SVD output commodities use the same unit label (e.g. `PJ`). -> For this reason, MUSE2 disallows processes that have SED/SVD outputs with differing -> unit labels. MUSE2 compares the labels but does not interpret or convert them. +> Note: this only works if all SED/SVD output commodities are measured in the same units (e.g. PJ). +> For this reason, MUSE2 disallows processes that have SED/SVD outputs with differing unit labels. +> MUSE2 compares the labels but does not interpret or convert them. The final full cost of output commodity \\( c \\) is: \\[ diff --git a/schemas/input/model.yaml b/schemas/input/model.yaml index 97f7fe905..026f416cc 100644 --- a/schemas/input/model.yaml +++ b/schemas/input/model.yaml @@ -12,7 +12,12 @@ properties: currency: type: string description: Optional currency label for monetary model inputs - notes: This is metadata only and does not affect model calculations or perform currency conversion + notes: | + This is a free-text label used to document the monetary convention for model inputs and + outputs. It may include a currency and reference year, such as `MUSD2020` (i.e. millions of + US dollars in 2020 terms), or take any other form chosen by the modeller. The label is metadata + only: MUSE2 does not interpret it, perform currency conversion, or adjust monetary values for + inflation. Monetary inputs should use a consistent currency and reference year. candidate_asset_capacity: type: number description: Capacity to give to candidate assets in dispatch optimisation