MUSE2
Overview
⚠️ Please note that MUSE2 currently only works with simple models and is not yet suitable for use in research. ⚠️
MUSE2 (ModUlar energy systems Simulation Environment) is a tool for running simulations of energy systems, written in Rust. Its purpose is to provide users with a framework to simulate pathways of energy system transition, usually in the context of climate change mitigation.
It is the successor to MUSE, which is written in Python. It was developed following re-design of MUSE to address a range of legacy issues that are challenging to address via upgrades to the existing MUSE framework, and to implement the framework in the high-performance Rust language.
This work was supported via the Climate Compatible Growth (CCG) programme and the EPSRC-funded HI-ACT project (EP/X038823/2). CCG is funded by UK Aid from the UK government. However, the views expressed herein do not necessarily reflect the UK government’s official policies.
Getting started
You will first need to install MUSE2. We recommend downloading the latest version for your platform
on the releases page. The archive file you download should contain a readme file containing
instructions on how to run MUSE2. Alternatively, you can install the muse2 crate from
crates.io using cargo (see Setting up your development environment).
Once you have installed MUSE2, you can look at the user guide and the command line help for details on how to get started with creating and running your own models.
Detailed information about the model used by MUSE2 is provided in Model Description. For a list of relevant terms, see the glossary.
If you want to work with the MUSE2 source code, for instance to submit a change to this repository, please see the developer guide.
User Guide
Once you have installed MUSE2, you should be able to run it via the muse2 command-line program.
For details of the command-line interface, see here or run muse2 help.
Example models
MUSE2 comes with a number of example models, partly to demonstrate the various program features as well as to be used as templates for new models. To see the list of available examples, you can run:
muse2 example list
It should print something like the following:
circularity
missing_commodity
muse1_default
simple
two_outputs
two_regions
To view information about a particular example, you can run, e.g.:
muse2 example info simple
You can run examples like so:
muse2 example run simple
Building and running your own model
🚧 Note that this section is under construction! 🚧
In the longer term, we plan to have a tutorial describing how to build a model in more detail.
Models in MUSE2 are defined with one TOML file (model.toml) and many CSV files. For a
description of each of the files and the different fields, see the documentation for input files.
Creating a new model from an example
We recommend you use one of the examples as starting point for your own model, as there are many required files.
To create a new model based on the simple example, run:
muse2 example extract simple new_model
This will create a new subdirectory called new_model in the current folder.
Validating this model
Before running a model, you can check that its input files can be read and that the model is internally consistent with:
muse2 validate new_model
Running this model
Next, let’s run this model so you can see the output for a working model. You can do this by running:
muse2 run new_model
If everything works as expected, you should see output on your terminal indicating the progress of the simulation (which should finish very quickly).
The first few lines should look something like:
[12:24:20 INFO muse2::cli] Starting MUSE2 v2.X.Y
[12:24:20 INFO muse2::cli] Loaded model from new_model/
[12:24:20 INFO muse2::cli] Output folder: muse2_results/new_model
...
You should see that a new muse2_results folder has been created. This folder will contain the
output for your model in a subfolder called new_model. For information about how to interpret
these files, see the documentation for output files. There is also the muse2_data_analysis
repository, which contains example Jupyter notebooks for processing output data.
Next steps
You will now want to configure the model for your own use case. You should start by looking at the documentation for input files for details of the different data types and parameters for MUSE2.
Unfortunately, this may not be easy, especially if you are not already familiar with MUSE1. In the longer term, we will have tutorials, so watch this space! In the meantime, if you have a question, feel free to open an issue.
Visualising commodity graphs
To visualise the structure of your model, you can use the muse2 save-graphs command to
create graphs of commodity/process relationships.
This command will output a graph for each region/year in the simulation, where nodes are commodities
and edges are processes.
Graphs will be saved in DOT format, which can be visualised locally with Graphviz, or online
with Graphviz online.
Dashed lines are used to indicate flows for non-primary outputs of a process (as defined in the
processes.csv input file).
Modifying the program settings
You can configure the behaviour of MUSE2 with a settings.toml file. To edit this file, run:
muse2 settings edit
There are also some more commands for working with the settings file. For details, run
muse2 settings help or see the muse2 settings command documentation.
For information about the available settings, see the documentation for the settings.toml
file.
Setting the log level
MUSE2 uses the fern crate for logging. The default log level is info, though this can be
configured either via the log_level option in settings.toml or by setting the
MUSE2_LOG_LEVEL environment variable. (If both are used, the environment variable takes
precedence.)
The possible options are:
errorwarninfodebugtraceoff
By default, MUSE2 will colourise the log output if this is available (i.e. it is outputting to a terminal rather than a file).
For more information, please consult the fern documentation.
Example models
This document describes the various example models included with MUSE2.
To list the available examples, run:
muse2 example list
To view details of a particular example, run e.g.:
muse2 example info simple
circularity example
This model extends “two_outputs” to showcase circular commodity flows, adding a hydrogen loop that turns electricity into hydrogen and back again.
Note: this model is currently experimental and only included for development purposes.
missing_commodity example
This model is like the “simple” example, except that it includes a separate biomass supply chain, which has no capacity in the base year. This is partly done to exemplify how MUSE2 can generate prices for commodities not utilised in a given year.
muse1_default example
This model is a port of the default model for MUSE v1 (see 1), primarily for comparison (e.g. benchmarking). Note that the model is not an exact port as MUSE v1 and MUSE v2 support different features. Also be aware that due to this and differences in the underlying algorithm, investment behaviour will differ between the two programs.
It is broadly similar to the “simple” example, in that it contains a single service demand commodity, residential heat, which can be served by either gas boilers or heat pumps, though some of the details, such as parameter values, are different. It also includes a carbon tax.
simple example
A minimal example for a single region.
It includes a single service demand commodity, residential heat, which can be served by a combination of gas boilers and heat pumps. Electricity and natural gas are modelled as upstream commodities, which can be served by various processes. CO2 is modelled as a byproduct of some gas-related processes and a carbon tax is included as a levy.
two_outputs example
This model is like the “missing_commodity” example, except that it includes transport related supply chains, and one processes that produces two SED outputs (OILREF), and one process that consumes two SED inputs (THYBCR).
two_regions example
This model is based on the “muse1_default” example, except that it includes two regions.
Command-Line Help for muse2
This document contains the help content for the muse2 command-line program.
Command Overview:
muse2↴muse2 run↴muse2 example↴muse2 example list↴muse2 example info↴muse2 example extract↴muse2 example run↴muse2 validate↴muse2 save-graphs↴muse2 settings↴muse2 settings edit↴muse2 settings delete↴muse2 settings path↴muse2 settings show↴muse2 settings show-default↴
muse2
A tool for running simulations of energy systems
Usage: muse2 [COMMAND]
For more detailed documentation on this version of MUSE2, see: https://energysystemsmodellinglab.github.io/MUSE2/v2.2.0/
Subcommands:
run— Run a simulation modelexample— Manage example modelsvalidate— Validate a modelsave-graphs— Build and output commodity flow graphs for a modelsettings— Manage settings file
muse2 run
Run a simulation model
Usage: muse2 run [OPTIONS] <MODEL_DIR>
Arguments:
<MODEL_DIR>— Path to the model directory
Options:
-o,--output-dir <OUTPUT_DIR>— Directory for output files--overwrite— Whether to overwrite the output directory if it already exists--debug-model— Whether to write additional information to CSV files--no-copy-input-files— Whether to skip copying input files to the output folder
muse2 example
Manage example models
Usage: muse2 example <COMMAND>
Subcommands:
list— List available examplesinfo— Provide information about the specified exampleextract— Extract an example model configuration to a new directoryrun— Run an example
muse2 example list
List available examples
Usage: muse2 example list
muse2 example info
Provide information about the specified example
Usage: muse2 example info <NAME>
Arguments:
<NAME>— The name of the example
muse2 example extract
Extract an example model configuration to a new directory
Usage: muse2 example extract <NAME> [NEW_PATH]
Arguments:
<NAME>— The name of the example to extract<NEW_PATH>— The destination folder for the example
muse2 example run
Run an example
Usage: muse2 example run [OPTIONS] <NAME>
Arguments:
<NAME>— The name of the example to run
Options:
-o,--output-dir <OUTPUT_DIR>— Directory for output files--overwrite— Whether to overwrite the output directory if it already exists--debug-model— Whether to write additional information to CSV files--no-copy-input-files— Whether to skip copying input files to the output folder
muse2 validate
Validate a model
Usage: muse2 validate <MODEL_DIR>
Arguments:
<MODEL_DIR>— The path to the model directory
muse2 save-graphs
Build and output commodity flow graphs for a model
Usage: muse2 save-graphs [OPTIONS] <MODEL_DIR>
Arguments:
<MODEL_DIR>— The path to the model directory
Options:
-o,--output-dir <OUTPUT_DIR>— Directory for graph files--overwrite— Whether to overwrite the output directory if it already exists
muse2 settings
Manage settings file
Usage: muse2 settings <COMMAND>
Subcommands:
edit— Edit the program settings filedelete— Delete the settings file, if anypath— Get the path to where the settings file is read fromshow— Show the contents of thesettings.toml, if presentshow-default— Show the default settings forsettings.toml
muse2 settings edit
Edit the program settings file
Usage: muse2 settings edit
muse2 settings delete
Delete the settings file, if any
Usage: muse2 settings delete
muse2 settings path
Get the path to where the settings file is read from
Usage: muse2 settings path
muse2 settings show
Show the contents of the settings.toml, if present
Usage: muse2 settings show
muse2 settings show-default
Show the default settings for settings.toml
Usage: muse2 settings show-default
This document was generated automatically by
clap-markdown.
File Formats
The following chapters describe the formats for the various files that MUSE2 consumes/produces. Files are either in CSV or TOML format.
Program settings: settings.toml
User settings for MUSE2. Note that these settings do not include model parameters, which
are found in model.toml instead.
All settings are optional. If the settings.toml file does not exist, the default settings will
be used.
The settings.toml file is read from MUSE2’s platform-specific configuration directory, in a
muse2 subdirectory. Use muse2 settings path to find the exact location on your system.
Top-level table
| Field | Description | Notes |
|---|---|---|
log_level | The program’s log level | Optional. Defaults to info.Must be one of error, warn, info, debug, trace or off (case insensitive) |
overwrite | Whether to overwrite output files by default | Optional. Defaults to false. |
debug_model | Whether to include extra debugging information in the model output | Optional. Defaults to false.This includes raw values such as commodity balance duals, which may be useful for debugging the model or understanding results in more detail. |
copy_input_files | Whether to copy input files to the output folder | Optional. Defaults to true. |
results_root | Results root path to save MUSE2 results | Optional. Defaults to muse2_results.Defaults to a “muse2_results” folder within the current working directory. |
graph_results_root | Results root path to save MUSE2 graph outputs | Optional. Defaults to muse2_graphs.Defaults to a “muse2_graphs” folder within the current working directory. |
Input files
This file contains information about the file formats for MUSE2 input files. It is not required to supply every input file - empty files can be omitted.
Model parameters: model.toml
Parameters which affect the behaviour of the model. Most are optional.
Top-level table
| Field | Description | Notes |
|---|---|---|
milestone_years | The milestone years for the simulation | Must be positive integers that are sorted and unique |
currency | Optional currency label for monetary model inputs | 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 | Capacity to give to candidate assets in dispatch optimisation | Optional. Defaults to 0.0001.The default value should work. Do not change this value unless you know what you’re doing! |
commodity_balance_epsilon | Epsilon added to commodity balance lower bounds to force some dispatch by candidate assets | Optional. Defaults to 1e-6.The default value should work. Do not change this value unless you know what you’re doing! |
capacity_tranche_fraction | Scales the size of inferred investment tranches for candidate assets | Optional. Defaults to 0.05.When a process does not define tranche_size, candidate assets for investment are assigned a tranche whose capacity is this fraction of the characteristic capacity scale based on annual demand. Each investment round appraises one such tranche.Must be >0 and <=1. Lower values produce smaller tranches and require more investment rounds, while higher values produce larger tranches. |
fallback_pricing_strategy | Pricing strategy used to calculate fallback prices in the mini dispatch optimisation during investment appraisal | Optional. Defaults to full_average.The fallback price represents the cost of procuring the commodity of interest from an alternative source rather than producing it with the asset being appraised. It provides an additional incentive for an asset to dispatch even when shadow prices alone are insufficient. Setting this to unpriced uses a fallback price of zero, reverting to the pure shadow-price formulation. |
value_of_lost_load | The cost applied to unmet demand | Optional. Defaults to 1e9.Currently this only applies to the LCOX appraisal. |
seasonal_utilisation_penalty | Penalty per unit of capacity used within a season | Optional. Defaults to 1e-6.A small additive penalty to discourage assets from using a high capacity-equivalent peak within a season. This should be very small, as it is only intended to break ties between otherwise equivalent solutions. |
annual_utilisation_penalty | Penalty per unit of capacity used across the whole year | Optional. Defaults to 1e-6.A small additive penalty to discourage assets from using a high capacity-equivalent peak across the year. This should be very small, as it is only intended to break ties between otherwise equivalent solutions. |
max_ironing_out_iterations | The maximum number of iterations to run the “ironing out” step of agent investment for | Optional. Defaults to 1. |
price_tolerance | The relative tolerance for price convergence in the ironing out loop | Optional. Defaults to 1e-6. |
capacity_margin | Slack proportion for assets selected during cycle balancing to absorb small demand shifts | Optional. Defaults to 0.2. |
mothball_years | Number of years an asset can remain unused before being decommissioned. Defaults to 0, decommissioning assets as soon as they are left unused. | Optional. Defaults to 0. |
please_give_me_broken_results | Allows other options that are potentially dangerous to be used, such as experimental features and parameters that are known to have correctness bugs. This option should only ever be enabled by developers. Never enable this for experiments that you care about, particularly if you intend to publish the results. | |
remaining_demand_absolute_tolerance | Absolute tolerance when checking if remaining demand is close enough to zero in the investment cycle. Changing the value of this parameter is potentially dangerous, so it requires setting please_give_me_broken_results to true. | Optional. Defaults to 1e-12. |
highs | Used for setting custom HiGHS options. As this is unsafe, it requires setting please_give_me_broken_results to true. For more information, see the relevant section of the developer documentation. |
[highs] table
| Field | Description | Notes |
|---|---|---|
global_options | HiGHS options applied to all optimisations | |
dispatch_options | HiGHS options applied only to dispatch optimisations | |
appraisal_options | HiGHS options applied only to appraisal optimisations |
Time slices
time_slices.csv
Time slices define how the year is divided up. Each time slice is composed of a season and a time
of day. Often, this is written separated by a dot (e.g. winter.day).
| Field | Description | Notes |
|---|---|---|
season | Name of season | |
time_of_day | Name of time of day | |
fraction | Fraction of year | Must be >0 and <=1 |
Notes
- The fractions for the different time slices must sum to one.
Regions
regions.csv
Describes regions in the system.
| Field | Description | Notes |
|---|---|---|
id | A unique identifier for a region | |
description | A human-readable label for the region |
Agents
agents.csv
Describes agents in the system.
| Field | Description | Notes |
|---|---|---|
id | A unique identifier for an agent | |
description | A human-readable label for the agent | |
regions | The region(s) in which the agent operates | One or more region IDs, separated by semicolons or the string all |
decision_rule | The decision rule applied to objectives | Currently the only supported rule is single |
decision_lexico_tolerance | Tolerance for lexico decision rule | Currently unused |
agent_search_spaces.csv
Defines the processes in which an agent will invest for given parameters.
| Field | Description | Notes |
|---|---|---|
agent_id | An agent ID | |
commodity_id | The commodity to which this entry applies | |
years | The year(s) to which this entry applies | One or more milestone years separated by semicolons, all to select all years or a year range in the form ‘start..end’ to select all valid years within range, inclusive. Either ‘start’ ‘end’ or both can be omitted, which will set the corresponding limit to the minimum or maximum valid year, respectively. |
search_space | The processes in which this agent will invest | One or more process IDs separated by semicolons or all, meaning all processes producing the relevant commodity in the given year in a relevant region. |
Notes
- If entries are missing for any combination of agent, commodity or milestone year, then it is assumed that all processes can be considered in this case.
agent_commodity_portions.csv
Portions of commodity demand for which agents are responsible.
| Field | Description | Notes |
|---|---|---|
agent_id | The agent to apply these values to | |
commodity_id | The commodity for which the agent is responsible | |
years | The year(s) to which this entry applies | One or more milestone years separated by semicolons, all to select all years or a year range in the form ‘start..end’ to select all valid years within range, inclusive. Either ‘start’ ‘end’ or both can be omitted, which will set the corresponding limit to the minimum or maximum valid year, respectively. |
commodity_portion | Portion of commodity demand | Value must be >0 and <=1. The portion applies only to the specified years. |
Notes
- If an entry is specified for one agent and commodity, there must be entries covering all milestone years.
- For each agent listed in this file, the total portions for each region/commodity/year combination must sum to one.
- In addition, there must be entries for every SVD and SED commodity for all regions and milestone years.
agent_objectives.csv
Describes the agents’ objectives.
| Field | Description | Notes |
|---|---|---|
agent_id | An agent ID | |
years | The year(s) to which this entry applies | One or more milestone years separated by semicolons, all to select all years or a year range in the form ‘start..end’ to select all valid years within range, inclusive. Either ‘start’ ‘end’ or both can be omitted, which will set the corresponding limit to the minimum or maximum valid year, respectively. |
objective_type | The type of objective | Must be npv (net present value) or lcox (levelised cost of X). |
decision_weight | Weight for weighted sum decision rule | Currently unused |
decision_lexico_order | Order in which to consider objectives for lexico decision rule | Currently unused |
Notes
- Every agent must have one objective for each milestone year.
- If the weighted sum decision rule is in use, the
decision_weightvalue must be provided, otherwise it must be omitted. - If the lexico decision rule is in use, the
decision_lexico_ordervalue must be provided, otherwise it must be omitted.
Assets
assets.csv
Defines assets in the system.
| Field | Description | Notes |
|---|---|---|
process_id | The process of which this asset is an instance | |
region_id | The region in which this agent operates | |
agent_id | The agent to which this asset belongs | |
capacity | The capacity of the asset | Must be >0 |
num_tranches | The number of tranches comprising the asset | Optional. If provided, the asset is split into num_tranches tranches of size capacity / num_tranches (taking precedence over the process’s tranche_size). If omitted and the process has a tranche_size, the asset is divided into ceil(capacity / tranche_size) tranches (note that rounding up the number of tranches can result in a total capacity greater than the specified capacity). If both values are omitted, the asset is represented as one tranche with the full asset capacity. Must be >0 if provided. |
commission_year | The year in which to commission this asset | This value can be any integer >=0. If it is before the start of the simulation, it will already be commissioned in the first year and if it after the end of the simulation then it will never be commissioned. |
max_decommission_year | The latest year in which this asset can be decommissioned | Optional. This value can be any integer > commission_year. If it is set before the simulation starting year, the asset will not be commissioned during the simulation. If not set, will default to commission_year + the lifetime of the process in that year. |
Commodities
commodities.csv
Describes commodities in the system.
| Field | Description | Notes |
|---|---|---|
id | A unique identifier for the commodity | |
description | A human-readable label for the commodity | |
type | The type of commodity | Must be one of svd (service demand), sed (supply equals demand) or oth (other) |
time_slice_level | The time slice level at which constraints for this commodity are applied | Must be one of annual (whole year), season (whole season) or daynight (a particular time of day) |
pricing_strategy | The pricing strategy for this commodity | Optional. If specified, must be one of shadow (priced using shadow prices from supply-demand constraints), marginal (priced at the marginal cost of the highest cost active asset), marginal_average (priced at the load-weighted average marginal cost of production across assets), full (priced at the full cost of production of the highest cost active asset, including capital costs), full_average (priced at the load-weighted average full cost of production across assets), scarcity (priced adjusted for scarcity), or unpriced (not priced at all).For svd and sed commodities, it must be one of shadow, marginal, marginal_average, full or full_average. For oth commodities, it must be unpriced.If unspecified, the commodity will use the default pricing strategy according to the commodity type: full_average for svd and sed commodities, and unpriced for oth commodities. |
units | The units of measurement for this commodity | This field is used for validation only and is not used for internal unit conversions. MUSE will validate that all SED/SVD output flows from the same process have the same units, as the annual fixed costs are distributed proportionally between outputs based on flow coefficients. E.g. “PJ” (Petajoules), “t” (Tonnes). |
Notes
- Every SED (supply equals demand) commodity must have both producer and consumer processes for every region and milestone year.
- Every SVD (service demand) commodity must have a producer for every region and milestone year.
commodity_levies.csv
Defines levies for commodities (or, if value is negative, incentives).
| Field | Description | Notes |
|---|---|---|
commodity_id | The commodity to which this entry applies | |
regions | The region(s) to which this entry applies | One or more region IDs, separated by semicolons or the string all |
years | The year(s) to which this entry applies | One or more milestone years separated by semicolons, all to select all years or a year range in the form ‘start..end’ to select all valid years within range, inclusive. Either ‘start’ ‘end’ or both can be omitted, which will set the corresponding limit to the minimum or maximum valid year, respectively. |
time_slice | The time slice(s) to which this entry applies | Can be a single time slice (e.g. winter.day), a whole season (e.g. winter) or annual, representing the whole year |
balance_type | The type of balance to which this is applied | Can be cons (applies to consumption only), prod (applies to production only) or net (applies to production, with an equal and opposite levy/incentive on consumption) |
value | The value of the levy/incentive |
Notes
- If an entry is included for a given combination of commodity and region, entries must be provided covering all milestone years and time slices. For those regions not explicitly included for a given commodity, a zero levy/incentive is assumed. For one particular combination of region, year and time slice, there can be one production and one consumption levy, or a single net levy.
demand.csv
Specifies the demand for service demand commodities.
| Field | Description | Notes |
|---|---|---|
commodity_id | The service demand commodity to which this entry applies | |
region_id | The region to which this entry applies | A region ID |
year | The year(s) to which this entry applies | One or more milestone years separated by semicolons, all to select all years or a year range in the form ‘start..end’ to select all valid years within range, inclusive. Either ‘start’ ‘end’ or both can be omitted, which will set the corresponding limit to the minimum or maximum valid year, respectively. |
demand | Total demand for this year |
Notes
- Every service demand commodity must have entries in this file covering every combination of milestone year and region.
demand_slicing.csv
Specifies how demand is distributed throughout the year (i.e. for different milestones).
| Field | Description | Notes |
|---|---|---|
commodity_id | The service demand commodity to which this entry applies | |
region_id | The region to which this entry applies | A region ID |
time_slice | The time slice(s) to which this entry applies | Can be a single time slice (e.g. winter.day), a whole season (e.g. winter) or annual, representing the whole year |
fraction | The fraction of annual demand for this entry | Must be >0 and <=1 |
Notes
- The user must provide entries covering every combination of service demand commodity, region and time slice.
- For each combination, the sum of the fractions must equal one.
Processes
processes.csv
Describes processes in the system.
| Field | Description | Notes |
|---|---|---|
id | A unique identifier for a process | |
description | A human-readable label for the process | |
regions | The region(s) in which this process can operate | One or more region IDs, separated by semicolons or the string all |
primary_output | The primary output commodity, if any, for this process | This field must be omitted for processes with only input flows. It can be omitted if there is only one output flow, in which case it will be inferred. |
start_year | The first year when this process is available to agents | |
end_year | The last year when this process is available to agents | Must be >= to start_year |
capacity_to_activity | Factor relating capacity units (e.g. GW) to activity units (e.g. PJ). It is the maximum activity per year for one unit of capacity. | Must be >0. Optional (defaults to 1.0). |
tranche_size | Default capacity of the individual tranches making up assets of this process | Optional (defaults to None). If present, must be >0. If no tranche size is specified, the tranche size for a MUSE2 investment is inferred from demand at the time of investment and capacity_tranche_fraction. For a user-provided asset, num_tranches in assets.csv can instead determine the tranche size. |
process_parameters.csv
Parameters for processes which vary by region and year.
| Field | Description | Notes |
|---|---|---|
process_id | The process to which this entry applies | |
regions | The region(s) to which this entry applies | One or more region IDs, separated by semicolons or the string all. Must be regions in which the process operates. |
commission_years | The milestone year(s) to which this entry applies | One or more milestone years separated by semicolons, all to select all years or a year range in the form ‘start..end’ to select all valid years within range, inclusive. Either ‘start’ ‘end’ or both can be omitted, which will set the corresponding limit to the minimum or maximum valid year, respectively. |
capital_cost | Overnight capital cost per unit capacity | |
fixed_operating_cost | Annual operating cost per unit capacity | |
variable_operating_cost | Annual variable operating cost per unit activity | |
lifetime | Lifetime in years of an asset created from this process | Must be a positive integer |
discount_rate | Process-specific discount rate | Must be positive. A warning will be issued if this number is >1. |
Notes
- For each process, entries must be provided which cover every combination of milestone year and region in which the process can be commissioned.
process_flows.csv
The commodity flows for each process.
| Field | Description | Notes |
|---|---|---|
process_id | The process to which this entry applies | |
commodity_id | The commodity for this flow | |
regions | The region(s) to which this entry applies | One or more region IDs, separated by semicolons or the string all. Must be regions in which the process operates. |
commission_years | The year(s) to which this entry applies | One or more milestone years separated by semicolons, all to select all years or a year range in the form ‘start..end’ to select all valid years within range, inclusive. Either ‘start’ ‘end’ or both can be omitted, which will set the corresponding limit to the minimum or maximum valid year, respectively. |
coeff | The flow for this commodity | Can be <0, indicating an input flow, or >0, indicating an output flow. Cannot be zero. |
type | The type of commodity flow | Currently this value must be fixed |
cost | The cost per unit flow | Optional. If present, must be >0. |
Notes
- Commodity flows can vary by region and year.
- For each process, there must be entries covering all the years and regions in which the process can be commissioned.
process_investment_constraints.csv
Constraints on the amount agents can invest in processes.
| Field | Description | Notes |
|---|---|---|
process_id | The process to which this entry applies | |
regions | The region(s) to which this entry applies | One or more region IDs, separated by semicolons or the string all. Must be regions in which the process operates. |
commission_years | The milestone year(s) to which this entry applies | One or more milestone years separated by semicolons, all to select all years or a year range in the form ‘start..end’ to select all valid years within range, inclusive. Either ‘start’ ‘end’ or both can be omitted, which will set the corresponding limit to the minimum or maximum valid year, respectively. Must be within the process’s year range. |
addition_limit | Yearly constraint on the amount agents can invest in the process | The addition limit is allocated between all agents in proportion to their share of the process’s primary commodity demand. |
capacity_growth_limit | The constraint on fractional growth per year, based on the available stock in a year, per region and technology. E.g 0.1 -> max 10% growth allowed. | This is currently unused. |
growth_seed | A parameter to scale the initial capacity value used in the capacity growth calculation, allowing growth to initiate when capacity is low/zero. Defaults to 1 if unspecified. | This is currently unused. |
total_capacity_limit | A hard upper limit on the total amount of installed capacity that can exist at any given time | The total capacity limit is allocated between all agents in proportion to their share of the process’s primary commodity demand. |
process_availabilities.csv
Defines constraints on process availabilities throughout the year.
| Field | Description | Notes |
|---|---|---|
process_id | The process to which this entry applies | |
regions | The region(s) to which this entry applies | One or more region IDs, separated by semicolons or the string all |
commission_years | The milestone year(s) to which this entry applies | One or more milestone years separated by semicolons, all to select all years or a year range in the form ‘start..end’ to select all valid years within range, inclusive. Either ‘start’ ‘end’ or both can be omitted, which will set the corresponding limit to the minimum or maximum valid year, respectively. |
time_slice | The time slice(s) to which this entry applies | Can be a single time slice (e.g. winter.day), a season (e.g. winter) or annual. If a season or annual, this means that limit will apply to the season/year as a whole. |
limits | Lower and upper limits on the availability of the process within the specified time slice(s) | A string in the format min..max, where min and max are decimal numbers between 0 and 1 inclusive (e.g. “0.2..0.9”). 0 represents no availability, and 1 represents full availability. Either min or max can be omitted (e.g “0.2..” or “..0.9”), which will set the corresponding limit to 0 or 1, respectively. |
Notes
- Limits can be combined across multiple time periods. For example, users could provide a limit of “..0.9” for “winter.day” and “..0.5” for “winter”, indicating that the activity can reach up to 90% during winter days, so long as activity does not exceed 50% across winter as a whole.
- Incompatible limits will be flagged at the input validation stage. For example, a limit of “..0.01” for “winter” would likely be incompatible with a limit of “0.9..” for “winter.day”.
- If a limit is provided for any time slice (or season) for a particular process/region/year, it must be provided for all time slices (or seasons).
Output Files
This file contains information about the file formats for MUSE2 output files.
Note that we only document the main CSV output files, not those produced when the --debug-model
option is active.
Metadata: metadata.toml
Metadata about the program run, the MUSE2 build and the user’s platform.
Top-level table
| Field | Description | Notes |
|---|---|---|
run | Information about the program run | |
program | Information about MUSE2 | |
platform | Information about the platform on which the simulation was run |
[run] table
| Field | Description | Notes |
|---|---|---|
model_path | Path to the input model | |
datetime | Timestamp for the model run | Time at which the model run commenced |
[program] table
| Field | Description | Notes |
|---|---|---|
name | The name of the program (muse2) | |
version | Program version (e.g. 2.0.0) | |
target | The target triple MUSE2 was built for | |
is_debug | Whether the current build is a debug or release build | |
rustc_version | The version of rustc used to compile MUSE2 | |
build_time_utc | The UTC time and date at which MUSE2 was built | |
git_commit_hash | The short hash of the git commit from which MUSE2 was built | If the git working tree was dirty, -dirty is appended to the hash |
[platform] table
| Field | Description | Notes |
|---|---|---|
sysname | The name of the implementation of the operating system | |
nodename | The hostname of the machine | |
release | The release level of the operating system | |
version | The version level of the release | |
machine | The name of the system’s hardware | |
osname | The name of the current OS |
Main CSV output files
assets.csv
Assets commissioned during the course of the simulation.
| Field | Description | Notes |
|---|---|---|
asset_id | The ID number of this asset | |
process_id | The ID of this asset’s associated process | |
region_id | The region in which this asset exists | |
agent_id | The agent to which this asset belongs | |
commission_year | The year in which the asset was commissioned |
asset_capacities.csv
Capacity of each asset at each milestone year during the simulation.
| Field | Description | Notes |
|---|---|---|
milestone_year | The milestone year of the simulation | |
asset_id | The ID of this asset | |
capacity | The total capacity of the asset in this year | |
num_tranches | The total number of tranches making up the asset in this year | |
mothballed_capacity | The capacity of the asset that is mothballed in this year | |
mothballed_tranches | The number of tranches that are mothballed in this year |
commodity_flows.csv
How much of each commodity was produced and consumed in the course of the simulation.
| Field | Description | Notes |
|---|---|---|
milestone_year | Milestone year of the simulation | |
asset_id | The asset this entry relates to | See the assets.csv output file for information about assets |
commodity_id | The commodity this entry relates to | |
time_slice | The time slice during which this flow occurred | |
flow | The amount of commodity flow | If the commodity was produced, this value will be positive and if it was consumed, it will be negative. |
commodity_prices.csv
The prices of commodities over time and place.
| Field | Description | Notes |
|---|---|---|
milestone_year | Milestone year of the simulation | |
commodity_id | The commodity this entry relates to | |
region_id | The region this entry relates to | |
time_slice | The time slice in which this price holds | |
price | The price of the commodity |
Model Description
The purpose of MUSE2 (ModUlar energy systems Simulation Environment) is to provide users with a framework to simulate pathways of energy system transition, usually in the context of climate change mitigation.
Model Concept
MUSE2 is a bottom-up engineering-economic modelling framework that computes a price-induced supply-demand equilibrium on a set of user-defined commodities. It does this for each milestone time period within a user-defined time horizon. This is a “partial equilibrium” in the sense that the framework equilibrates only the user-defined commodities, as opposed to a whole economy.
MUSE2 is data-driven in the sense that model processing and data are entirely independent, and user-defined data is at the heart of how the model behaves. It is also “bottom-up” in nature, which 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 for how capacity, activity, commodity flows, and monetary values are related.
At a high level, the user defines:
-
The overall temporal arrangements, including the base time period, milestone time periods and time horizon, and within-period time slice lengths.
-
The service demands for each end-use (e.g. residential heating, steel production), for each region, and how that demand is distributed between the user-defined time slices within the year. Service demands must be given a value for the base time period and all milestone time periods in each region.
-
The existing capacity of each process (i.e. assets) in the base time period, and the year in which it was commissioned or will be decommissioned.
-
The techno-economic attributes (e.g. capital cost, operating costs, efficiency, lifetime, input and output commodities, etc) of each process. This must include attributes of processes existing in the base time period (i.e. assets) and possible future processes that could be adopted in future milestone time periods.
-
The agents that choose between processes by applying search spaces, objectives and decision rules. Portions of demand for each commodity must be assigned to an agent, and the sum of these portions must be one.
Framework Overview
The model operates sequentially across a series of milestone years (MSYs). For the base year, existing assets are commissioned and a dispatch optimisation is run to establish commodity prices for the first investment year. For each subsequent MSY, the model performs the following steps:
1. Decommission end-of-life assets
Assets whose scheduled decommissioning year has been reached are removed from the active pool.
2. Agent investment
Agents select assets to meet commodity demand for the current MSY. Investment decisions follow a pre-computed investment order derived from the topology of the commodity graph: markets are processed deepest-first (most downstream to most upstream), grouped into layers of independent markets at the same depth. See Investment Appraisal for full details.
After all markets in each layer are settled, a partial system dispatch is run over all assets selected so far. This propagates demand upstream: input commodity flows consumed by newly committed assets become demand targets for the markets not yet invested in.
3. Mothballing and decommissioning
Previously commissioned assets that were not selected for retention are mothballed. Any asset that
has remained mothballed for longer than mothball_years (defined in
model.toml) is permanently decommissioned.
4. Ironing-out loop
Investment and dispatch are repeated iteratively to resolve any price instability introduced by new
capacity commitments. Each iteration runs the full agent investment pass followed by a full system
dispatch. The loop terminates when time-slice-weighted market prices have converged within
price_tolerance, or when max_ironing_out_iterations is reached (both defined in
model.toml).
5. Final dispatch
A full dispatch optimisation is run over the settled asset pool. The resulting flows and commodity prices are written to the output files and carried forward as the price basis for the next MSY’s investment appraisal.
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:
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:
- Choose units for each commodity, such as PJ for energy and ktCO2 for emissions.
- Choose a capacity unit for each process, such as GW for a power plant.
- Set
capacity_to_activityso that capacity converts to the appropriate annual activity scale. - Define flow coefficients so that activity produces and consumes the intended commodity quantities.
- 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.
Dispatch Optimisation
This section describes the formulation of the dispatch optimisation model in MUSE2. For a given milestone year (MSY), the dispatch model calculates the least-cost operation of the energy system assets to satisfy commodity demands. It is solved as a Linear Programme (LP) using the HiGHS solver.
Sets and Indices
The energy system is defined using the following sets:
- \( r \in \mathbf{R} \): Regions. Represents distinct geographical areas.
- \( t \in \mathbf{T} \): Time Slices. Discrete operational sub-periods within a year.
- \( s \in \mathbf{S} \): Time Slice Selections. Collections of time slices representing temporal groupings at different levels (e.g. a single time slice, a season, or the entire year).
- \( a \in \mathbf{A} \): Assets. All commissioned production, consumption, or conversion technologies.
- \( c \in \mathbf{C} \): Commodities. All energy carriers, emissions, or materials, partitioned into:
- \( \mathbf{C}^{\mathrm{SVD}} \): Service Demand commodities (representing final end-use demands).
- \( \mathbf{C}^{\mathrm{SED}} \): Supply-Equals-Demand commodities (intermediate carrier flows like electricity or hydrogen).
- \( \mathbf{C}^{\mathrm{OTH}} \): Other commodities (e.g. emissions or tracked flows not subject to supply-demand balancing).
Decision Variables
The dispatch model determines the activity level of all assets \( a \in \mathbf{A} \) in all time slices \( t \in \mathbf{T}\):
\[ \mathrm{Activity}_{a, t} \ge 0 \text{ for all } a \in \mathbf{A}, t \in \mathbf{T} \]
Activity is a dimensionless quantity representing the level of operation of the asset. It is multiplied by flow coefficients to give the corresponding commodity flows in the units of that commodity (e.g. PJ of energy for an energy flow).
Objective Function
The objective is to minimise the total operating cost of the energy system:
\[ \text{Minimise } \sum_{a \in \mathbf{A}} \sum_{t \in \mathbf{T}} \mathrm{Activity}_{a, t} \cdot \mathrm{Cost}_{\mathrm{Activity},a,t} \]
Activity Cost Coefficient \( \mathrm{Cost}_{\mathrm{Activity},a,t} \)
The cost per unit of activity for asset \( a \) in time slice \( t \) is composed of variable operating costs, flow costs, and levies:
\[ \mathrm{Cost}_{\mathrm{Activity},a,t} = \mathrm{VariableOpex}_a + \sum_{f \in \mathrm{Flows}_a} |f_{\mathrm{coeff}}| \cdot \left( f_{\mathrm{cost}} + \mathrm{Levy}_{f,t} \right) \]
- \( \mathrm{VariableOpex} \): The non-fuel variable O&M cost of the process.
- \( f_{\mathrm{coeff}} \): The flow coefficient for a commodity flow associated with the asset (positive for output/production, negative for input/consumption).
- \( f_{\mathrm{cost}} \): The cost per unit of commodity flow.
- \( \mathrm{Levy}_{f,t} \): The regional levy (or subsidy, if negative) per unit of commodity flow in time slice \( t \).
Constraints
Asset Activity Limits
For each asset \( a \) and every time slice selection \( s \):
\[ \mathrm{Avail}_{\mathrm{LB},a,s} \cdot \Delta_s \cdot \mathrm{Capacity}_a \cdot \mathrm{cap2act}_a \le \sum_{t \in s} \mathrm{Activity}_{a, t} \le \mathrm{Avail}_{\mathrm{UB},a,s} \cdot \Delta_s \cdot \mathrm{Capacity}_a \cdot \mathrm{cap2act}_a \]
where:
- \( \mathrm{Capacity}_a \) is the fixed installed capacity of the asset.
- \( \mathrm{cap2act}_a \) is the conversion factor from capacity to activity units.
- \( \Delta_s = \sum_{t \in s} \Delta_t \) is the total duration of selection \( s \) as a fraction of the year.
- \( \mathrm{Avail}_{\mathrm{LB},a,s} \) and \( \mathrm{Avail}_{\mathrm{UB},a,s} \) are the
lower and upper availability fractions from
process_activity_limits.csv, defaulting to \( 0 \) and \( 1 \) respectively for any selection not explicitly defined.
Equal Utilisation of Equivalent Assets
To avoid arbitrarily utilising one asset over another, the dispatch model adds additional constraints to equalise the utilisation of assets with equivalent dispatch properties. Assets are considered equivalent when they are in the same region and have the same variable operating cost, activity limits, and commodity flows. Their capacity, state, and process identity are not considered when determining equivalence.
For an asset \(a\) in time slice \(t\), utilisation is defined as
\[ \mathrm{Utilisation}_{a,t} = \frac{\mathrm{Activity}_{a,t}}{\mathrm{Capacity}_a \cdot \mathrm{cap2act}_a} \]
For every pair of equivalent assets \( x \) and \( y \), and for every time slice \( t \), the optimisation model imposes:
\[ \mathrm{Utilisation}_{x,t} = \mathrm{Utilisation}_{y,t} \]
Commodity Balance Constraints
For each balanced commodity \( c \in \mathbf{C}^{\mathrm{SED}} \cup \mathbf{C}^{\mathrm{SVD}} \)
in region \( r \) and time slice selection \( s \) at the commodity’s temporal resolution
(time_slice_level), the sum of production across all assets minus consumption must satisfy
demands:
\[ \sum_{a \in \mathbf{A}_r} f_{\mathrm{coeff},a,c} \cdot \sum_{t \in s} \mathrm{Activity}_{a, t} \ge \mathrm{Bound}_{c, r, s} \]
where:
- \( f_{\mathrm{coeff},a,c} \) is the flow coefficient of commodity \( c \) for asset \( a \) (positive for outputs, negative for inputs).
- \( \mathrm{Bound}_{c, r, s} \) is the constraint lower bound:
- For Service Demand (
SVD): \( \mathrm{Demand}_{c, r, s} \) - For Supply-Equals-Demand (
SED): \( 0 \)
- For Service Demand (
Commodity Consumption/Production Constraints
Commodity constraints impose lower and upper limits on the total production or consumption of a commodity in a region over a specified time slice selection:
\[ L_{c,r,s} \leq \sum_{a \in \mathbf{A}_r^d} |f_{\mathrm{coeff},a,c}| \cdot \sum_{t \in s} \mathrm{Activity}_{a,t} \leq U_{c,r,s} \]
where:
- \( d \) is the balance type: production (
prod) or consumption (cons). - \( \mathbf{A}_r^d \) contains assets in region \( r \) with flows in direction \( d \).
- \( L_{c,r,s} \) and \( U_{c,r,s} \) are the lower and upper limits.
These constraints are defined in the optional commodity_constraints.csv file. They can apply to
SED and OTH commodities, but not SVD commodities. The feature is experimental and requires
please_give_me_broken_results = true in model.toml.
Shadow Prices
The dual values (shadow prices) of the commodity balance constraints represent the marginal cost of satisfying an additional unit of demand for that commodity in region \( r \) during selection \( s \). These shadow prices are critical outputs of the dispatch model and are used to seed and guide investment appraisal in subsequent steps.
Seasonal/Annual Utilisation Penalties
MUSE2 optionally applies small penalties to the peak capacity required by each asset within a season and across the whole year. These penalties encourage activity to be distributed across time slices within a season and across seasons, respectively. They are particularly useful when commodities are balanced at the seasonal/annual levels and the balance constraint otherwise leaves the intra-seasonal or inter-seasonal production profile undetermined. In real-world terms, this represents a preference to avoid concentrating an asset’s operation into short periods of high utilisation, which may reduce cycling, wear, start-up requirements, or the need to maintain capacity for seasonal peaks.
For asset \(a\) and time slice \(t\), the capacity required to support its activity is
\[ \mathrm{RequiredCapacity}_{a,t} = \frac{\mathrm{Activity}_{a,t}} {\mathrm{cap2act}_a \cdot \Delta_t} \]
For each asset and season, MUSE2 introduces an auxiliary variable \(U_{a,s}\), representing the peak capacity required by the asset during that season. It is constrained by
\[ U_{a,s} \geq \mathrm{RequiredCapacity}_{a,t} \]
for every time slice \(t\) in season \(s\). In addition, MUSE2 introduces an auxiliary variable \(U_{a,\mathrm{annual}}\), representing the greatest seasonal peak capacity required by the asset during the year. It is constrained by
\[ U_{a,\mathrm{annual}} \geq U_{a,s} \]
for every season \(s\).
When enabled, the penalties add the following term to the optimisation objective:
\[ \lambda_{\mathrm{seasonal}} \sum_{a \in \mathbf{A}} \sum_{s \in \mathbf{S}} \Delta_s U_{a,s} + \lambda_{\mathrm{annual}} \sum_{a \in \mathbf{A}} U_{a,\mathrm{annual}} \]
Here, \(\lambda_{\mathrm{seasonal}}\) and \(\lambda_{\mathrm{annual}}\) are set by the
seasonal_utilisation_penalty and annual_utilisation_penalty model parameters, respectively.
Setting either parameter to zero disables its corresponding penalty. The seasonal parameter controls
how strongly activity is spread within seasons, while the annual parameter controls how strongly it
is spread across seasons. Both are weighted objective terms, so their values should be small enough
that smoothing dispatch does not outweigh meaningful differences in operating cost (default for
both = 1e-6).
Candidate Dispatch Run
After the primary dispatch run, MUSE2 performs a second dispatch run that includes candidate assets — technologies not yet commissioned but available for investment — alongside the existing assets. The purpose of this run is to obtain shadow prices for commodities that are not consumed or produced in the primary dispatch solution.
In the primary dispatch, a commodity balance constraint will only have a non-zero shadow price if the corresponding constraint is binding (i.e. supply exactly meets demand). For a commodity that is neither produced nor consumed by any active asset, the constraint is trivially satisfied and its dual value is zero. This gives no signal to the investment model about the potential value of new capacity for that commodity.
By including candidate assets in the second run, MUSE2 ensures that every commodity served by at least one candidate process has a balance constraint that can be binding, yielding a meaningful shadow price. To guarantee a non-zero shadow price even when there is no existing demand for the commodity, a small epsilon is added to the lower bound of commodity balance constraints where candidate assets are present:
\[ \mathrm{Bound}_{c, r, s} = \begin{cases} \max\left( \mathrm{Demand}_{c, r, s}, \epsilon \right) & \text{if } c \in \mathbf{C}^{\mathrm{SVD}} \text{ and candidate assets serve } (c, r, s) \\ \epsilon & \text{if } c \in \mathbf{C}^{\mathrm{SED}} \text{ and candidate assets serve } (c, r, s) \\ \mathrm{Demand}_{c, r, s} & \text{if } c \in \mathbf{C}^{\mathrm{SVD}} \\ 0 & \text{if } c \in \mathbf{C}^{\mathrm{SED}} \end{cases} \]
where \( \epsilon \) is the commodity_balance_epsilon parameter. The shadow prices from this
candidate dispatch run are then used to seed and guide investment appraisal in subsequent steps.
Diagnosing Infeasible Models
In practice, a dispatch optimisation run may be infeasible for several reasons, such as insufficient installed asset capacity to meet demand or incompatible commodity production/consumption constraints. When this occurs, MUSE2 performs additional dispatch runs with modified optimisation problems to help identify the cause. The resulting diagnostic information is included in error messages and saved in the dispatch debug files.
Unmet Demand Diagnostic
- Diagnostic Re-Run: MUSE2 reruns the dispatch optimisation with a set of slack variables representing unmet demand, \( \mathrm{UnmetD}_{c, r, t} \ge 0 \), added to the commodity balance constraints: \[ \sum_{a \in \mathbf{A}_r} f_{\mathrm{coeff},a,c} \cdot \sum_{t \in s} \mathrm{Activity}_{a, t} + \sum_{t \in s} \mathrm{UnmetD}_{c, r, t} \ge \mathrm{Bound}_{c, r, s} \]
- Objective Penalty: To ensure the solver only leaves demand unmet if it is mathematically
impossible to satisfy it, these variables are heavily penalised in the diagnostic objective function
using the
value_of_lost_loadparameter (\( \mathrm{VoLL} \)): \[ \text{Minimise } \sum_{a \in \mathbf{A}} \sum_{t \in \mathbf{T}} \mathrm{Activity}_{a, t} \cdot \mathrm{Cost}_{\mathrm{Activity},a,t} + \mathrm{VoLL} \cdot \sum_{c, r, t} \mathrm{UnmetD}_{c, r, t} \] - Isolating Shortfalls: The addition of \( \mathrm{UnmetD}_{c, r, t} \) guarantees that the LP remains mathematically feasible. When solved, any time slice, region, or commodity with a shortfall will have \( \mathrm{UnmetD}_{c, r, t} > 0 \).
- Error Reporting: MUSE2 scans the solution, identifies all balanced markets \( (c, r) \) where unmet demand occurred, outputs detailed diagnostic CSV files, and aborts the simulation with an error identifying the exact out-of-balance markets.
Commodity Constraints Diagnostic
If the dispatch optimisation remains infeasible, MUSE2 reruns it with the commodity
consumption/production constraints disabled. If this rerun succeeds, the infeasibility is likely
caused by one or more constraints defined in commodity_constraints.csv.
If the rerun remains infeasible, commodity constraints are not identified as the cause. Since commodity constraints are an experimental feature, this diagnosis should be treated as indicative rather than definitive.
Investment Appraisal
This section describes the investment and asset retention process applied at each milestone year
(MSY). For each commodity market (a commodity–region pair), processed in investment order
(see below), agents evaluate every available supply option — existing commissioned assets and new
candidate assets from their search space — and select the best option to commit. The committed
asset’s production is subtracted from the remaining demand for that market, and the process
repeats until demand is met or no feasible options remain. Demands for ServiceDemand (SVD)
commodities are fixed from the input data, while demands for SupplyEqualsDemand (SED)
commodities accumulate as assets are committed earlier in the investment order.
Investment Order
Investment decisions are made sequentially, starting from the most downstream commodity markets and moving upstream. For example, in a model where gas may be used to generate electricity, investment in electricity generation would happen before investment in gas production.
This ordering ensures that when an upstream market is being invested in, the demand created by already-committed downstream assets is already known.
Markets are processed in layers: a layer is a set of commodity markets that are independent of one another (i.e. at the same depth in the commodity graph and with no dependencies between them). All markets within a layer are settled before moving to the next layer. After each layer is settled, a single partial system dispatch is run over all assets selected so far. This quantifies the input commodity flows consumed by newly committed assets — for example, a gas generator committed during electricity market investment will consume gas, creating demand that the gas market investment must subsequently meet.
Only commodities of type ServiceDemand and SupplyEqualsDemand are subject to
investment decisions. Other commodity types (e.g. OTH) are excluded.
Note: the investment order is the reverse of the price calculation order, where prices are computed upstream first.
Circularities
When commodity markets form a cycle (e.g. electricity → hydrogen → electricity), the markets in
the cycle are resolved in sequence within one pass. After each market in the cycle is visited, a
partial system dispatch is run to rebalance demand. Newly committed
assets within the cycle are given limited capacity flexibility, controlled by the capacity_margin
parameter (defined in model.toml), to absorb small demand shifts caused by later
markets in the cycle. If these shifts exceed capacity_margin, the simulation terminates with an
error, and the user should increase this parameter.
Commodity Prices Used in Appraisal
Investment appraisal uses two distinct price sets, both sourced from the previous MSY’s dispatch (or from the previous ironing-out loop iteration if enabled):
- Shadow prices \( \lambda_{c,r,t} \): used for activity coefficients in the mini dispatch optimisation step of each appraisal (see Mini Dispatch Optimisation).
- Market prices \( \pi_{c,r,t} \): used to calculate the investment metric (Cost Index or SNAS) after dispatch.
- Fallback prices \( \phi_{c,r,t} \): used to incentivise dispatch in the mini dispatch
optimisation when shadow prices alone are insufficient (see
Mini Dispatch Optimisation). Calculated using the strategy defined
by
fallback_pricing_strategyinmodel.toml.
See Commodity Prices for how these price sets are calculated.
Agent Shares
Each commodity market may be served by multiple agents, each responsible for a defined share (or portion) of the total demand. An agent’s portion determines:
- The fraction of the total demand that the agent is responsible for meeting.
- The scaling applied to any
addition_limitandtotal_capacity_limitinvestment constraints (see Investment Constraints).
Agent portions for each commodity and milestone year are defined in the agent input files.
Investment Options
For each commodity market, agents consider two categories of supply option:
- Existing assets: already-commissioned assets owned by the agent that produce the commodity of interest as their primary output.
- Candidate assets: processes in the agent’s search space with the commodity of interest as their primary output, available to be newly built.
Annualised Fixed Cost
The annualised fixed cost (AFC) per unit of capacity differs between the two categories:
-
Existing assets: AFC comprises only the fixed operations and maintenance (O&M) cost (\( \mathrm{FOM} \)): \[ \mathrm{AFC}_{\mathrm{existing}} = \mathrm{FOM} \]
-
Candidate assets: AFC includes annualised capital expenditure plus fixed O&M: \[ \mathrm{AFC}_{\mathrm{candidate}} = \mathrm{CAPEX} \times \mathrm{CRF} + \mathrm{FOM} \]
where the Capital Recovery Factor (CRF) annualises the upfront capital cost over the asset’s lifetime \( L \) at discount rate \( d \): \[ \mathrm{CRF} = \frac{d \cdot (1 + d)^L}{(1 + d)^L - 1} \]
If \( d = 0 \), then \( \mathrm{CRF} = 1/L \).
Asset Capacities
Every asset consists of one or more equal-capacity tranches. A single-tranche asset is retained or mothballed as a whole, while the tranches of a multi-tranche asset can be retained or mothballed independently (see Mothballing and Decommissioning).
- For assets defined in
assets.csv, an explicitly suppliednum_tranchesdetermines the tranche size. Otherwise, a processtranche_sizedetermines the tranche size. If neither is supplied, the asset consists of one tranche with its full capacity. - Assets invested in by MUSE are commissioned one tranche at a time, using the process
tranche_sizeif defined, or a tranche size inferred from demand at the time of investment (see “trial capacity” below).
Existing assets
Existing assets (i.e assets that have already been commissioned, whether via assets.csv or by
MUSE) are appraised one tranche at a time to decide how many tranches to retain. This allows partial
retention — for example, some tranches of a multi-tranche plant may be retained while others are
mothballed.
Candidate assets
Before a candidate asset for new investment can be appraised, it is assigned a trial capacity. This is the capacity of one tranche, or the amount of capacity that can be installed in a single investment round.
If a process has a defined tranche_size, the trial capacity is set to that tranche size. Otherwise,
the tranche size is inferred from the capacity that would satisfy the total annual demand if the
asset operated at its maximum annual rate:
\[ \mathrm{TrialCapacity} = \frac{\sum_t \mathrm{Demand}_t}{\mathrm{MaxAnnualSupplyPerCapacity}} \times \mathrm{CapacityTrancheFraction} \]
capacity_tranche_fraction (set in model.toml, must be > 0 and <= 1) controls the
size of inferred investment tranches relative to this demand-based capacity. Lower values produce
smaller tranches and therefore require more investment rounds, while higher values produce larger
tranches.
Investment constraints
Processes may be subject to investment constraints defined in
process_investment_constraints.csv: an addition_limit
constraining new-build capacity, and a total_capacity_limit constraining total installed
capacity.
In multi-agent simulations, both limits are scaled by \( \mathrm{AgentPortion} \), the fraction of the commodity market for which an agent is responsible, to give a limit for each agent. One agent falling short of its own limits does not allow others to exceed theirs. Therefore, the overall limits can only ever be reached if all agents max out their own individual limits.
Addition limits
The addition_limit specifies the maximum new capacity that can be built per year, and applies
only to candidate (new-build) assets. The installable capacity limit for a given process
(\( p \)), agent (\( g \)), region (\( r \)), commodity (\( c \)) and milestone year
(\( y \)) is:
\[ \mathrm{MaxInstallableCapacity}_{p,r,y,g} = \mathrm{AdditionLimit}_{p,r,y} \cdot \Delta_{\mathrm{MSY}} \cdot \mathrm{AgentPortion}_{c,r,y,g} \]
where \( \Delta_{\mathrm{MSY}} \) is the number of years since the previous milestone year.
Each selection of a candidate asset tranche counts towards the \(\mathrm{MaxInstallableCapacity} \) of that process. A candidate asset tranche is excluded from consideration if its capacity would exceed the remaining limit.
Total capacity limits
The total_capacity_limit specifies a hard upper limit on the total capacity of a process that
may exist at any given time. This includes both existing assets which are considered for retention,
and candidate assets which are considered for commissioning. The total capacity limit for a given
process, agent, region, commodity and milestone year is:
\[ \mathrm{MaxTotalCapacity}_{p,r,y,g} = \mathrm{TotalCapacityLimit}_{p,r,y} \cdot \mathrm{AgentPortion}_{c,r,y,g} \]
Each selection of a candidate asset tranche, or retention of an existing asset tranche, counts towards the \(\mathrm{MaxTotalCapacity} \) of their respective process. Any tranche (whether for a candidate asset or an existing asset) is excluded from consideration if it would exceed the remaining limit.
Mini Dispatch Optimisation
For each supply option being appraised, a small linear programme (LP) is solved to determine the optimal activity profile given the current remaining demand.
Activity coefficients
The mini dispatch optimisation implicitly frames each time slice as a choice: the asset can either produce the commodity of interest, or it can be treated as procured from an alternative source at the fallback price \( \phi_{c,r,t} \). Each unit of activity produces \( f_{c,\mathrm{primary}} \) units of output, displacing that quantity from the fallback source. The optimiser therefore dispatches the asset whenever doing so is cheaper than procuring from elsewhere.
This is captured by the activity coefficient \( \alpha_t \), which combines two components:
\[ \mathrm{NetOperatingCost}_t = \mathrm{OperatingCost}(t) - \mathrm{RevenueFromFlows}(\lambda, t) \]
\[ \mathrm{FallbackCost} = \phi_{c,r,t} \cdot f_{c,\mathrm{primary}} \]
\[ \alpha_t = \mathrm{FallbackCost} - \mathrm{NetOperatingCost}_t + \varepsilon \]
where \( \mathrm{RevenueFromFlows} \) is the sum of all commodity flow revenues and costs (positive for outputs, negative for inputs) valued at shadow prices, \( \mathrm{OperatingCost} \) is the variable operating cost plus levies and flow costs, \( f_{c,\mathrm{primary}} \) is the primary output flow coefficient, and \( \varepsilon \) is a small positive constant added to ensure that break-even assets are still dispatched.
NetOperatingCost is the net cost of running the asset for one unit of activity at shadow prices — negative when the asset is profitable (revenues exceed costs).
FallbackCost is the cost of procuring one unit of activity’s worth of primary output from an alternative source at the fallback price.
The asset dispatches when \( \alpha_t > 0 \), i.e. when:
\[ \mathrm{NetOperatingCost}_t < \mathrm{FallbackCost} \]
\( \phi_{c,r,t} \) is calculated according to the strategy defined by fallback_pricing_strategy
in model.toml.
Constraints
- Activity bounds: the sum of activity within each time-slice selection is bounded by the asset’s availability limits multiplied by its capacity.
- Demand constraints: demand for a commodity is balanced at the commodity’s defined time-slice level (e.g. annual, seasonal, or time-slice). The total supply (activity × flow coefficient) within each balance bucket must not exceed the remaining demand for that bucket.
Objective
The optimisation maximises the total net revenue across all time slices, subject to the above constraints:
\[ \max \sum_t \alpha_t \cdot \mathrm{Activity}_t \]
Metric Calculation
After the dispatch LP is solved, an investment metric is calculated from the resulting activity profile using market prices.
Market costs per time slice
The market cost \( \mu_t \) is calculated differently depending on the objective type:
-
LCOX: the net cost of operating, excluding revenues from the primary output commodity: \[ \mu_t^{\mathrm{LCOX}} = \mathrm{OperatingCost}(t) - \mathrm{RevenueFromNonPrimaryFlows}(\pi, t) \]
-
NPV: the net cost of operating, including all commodity flows (so negative values represent profit): \[ \mu_t^{\mathrm{NPV}} = \mathrm{OperatingCost}(t) - \mathrm{RevenueFromFlows}(\pi, t) \]
LCOX metric (objective_type = "lcox")
The LCOX metric is calculated as the total annualised cost divided by total annual output, using the above defined market costs which exclude the primary output commodity:
\[ \mathrm{LCOXMetric} = \frac{\mathrm{AFC} \times \mathrm{Capacity} + \sum_t \mathrm{Activity}_t \times \mu_t^{\mathrm{LCOX}}} {\sum_t \mathrm{Activity}_t} \]
Lower values indicate lower-cost investments.
NPV metric (objective_type = "npv")
The NPV metric is based on the Specific Net Annualised Surplus (SNAS). This the net surplus per unit of activity, using market costs that include the primary output commodity:
\[ \mathrm{SNAS} = \frac{-\left(\mathrm{AFC} \times \mathrm{Capacity} + \sum_t \mathrm{Activity}_t \times \mu_t^{\mathrm{NPV}}\right)}{\sum_t \mathrm{Activity}_t} \]
Higher values indicate more profitable investments.
For both metrics, any option with zero total activity after the mini dispatch LP is excluded from consideration, as it cannot contribute to meeting demand. This will generally happen if all time slices have negative activity coefficients, unless the process has lower-bound activity constraints that force activity.
Asset Selection
Sorting and tie-breaking
All feasible options are appraised and ranked by their metric. When two options have approximately equal metrics, the following tie-breaking rules are applied in order:
- Existing commissioned assets are preferred over new candidates.
- Among existing assets, newer assets (commissioned more recently) are preferred.
- If the tie is still unresolved, the first option in the ordering is selected arbitrarily, and a
debug-level log message is emitted.
Selection loop
The best-ranked asset is committed. Its production profile from the mini dispatch optimisation is subtracted from the remaining demand, and the loop repeats with the updated demand profile. This continues until:
- The remaining demand falls below
remaining_demand_absolute_tolerance(inmodel.toml), or - No feasible options remain. In this case, a warning is logged and the loop ends early. The unmet demand may still be satisfied during the full system dispatch, but is not guaranteed.
If demand cannot be met at all due to overly restrictive investment constraints, the simulation terminates with an error.
Note: only production of the primary output commodity is counted against the remaining demand. If a committed asset also produces other commodities as secondary outputs, that side-production does not reduce the demand targets of those other commodity markets. This behaviour may be revised in a future release.
Mothballing and Decommissioning
After investment is complete for a given MSY, any previously commissioned assets (or individual tranches making up the asset) that were not selected for retention are mothballed: their mothball year is recorded and they are removed from the active asset pool. They remain available for potential re-selection in future MSYs.
A mothballed asset that remains unused for mothball_years consecutive years (as defined in
model.toml) is decommissioned — permanently removed from the asset pool and
excluded from all future investment and dispatch.
Example: Gas Power Plant
The following illustrates how LCOX and NPV metrics are calculated for a gas combined-cycle power plant, evaluated across two time slices: \( t_0 \) (peak) and \( t_1 \) (off-peak).
Parameters
Asset flows and operating costs
| Flow | Value | Description |
|---|---|---|
| Electricity output | \( +1.0 \) MWh/MWh activity | Primary output |
| Heat output | \( +0.5 \) MWh/MWh activity | By-product |
| Natural gas input | \( -2.5 \) MWh/MWh activity | Fuel |
| \( \mathrm{OperatingCost} \) | £5/MWh activity | Constant across time slices |
All per-flow costs (\( \mathrm{cost}_{\mathrm{input}} \), \( \mathrm{cost}_{\mathrm{output}} \)) are zero.
Fixed costs and capacity
| Parameter | Value |
|---|---|
| AFC | £1,000/MW |
| Capacity | 100 MW |
Prices (both shadow and market prices are equal in this example)
| Commodity | \( t_0 \) (Peak) | \( t_1 \) (Off-peak) |
|---|---|---|
| Electricity | £90/MWh | £50/MWh |
| Heat | £25/MWh | £15/MWh |
| Natural gas | £35/MWh | £25/MWh |
Mini Dispatch Optimisation (identical for LCOX and NPV)
Activity coefficients use shadow prices:
\( t_0 \): \[ \alpha_{t_0} = (1.0 \times 90) + (0.5 \times 25) + (-2.5 \times 35) - 5 = \text{£10/MWh} \]
\( t_1 \): \[ \alpha_{t_1} = (1.0 \times 50) + (0.5 \times 15) + (-2.5 \times 25) - 5 = \text{£}{-10}\text{/MWh} \]
The optimiser maximises \( 10 \cdot \mathrm{Activity}{t_0} + (-10) \cdot \mathrm{Activity}{t_1} \), so it prefers to dispatch during \( t_0 \) and minimise activity during \( t_1 \), subject to demand and availability constraints.
Suppose the optimiser determines \( \mathrm{Activity}{t_0} = 80 \) MWh and \( \mathrm{Activity}{t_1} = 20 \) MWh.
LCOX Metric
Market costs (excluding primary output):
\[ \begin{aligned} \mu_{t_0}^{\mathrm{LCOX}} &= 5 + (2.5 \times 35) - (0.5 \times 25) = \text{£80/MWh} \\ \mu_{t_1}^{\mathrm{LCOX}} &= 5 + (2.5 \times 25) - (0.5 \times 15) = \text{£60/MWh} \end{aligned} \]
Cost Index: \[ \begin{aligned} \mathrm{CostIndex} &= \frac{(1{,}000 \times 100) + (80 \times 80) + (20 \times 60)}{80 + 20} \\ &= \text{£1,076/MWh} \end{aligned} \]
NPV Metric
Market costs (including primary output):
\[ \begin{aligned} \mu_{t_0}^{\mathrm{NPV}} &= 5 - (1.0 \times 90) - (0.5 \times 25) + (2.5 \times 35) = \text{£}{-10}\text{/MWh} \\ \mu_{t_1}^{\mathrm{NPV}} &= 5 - (1.0 \times 50) - (0.5 \times 15) + (2.5 \times 25) = \text{£10/MWh} \end{aligned} \]
SNAS: \[ \begin{aligned} \mathrm{SNAS} &= \frac{-\left[(1{,}000 \times 100) + (80 \times (-10)) + (20 \times 10)\right]} {80 + 20} \\ &= \text{£}{-994}\text{/MWh} \end{aligned} \]
The negative SNAS indicates that at current market prices, this asset does not generate a surplus over its annualised costs. It would still be selected if it has the highest SNAS among all available options.
Partial System Dispatch
After each layer of commodity markets is settled during the investment loop, a partial system dispatch is run over all assets selected so far. This is a standard dispatch optimisation solve, with three key modifications described below.
Market subset
Only the commodity markets visited so far — i.e. the current market and all those settled in earlier investment rounds — are subject to commodity balance constraints. Markets that are upstream of the current investment frontier are not yet constrained, because no assets have been committed to serve them yet.
Input prices for upstream commodities
Because upstream markets are unconstrained, their commodities have no balance constraints and therefore no shadow prices in this dispatch. Without any signal on the cost of upstream inputs, those inputs would appear free to the solver.
To avoid this, the shadow prices \( \lambda_{c,r,t} \) from the previous MSY (see Commodity Prices Used in Appraisal) are passed as explicit cost penalties on any input flows corresponding to upstream, unconstrained commodities. This ensures the dispatch correctly accounts for the cost of consuming upstream resources, even before those markets have been invested in.
Capacity flexibility in circularities
When markets form a cycle, the partial dispatch after each market in the cycle uses flexible capacity variables for all newly committed assets in the cycle. Rather than fixing the capacity of these assets at their committed value, the solver may adjust the number of selected tranches within the bounds:
\[ \bigl[(1 - \mathrm{capacity_margin}) \cdot \mathrm{NumTranches}_a, \space (1 + \mathrm{capacity_margin}) \cdot \mathrm{NumTranches}_a\bigr] \]
where \( \mathrm{NumTranches}_a \) is the initially committed number of tranches of asset
\( a \). The upper bound is additionally capped by the process’s MaxInstallableCapacity.
Note: Total capacity limits are not yet accounted for - this is a known bug which will be addressed in future versions.
Each flexible capacity variable enters the dispatch as an integer variable, with a cost coefficient equal to the asset’s AFC (annualised capital cost plus fixed O&M) multiplied by the tranche size. This flexibility allows the dispatch to absorb small demand shifts caused by subsequent markets in the cycle.
If the demand shift for a previously settled market in the cycle exceeds what the flexible capacity
can accommodate, the simulation terminates with an error. The user should increase the
capacity_margin parameter (defined in model.toml) in this case.
Using the partial dispatch result
The flow map from the partial dispatch is used to update the net demand seen by each upstream market. For each newly committed asset, its input commodity flows are added to the demand for the corresponding upstream markets, and its primary output flows reduce the remaining demand for the current market. Only primary output flows are counted against demand; secondary outputs do not affect the demand targets of other commodity markets. These updated demands then drive the next investment decisions as the process moves along the investment order, with each market’s committed assets shaping the demand seen by those upstream.
Commodity Prices
This section describes how commodity prices are calculated in MUSE2 at the end of each milestone year iteration. The pricing algorithm operates on the results of the dispatch optimisation, translating flows, capacities, and solver dual values (shadow prices) into commodity market prices.
Pricing Strategies
Each commodity is configured with a specific pricing_strategy via commodities.csv.
The options are:
marginal: Prices are set to the marginal cost of the highest-cost active asset producing the commodity.marginal_average: Prices are set to the output-weighted average marginal cost across all active assets.full: Prices are set to the full cost (marginal cost + annual fixed/capital costs) of the highest-cost active asset producing the commodity.shadow: Prices are taken directly from the shadow prices (dual values) of the commodity balance constraints in the dispatch optimisation.scarcity: Prices are set to the shadow price plus the highest activity dual of the assets producing the commodity in that region and time slice.unpriced: No prices are calculated for the commodity. This is mandatory for all OTH (Other)-type commodities.
Cost Calculations
Cost-based prices (marginal, marginal_average, full, full_average) are calculated based on
process cost parameters, asset capacities and dispatch flows. For assets producing multiple output
commodities (e.g. an asset producing both oil and gas), generic activity costs and fixed costs are
shared between outputs according to output flow coefficients.
Generic Activity Cost
The generic activity cost comprises all operating expenditures, input purchases, and flow costs/levies not associated with specific SED/SVD outputs: \[ \mathrm{GenericActivityCost} = \mathrm{VariableOpex} + \mathrm{InputPurchases} + \sum \mathrm{GenericFlowCosts} \]
This is shared equally over all SED/SVD outputs in proportion to their output coefficients to compute a generic cost per unit of output flow: \[ \mathrm{GenericCostPerOutput} = \frac{\mathrm{GenericActivityCost}}{\sum_{c \in \mathrm{SED,,SVD}} \mathrm{OutputCoefficient}_c} \]
Marginal Cost
The marginal cost of an output commodity \( c \) is the sum of the generic cost per output and any commodity-specific costs (e.g., production levies, flow costs): \[ \mathrm{MarginalCost}_c = \mathrm{GenericCostPerOutput} + \mathrm{SpecificCostPerOutput}_c \]
Full Cost
The full cost includes the marginal cost plus annualised capital and fixed operating costs.
Annualised capital costs are calculated using the Capital Recovery Factor (CRF): \[ \mathrm{AnnualCapitalCostPerCapacity} = \mathrm{TotalCapitalCostPerCapacity} \times \mathrm{CRF} \]
The annualised fixed cost (AFC) is: \[ \mathrm{AFC} = (\mathrm{AnnualCapitalCostPerCapacity} + \mathrm{AnnualFixedOpex}) \times \mathrm{TotalCapacity} \]
This is divided by annual activity to get a cost per unit of activity: \[ \mathrm{AnnualFixedCostPerActivity} = \frac{\mathrm{AFC}}{\mathrm{AnnualActivity}} \]
This is divided again by the sum of SED/SVD output coefficients to get a cost per unit output: \[ \mathrm{AnnualFixedCostPerOutput} = \frac{\mathrm{AnnualFixedCostPerActivity}}{\sum_{c \in \mathrm{SED,,SVD}} \mathrm{OutputCoefficient}_c} \]
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: \[ \mathrm{FullCost}_c = \mathrm{MarginalCost}_c + \mathrm{AnnualFixedCostPerOutput} \]
Candidate asset fallback
For cost-based strategies (marginal, marginal_average, full, full_average), if no active
producers exist in the current year, prices are calculated by looking at candidate assets for
investment in the following milestone year. Prices are set by the candidate asset with the lowest
marginal/full cost, calculated assuming full utilisation (i.e. assuming annual activity equals the
maximum annual activity limit).
In the final milestone year, prices for commodities with no active producer are omitted.
Time Slice Aggregation
For commodities defined at coarser time slice levels (e.g. seasonal or annual), prices are calculated by weighting asset costs across time slices by activity to yield a flat price for the season/year. For candidate assets, or assets with zero activity across the selection, upper activity limits are used as weights.
Price Calculation Order
Prices are calculated sequentially starting from upstream commodities (e.g. raw fuels) and moving downstream to intermediate energy carriers and final service demands.
This sequence is the reverse of the investment order. This ensures that the prices of input commodities (from upstream markets) are calculated and known before they are consumed as inputs by downstream processes evaluating marginal or full cost pricing.
Circularities
When the energy system includes circular commodity flows (e.g., electricity producing hydrogen, which then generates electricity), the resulting cyclic dependencies are resolved as follows:
- Initial Seeding: Cyclic markets are seeded with their shadow prices.
- Sequential Evaluation: Commodities in the cycle are evaluated in sequence, starting with those furthest upstream relative to the rest of the system (i.e., furthest from the commodities downstream of the SCC), and working down. Marginal cost calculations use newly updated prices for input commodities already evaluated in the sequence, and fall back to the seeded shadow prices for any not yet evaluated, which may occur due to circular dependencies.
Example
The following example demonstrates the calculation of full-cost prices for two commodities using the
full pricing strategy.
GASPRD
In this scenario, GASPRD (raw gas) is produced by a single GASDRV (dry gas extraction) asset. This asset produces 1 unit of GASPRD per unit of activity, along with 5.113 units of CO₂ emissions. Suppose an annual utilisation of 0.20786471743 units of activity per unit of capacity (based on dispatch results).
Marginal Cost
The asset incurs a CO₂ levy (0.04/unit) and a variable operating cost of 2.0:
\[ \mathrm{MarginalCost}_{\mathrm{GASPRD}} = ( 5.113 \times 0.04) + 2.0 = 2.20452 \]
Full Cost
Using a lifetime of 25 years, a discount rate of 10%, annual fixed costs of 0.3 per unit capacity, and a total capital cost of 10 per unit capacity:
\[ \begin{aligned} \mathrm{CRF} &= 0.11016807219 \\ \mathrm{AnnualFixedCostPerCapacity} &= (10 \times 0.11016807219 + 0.3) = 1.4016807219 \end{aligned} \]
Accounting for annual utilisation: \[ \mathrm{AnnualFixedCostPerActivity} = \frac{1.4016807219}{0.20786471743} = 6.7432354044 \]
Accounting for the output coefficient: \[ \mathrm{AnnualFixedCostPerOutput} = \frac{6.7432354044}{1} = 6.7432354044 \]
The full cost is: \[ \mathrm{FullCost}_{\mathrm{GASPRD}} = 2.20452 + 6.7432354044 = 8.9477554044 \]
GASNAT
Following on from the above scenario, GASNAT (processed natural gas) is produced by a single GASPRC (gas processing) asset, which consumes GASPRD as an input fuel. This asset consumes 1.05 units of GASPRD and produces 1 unit of GASNAT per unit of activity, along with 2.5565 units of CO₂ emissions. Suppose an annual utilisation of 0.2094885671 units of activity per unit of capacity (based on dispatch results).
Marginal Cost
The asset incurs a CO₂ levy (0.04/unit), a variable operating cost of 0.5 and fuel purchases:
\[ \begin{aligned} \mathrm{CO2Levy} &= 2.5565 \times 0.04 = 0.10226 \\ \mathrm{InputPurchases} &= 8.9477554044 \times 1.05 = 9.39514317462 \\ \mathrm{MarginalCost}_{\mathrm{GASNAT}} &= 0.10226 + 0.5 + 9.39514317462 = 9.99740317462 \end{aligned} \]
Full Cost
Using the same lifetime and discount rate, along with annual fixed costs of 0.21 per unit capacity, and a total capital cost of 7 per unit capacity:
\[ \begin{aligned} \mathrm{CRF} &= 0.11016807219 \\ \mathrm{AnnualFixedCostPerCapacity} &= (7 \times 0.11016807219 + 0.21) = 0.98117650533 \end{aligned} \]
Accounting for annual utilisation: \[ \mathrm{AnnualFixedCostPerActivity} = \frac{0.98117650533}{0.2094885671} = 4.6836756722 \]
Accounting for the output coefficient: \[ \mathrm{AnnualFixedCostPerOutput} = \frac{4.6836756722}{1} = 4.6836756722 \]
The full cost is: \[ \mathrm{FullCost}_{\mathrm{GASNAT}} = 9.99740317462 + 4.6836756722 = 14.6810788468 \]
This example illustrates the sequential nature of price calculation: the GASPRD price is calculated first and then used as an input cost when calculating the GASNAT price.
Model diagrams
This document contains diagrams showing the algorithm used by MUSE2. It is likely to contain errors and omissions and will change as the code is developed. It is principally aimed at MUSE developers.
Functions are described with the following terms:
- Inputs: immutable input arguments; values not modified by function
- Outputs: values returned from function
- Modifies: mutable input arguments; values modified by function
Glossary
Activity: The quantity representing how much an Asset operates during a given period. Its flow coefficients determine the resulting input and output flows of each Commodity.
Agent: A decision-making entity in the system. An Agent is responsible for serving a user-specified portion of a Commodity demand or Service Demand. Agents invest in and operate Assets to serve demands and produce Commodities.
Agent Objective: One or more objectives that an Agent considers when deciding which Process to invest in. Objectives can be economic, environmental, or others. An Agent can use multiple objectives when applying a Decision Rule.
Agent Share: The portion of a Commodity demand for which an Agent is responsible. Agent shares determine how total demand is divided between agents and can also scale investment limits. For example, if total electricity demand is 100 PJ and an Agent has a 25% Agent Share, that Agent is responsible for 25 PJ of demand.
Annualised Capital Cost: The annual cost equivalent of an upfront Capital Cost, calculated using the Capital Recovery Factor for the relevant Asset Lifetime and Discount Rate. For example, an Asset with a £1 million Capital Cost, a 10-year Lifetime, and a 5% Discount Rate has an annualised Capital Cost of approximately £129,500 per year.
Asset: A specific instance of a Process, representing installed or candidate capacity. It carries the Process’s technical and economic properties, and adds instance-specific information such as Capacity, ownership, location, commissioning Year, and operating state.
Asset Commissioning: The point at which an Asset becomes active and available for Dispatch. An Asset is commissioned according to its commission Year, either from user input or following an investment decision.
Asset Decommissioning: The permanent removal of an Asset from the active model. An Asset can be decommissioned when it reaches the end of its allowed Lifetime or after remaining mothballed for the configured period.
Asset Mothballing: The temporary withdrawal of an Asset or some of its tranches from the active pool when it is not selected for retention. A mothballed Asset is not dispatched, but may be reactivated in a later investment period; it is permanently decommissioned if it remains mothballed for longer than a configured number of years.
Asset Tranche: A discrete portion of an Asset’s Capacity. An Asset can consist of multiple Asset Tranches of equal size, which can be retained, mothballed, or decommissioned independently.
Availability: A Process property specifying the maximum, minimum, or fixed percentage of maximum Activity that its Assets can deliver over a period. It can be specified for a single Time Slice, a Season, or a Year. For example, an Asset with 100 MW of Capacity and 50% annual Availability can provide at most 50 MW over the year. This is a limit, not a measure of how much the Asset is actually used; that is its Utilisation.
Base Year: The starting Year of a model run. The Base Year is typically calibrated to known data, including Asset stock and Commodity consumption/production.
Calibration: The act of ensuring that the model represents the system being modelled in a historical base year. A set of Assets must exist in the Base Year sufficient to serve Base Year demands.
Candidate Asset: A hypothetical Asset representing a Process that is available for an Agent to invest in, but has not yet been selected or commissioned. Its Capacity is assigned for investment appraisal and can be adjusted during the investment process.
Capacity: The installed size of an Asset. Together with its Capacity-to-activity factor, it determines the maximum Activity the Asset can perform.
Capacity-to-activity factor: The factor that converts an Asset’s Capacity into its maximum annual Activity.
Capital Cost: The upfront cost per unit of Capacity established in an Asset. It does not vary directly with short-term Activity.
Capital Recovery Factor: The factor used to convert an upfront Capital Cost into an annualised cost over an Asset’s Lifetime at a given Discount Rate. It represents the proportion of the upfront capital cost that is paid each year to spread that cost over the asset’s lifetime. The factor increases with the discount rate and decreases as the lifetime increases. When the discount rate is zero, it is the reciprocal of the lifetime.
Circular Commodity Dependency: A dependency cycle in which the production or consumption of one Commodity depends, directly or indirectly, on another Commodity that eventually depends on the first. For example, electricity can be used to produce hydrogen, which is then used to produce electricity.
Commodity: A substance (e.g. CO₂) or form of energy (e.g. electricity) that can be produced and/or consumed by Processes in the model. A Service Demand is a type of Commodity that is defined at the end point of the system.
Commodity Levy: Represents a tax, levy or other external cost on a Commodity. Levies can be applied to all Commodity production (sum of output of all Assets for that Commodity), net production (sum of output and input for all Assets), or all consumption (sum of input for all Assets). It can also be negative, indicating an incentive on Commodity production/consumption/net.
Commodity Market: The market for a particular Commodity in a Region. MUSE2 evaluates each market during investment and calculates prices for the Commodity based on the operation of relevant Assets and the applicable Pricing Strategy.
Decision Rule: The rule via which an Agent uses the Agent Objective(s) to decide between Process options to invest in.
Demand Distribution: The allocation of annual Service Demand across Time Slices within a Year. It specifies how much of the annual demand occurs in each Time Slice, while the total across the year remains equal to the annual demand.
Discount Rate: The proportion used to convert a Process’s future costs and benefits into present-value terms. For example, a discount rate of 0.05 means that £100 received in one year’s time is valued at approximately £95.24 today (100 / (1 + 0.05)).
Dispatch: The way in which Assets are operated to serve demand. Within the limits imposed by Availability, Commodity balances, and other user-defined constraints, MUSE2 effectively dispatches Assets in Merit Order: cheaper Assets are used first, up to their available capacity or until demand is met. The resulting operation is calculated by a least-cost linear programme.
End Year: The final year in the model Time Horizon.
Equivalent Annual Cost (EAC): The constant annual cost equivalent of an Asset’s upfront capital cost and operating costs over its Lifetime, accounting for the Discount Rate.
Fixed Operating Cost: The annual operating cost charged per unit of an Asset’s Capacity. Unlike Variable Operating Cost, it does not vary directly with Activity.
Flow Coefficient: The factor that converts one unit of Activity into the corresponding input or output flow of a Commodity. Positive coefficients represent production and negative coefficients represent consumption. For example, a flow coefficient of 2 means that one unit of Activity produces two units of that Commodity.
Full Cost: The cost of producing a unit of output including Marginal Cost, annualised Capital Cost, and Fixed Operating Cost. For example, a Process with low fuel costs but high Capital Cost may have a low Marginal Cost but a higher Full Cost.
Input Commodity: A Commodity that flows into a Process. A Process may have multiple input commodities.
Investment Appraisal: The process of evaluating existing and Candidate Assets to determine which investments an Agent should make in a particular Commodity Market and Milestone Year. Appraisal considers the Agent Objective, expected costs, commodity prices, and the asset’s expected operation.
Investment Order: The sequence in which Commodity markets are considered for investment. MUSE2 generally processes downstream markets before upstream markets, so that demand for input Commodities created by committed Assets is known when the upstream markets are evaluated. For example, investment in electricity generation is considered before investment in gas production when gas is used to generate electricity.
Ironing-out Loop: The repeated sequence of Agent investment and Dispatch used to resolve price instability introduced by new capacity commitments. The loop stops when time-slice-weighted market prices converge within the configured tolerance or the maximum number of iterations is reached.
Levelised Cost of X (LCOX): The cost per unit of output Commodity X from an Asset, calculated by dividing its discounted lifetime costs by its discounted lifetime output under a specified Discount Rate.
Lifetime: The expected operational duration of a Process, measured in years.
Marginal Cost: The cost of producing one additional unit of an output Commodity from an Asset. It combines the Process’s Variable Operating Cost with activity-dependent costs associated with the Asset’s commodity flows, such as input prices and levies. It excludes Capital Cost and Fixed Operating Cost.
Merit Order: A method of operating Assets where the cheapest is dispatched first, followed by the next most expensive, etc, until demand is served.
Milestone Year: A Year in the Time Horizon where model results are recorded. A model can have multiple Milestone Years. For example, with a 2025 Base Year and End Year 2100, a user might choose to record outputs in 5-year steps.
Net Present Value (NPV): The value of future costs and benefits expressed in the present, calculated by discounting them using a specified Discount Rate.
OTH (other)-type commodity: A Commodity that is not supply-demand balanced in the model. Includes side products and emissions such as CO₂, or any raw materials that are not output by modelled Processes.
Output Commodity: A Commodity that flows out of a Process. A Process may have multiple output commodities.
Partial Equilibrium: An equilibrium in which only the user-defined Commodities and markets are balanced. MUSE2 does not attempt to equilibrate the whole economy.
Pricing Strategy: The method used to calculate a Commodity’s price. This includes Marginal Cost pricing, Full Cost pricing, and Shadow Price pricing.
Process: A blueprint of an available Process that converts Input Commodities to Output Commodities. Processes have economic attributes of Capital Cost, Fixed Operating Cost per unit Capacity, Variable Operating Cost per unit Activity, and risk Discount Rate. They have physical attributes of quantity and type of Input Commodities and Output Commodities (which implicitly specify efficiency), Availability limits (by Time Slice, Season, and/or Year), and Lifetime (years). When a Process is selected by an Agent for investment an instance of it called an Asset is created.
Region: A geographical area represented in the model. Regions contain the Assets, Processes, Agents, demands, and Commodity markets that apply in that area, and define the boundaries within which trade and other regional interactions are modelled.
Search Space: The set of Processes an Agent is allowed to consider for investment, defined for a particular Commodity, Region, and Year. It determines which Candidate Assets are available during investment appraisal.
Season: A subdivision of a Year that groups related Time Slices. For example, summer, winter, or other.
Sector: Models are often broken down into sectors, each of which is associated with specific Service Demands or specific Commodity production. For example, the residential sector, the power sector, etc.
Service Demand (SVD): A Commodity consumed at the boundary of the modelled system to represent demand for a service. For example, tonne-kilometres of road freight or PJ of useful heat demand.
Shadow Price: The value of relaxing a Commodity balance constraint by one unit, as calculated by the Dispatch optimisation. For example, a high Shadow Price indicates that one additional unit of a Commodity would substantially improve the optimisation objective because supply is scarce.
Supply Equals Demand (SED): An SED Commodity is a type of Commodity that is both consumed and produced by Assets in the system. In fully resolved systems, supply of these Commodities is constrained to be equal to or greater than demand.
Time Horizon: The overall period modelled. For example, 2025–2100.
Time Slice: A discrete operational sub-period within a Year, identified by a Season and a time-of-day category. For example, a Time Slice might represent winter-day or summer-night. Time Slices are the within-year periods over which Activity, demand, and flows are modelled. A Time Slice can be grouped with other Time Slices in a Time-slice Selection to represent a Season, an entire Year, or another temporal grouping.
Time-slice Level: The temporal resolution at which a Commodity’s balance constraints are applied. MUSE2 supports annual, seasonal, and day-night levels, from a whole year to a particular time of day. For example, an annual level balances total supply and demand across the year, whereas a day-night level requires the balance to be met separately in each time-of-day group.
Time-slice Selection: A group of model Time Slices treated as one period when applying an Asset’s Activity or Availability constraints, or balancing a Commodity at its Time-slice Level. For example, a selection can group all winter Time Slices so that a seasonal Availability limit applies to their combined Activity.
Tranche Size: The capacity of one tranche of an Asset. Every Asset has a Tranche Size, either defined when the Asset is created or inferred when an investment is made. Tranche Size defines the granularity at which retention, mothballing, and decommissioning decisions are made. For example, a 100 MW Asset with a 25 MW Tranche Size has four tranches, which can be retained or mothballed independently.
Utilisation: The proportion of an Asset’s Capacity that is actually used to produce its Commodities, ranging from 0 to 1 inclusive. Can be measured at Time Slice, Season, or Year level. For example, an Asset operating at 25 MW from 100 MW of available Capacity has 0.25 Utilisation. Availability describes what the Asset could provide; Utilisation describes what it actually provides.
Variable Operating Cost: A Process parameter specifying the generic operating cost per unit of Activity. It excludes costs associated with commodity flows, such as input prices and levies.
Year: A calendar year used to identify a Base Year, Milestone Year, or End Year and to specify when an Asset is commissioned or decommissioned.
Developer Guide
This is a guide for those who wish to contribute to the MUSE2 project or make local changes to the code.
The API documentation is available here.
Setting up your development environment
To develop MUSE2 locally you will need the following components:
Optional requirements:
You can either install the necessary developer tools locally on your machine manually (a bare metal installation) or use the provided development container. Bare metal installation should generally be preferred if you plan on doing substantial development, as you should get better performance locally (and therefore shorter compile times), as well as making it easier to interact with your local filesystem. Development containers provide a mechanism for installing all the tools you will need (into a Docker container) and are generally used either via Visual Studio Code running locally or GitHub Codespaces, which run on GitHub-provided virtual machines running remotely.
We provide a justfile for some common developer tasks.
Installing tools
Option 1: Bare metal installation
Installing the Rust toolchain
We recommend that developers use rustup to install the Rust toolchain. Follow the instructions on
the rustup website.
This project defines the required version of the Rust toolchain in rust-toolchain.toml. The
first time you attempt to run cargo (see Building and developing MUSE2), this toolchain should
be downloaded and installed automatically by rustup.
Installing C++ tools for HiGHS
The highs-sys crate requires a C++ compiler and CMake to be installed on your system.
These may be installed already, but if you encounter errors during the build process for highs-sys
(e.g. “Unable to find libclang”), you should follow the instructions in the highs-sys
repository.
Option 2: Developing inside a container
If you wish to use the development container locally with Visual Studio Code, you should first install the Dev Containers extension. Note you will also need Docker to be installed locally. For more information, see the documentation.
You can use GitHub Codespaces to develop directly from your web browser. For more information, please see GitHub’s guide. When you first create your Codespace, you will be asked whether you wish to install the recommended extensions and you should choose “yes”.
Downloading the MUSE2 source code
Unless you are developing in a GitHub Codespace (see above), you will need to download the MUSE2 source code to your local machine before you can develop it. Like many projects, MUSE2 is stored in a Git repository hosted on GitHub. Many IDEs, such as Visual Studio Code, provide an interface to clone Git repositories, but you can also use the Git command-line tool (see installation instructions), like so:
git clone https://github.com/EnergySystemsModellingLab/MUSE2
The source code will now be available in a folder named MUSE2.
Pre-Commit hooks
We use prek to automatically run a number of hooks for this repository when a new Git
commit is made. prek is a Rust rewrite of the pre-commit tool (which you can use instead, if
you prefer).
prek can be installed via cargo:
cargo install --locked prek
You can enable prek for this repository with:
prek install
Thereafter, a series of checks should be run every time you commit with Git. In addition, the pre-commit hooks are also run as part of the CI pipeline.
Note: you may get errors due to the clippy hook failing. In this case, you may be able to
automatically fix them by running cargo clipfix (which we have defined as an alias in
.cargo/config.toml).
Building and developing MUSE2
Background: The Rust programming language
MUSE2 is written using Rust, which is a high-performance, compiled language. If you have used other compiled languages, such as C++, many of the concepts will be familiar. One feature which distinguishes it from other languages like C and C++, however, is that undefined behaviour, such as memory safety bugs, are not possible, provided you keep to the safe subset of the language. This means you can have the performance benefits of using a low-level language like C, with the safety guarantees and much of the convenience of a higher-level language like Python.
There is much high quality documentation available for learning Rust, but it is probably best to start with The Rust Programming Language book, which is freely available online.
Building MUSE2
To build the project, run:
cargo build
Note that if you just want to build-test the project (i.e. check for errors and warnings) without
building an executable, you can use the cargo check command, which is much faster.
To run MUSE2 with the “simple” example, you can run:
cargo run run examples/simple
(Note the two runs. The first is for cargo and the second is passed as an argument to the built
muse2 program.)
Tests can be run with:
cargo test
More information is available in the official cargo book.
Checking test coverage
We use Codecov to check whether pull requests introduce code without tests.
To check coverage locally (i.e. to make sure newly written code has tests), we recommend using cargo-llvm-cov.
It can be installed with:
cargo install cargo-llvm-cov
Once installed, you can use it like so:
cargo llvm-cov --open
Alternatively, you can use Just:
just coverage --open
This will generate a report in HTML format showing which lines are not currently covered by tests and open it in your default browser.
Working with the benchmarking framework
MUSE2 includes a benchmarking framework to measure the performance of various components using the Criterion.rs library. To run all the available benchmarks, use the following command:
cargo bench --features bench
The --features bench flag disables some aspects of the code that are incompatible with the
benchmarking, like singleton objects that can be only instantiated once and throw an error otherwise.
See rust documentation for more information about features.
You can also run specific benchmarks by specifying their names:
cargo bench --features bench --bench <benchmark_name>
For example, to run the examples benchmark, you would use:
cargo bench --features bench --bench examples
If run more than once, the benchmarking framework will compare the results of the current run with the previous run and report the differences. This is useful for checking whether a change has introduced a performance regression.
Criterion.rs automatically generates an HTML report that can be found in the
target/criterion/report directory. You can open it with your web browser to see the results and
navigate through the different benchmarks.
Adding new benchmarks
To add a new benchmark, i.e. a collection of related performance tests, create a new file in the
benches directory with the name of your benchmark. For example, to add a benchmark for the foo
functionality, you would create a file called benches/foo.rs. Then foo will need to be added to
cargo.toml under the [bench] section, like so:
[[bench]]
name = "foo"
harness = false
The harness = false line is necessary because Criterion.rs provides its own test harness, which
is incompatible with the default Rust test harness. Within the new benchmark file, you can then add
your performance tests using the Criterion.rs API. Check benches/examples.rs for an example of how
to do this. More information about writing benchmarks can be found in the Criterion.rs documentation.
Architecture and coding style
This document describes the overall architecture of the MUSE2 project, as well as the coding style used for Rust. This document is intended to help new contributors get started with the codebase more quickly rather than to be a set of prescriptions.
User interface and deployment
MUSE2 is a command-line utility, designed to be built and run as a single, standalone executable
file (called muse2 on Unix platforms and muse2.exe on Windows). The normal way in which users
will obtain MUSE2 is by downloading a pre-built binary for their platform (currently, Linux, Windows
and macOS ARM binaries are provided). The user should not need to install any additional
dependencies in order to use MUSE2. All assets (e.g. example models; see below) should be bundled
into this executable file.
For information on the command-line interface, see the documentation. The clap crate is
used to provide this interface.
Using external crates
With Rust it is easy to add external dependencies (called “crates”) from crates.io. It is preferable to make use of external crates for additional required functionality rather than reimplementing this by hand.
Some crates which we make heavy use of are:
anyhow- Ergonomic error handling in Rust (see below)float-cmp- For approximate comparison of floating-point typesindexmap- Provides hash table and set types which preserve insertion orderitertools- Provides extra features for iterator types (consider using to simplify code using iterators)
Error handling
One of the distinctive features of the Rust programming language is its approach to error handling.
There are two usual ways to signal that an error has occurred: either by returning it from a
function like any other value (usually via the Result enum) or by “panicking”, which usually
results in termination of the program1. See the official docs for more information.
In the case of MUSE2, we use the anyhow crate for error handling, which provides some useful
helpers for passing error messages up the call stack and attaching additional context. For any
user-facing error (e.g. caused by a malformed input file), you should return an error wrapped in a
Result, so that it can be logged. For purely developer-facing errors, such as functions called
with bad arguments, you should instead panic!. Note that users should NEVER be able to
trigger a panic in MUSE2 and any case where this happens should be treated as a bug. We use the
human-panic crate to direct users to report the bug if a panic occurs in a release build.
Logging
MUSE2 makes use of the log crate, which provides a number of macros for logging at different
levels (e.g. info!, warn! etc.). This crate just provides the helper macros and does not provide
a logging backend. For the backend, we use fern, which deals with formatting and (in the case of
simulation runs) writing to log files. A few simple commands print to the console directly without
using the logging framework (e.g. muse2 example list), but for code run as part of model
validation and simulation runs, you should use the log macros rather than printing to the console
directly. Note that you should generally not use the error! macro directly, but should instead
pass these errors up the call stack via anyhow (see above).
Note that the log level is configurable at runtime; see user guide for details.
Writing tests
This repository includes tests for many aspects of MUSE2’s functionality (both unit tests and
integration tests). These can be run with cargo test. All tests must pass for submitted code; this
is enforced via a GitHub Actions workflow. Newly added code should include tests,
wherever feasible. Code coverage is tracked with Codecov. There is good documentation on how to
write tests in Rust in the Rust book.
You may wish to use test fixtures for your unit tests. While Rust’s built-in testing framework
does not support test fixtures directly, the rstest crate, which is already included as a
dependency for MUSE2, provides this functionality. You should prefer adding test fixtures over
copy-pasting the same data structures between different tests. For common data structures (e.g.
commodities, assets etc.), there are fixtures for these already provided in fixture.rs. You
should use these where possible rather than creating new fixtures.
As the fixtures needed for many tests are potentially complicated, there are also helper macros for
testing that validation/running fails/succeeds for modified versions of example models. For more
information, see fixture.rs. As this method is likely to lead to terser code compared to using
fixtures, it should be preferred for new tests.
We check whether each of the bundled example models (see below) runs successfully to completion as regression tests. We also check that the output has not substantially changed (i.e. that the numbers in the outputs are within a tolerance), which helps catch accidental changes to the behaviour of MUSE2. Of course, often we do want to change the behaviour of MUSE2 as the model evolves. In this case, you can regenerate test data by running:
just regenerate_test_data
If you do so, please verify that the changes to the output files are at least roughly what was expected, before you commit these updated test files.
Note that if you only need to regenerate the data for some of the models, you can specify this with additional arguments, e.g.:
just regenerate_test_data simple muse1_default
This avoids regenerating data for other models unnecessarily, which can result in negligible differences in floating-point values in the output files.
If the model is a patched example, then you need to pass the --patch flag, e.g.:
just regenerate_test_data --patch simple_tranche_size
Example models
MUSE2 provides a number of example models, to showcase its functionality and help users get started
with creating their own. These models live in the examples folder of the repository and are also
bundled with the MUSE2 executable (see user guide for more detail).
As these are intended as both a kind of documentation and templates, they should ideally be kept as simple as possible.
If you add a new example model, please also add a regression test to tests/regression.rs.
Unit types
We define a number of types for units used commonly in MUSE2, such as activity, capacity etc. These
are simple wrappers around f64s, but provide additional type safety, ensuring that the wrong types
are not passed to functions, for example. Certain arithmetic operations involving types are also
defined: for example, if you divide a variable of type Money by one of type Activity, you
get a result of type MoneyPerActivity.
These types should be used in preference to plain f64s, where possible. For variables which are
unitless, there is a Dimensionless type to make this explicit.
For more information, consult the documentation for the units module.
Input and output files
Input and output files for MUSE2 are either in CSV or TOML format. Users provide model
definitions via a number of input files and the simulation results are written to files in an output
folder. The code responsible for reading and validating input files and writing output files is in
the input and output modules, respectively.
The file formats for MUSE2 input and output files are described in the documentation. This documentation is generated from schema files (JSON schemas for TOML files and table schemas for CSV files); these schemas MUST be updated when the file format changes (i.e. when a field is added/removed/changed). (For details of how to generate this documentation locally, see Developing the documentation.)
When it comes to reading input files, we try to perform as much validation as possible within the
input layer, so that we can provide users with detailed error messages, rather than waiting until
errors in the input data become apparent in the simulation run (or, worse, are missed altogether!).
A certain amount of type safety is given by the serde crate (e.g. checking that fields which
should be integers are really integers), but we also carry out many other validation checks (e.g.
checking that there is a producer for every required commodity in the first year).
-
Technically, panics can be caught, but this is unusual and we don’t do it in MUSE2 ↩
Applying custom HiGHS options
As part of development, you may wish to directly set custom options for the HiGHS solver. Note that
while some of these options will not affect results of simulations (e.g. to enable console logging
for HiGHS), as we cannot guarantee this for all options, in order to use this feature, you have to
set please_give_me_broken_results = true in your model.toml file.
You can change any of the options exposed by the HiGHS solver; for more information, see the HiGHS documentation.
You can set options to be applied to all optimisations, just dispatch or just appraisal.
Here is an example:
please_give_me_broken_results = true
milestone_years = [2020, 2030, 2040]
# These options are applied to all optimisations
[highs.global_options]
# These two options are required to be enabled to log to console
log_to_console = true
output_flag = true
# These ones are just applied to dispatch
[highs.dispatch_options]
# Increase to higher than default
primal_feasibility_tolerance = 10e-6
# These ones are just applied to appraisal
[highs.appraisal_options]
optimality_tolerance = 10e-6
Developing the documentation
You can make changes to the documentation without installing any additional tools, however, you may wish to do so in order to view your changes locally.
For developing the main documentation, you will need mdBook installed (see below) and for the file format documentation, you will need uv. We also recommend that you install Just (the instructions below assume it is present).
If you have all the necessary tools installed and wish to build all of the documentation, you can run:
just build-docs
However, you will likely want to build only part of the documentation, which you can do by passing another argument to this command, e.g.:
just build-docs file-format
To see the possible recipes, run:
just --list build-docs
The book
We use mdBook for generating technical documentation.
If you are developing the documentation locally, you may want to check that your changes render correctly (especially if you are working with equations).
To do this, you first need to install mdBook:
cargo install mdbook
You can then view the documentation in your browser like so:
mdbook serve -o
Documenting file formats
Documentation for file formats of different input and output files used by MUSE2 is automatically
generated from schemas, stored in the
schemas/ folder.
Files are either in TOML or CSV format. For TOML files, we use JSON schemas and for CSV files we use table schemas (a similar format).
The documentation is generated with the
docs/file_formats/generate_docs.py
script. To generate all file format docs, run:
just build-docs file-format
To generate just one kind of docs (e.g. for input files only), run:
just build-docs file-format input
Generating the command_line_help.md file
This file is created automatically. In order to examine the output locally, run:
just build-docs cli-help
The file will be written to docs/command_line_help.md.
Making a release
This page describes the steps for making a new MUSE2 release.
Before you start
Before preparing the release, manually run the Test with MUSE2 workflow on the main branch of
the muse2_data_analysis repository and check that all jobs pass. This workflow tests the analysis
repository’s main branch against MUSE2’s main branch. If it does not pass, identify and fix
the errors there before proceeding with the release.
Choose a version number
MUSE2 version numbers have the form 2.X.Y. The first digit is fixed at 2 to distinguish MUSE2
releases from the Python-based MUSE_OS. The second digit represents major changes, while the third
digit represents minor changes. This is a looser versioning scheme than Semantic Versioning, where
“major” and “minor” have stricter definitions.
Finalise the release notes
All releases should have comprehensive release notes explaining the main user-facing changes since the last release.
Developers are encouraged to add a note to docs/release_notes/upcoming.md for each user-facing
change as it is made. This file should therefore provide a good starting point, but it should not
be assumed to be complete or up to date. Review the full PR history since the last release and make
sure that all user-facing changes introduced since then are included. Do not include changes that
are only relevant to developers, or fixes for bugs that were introduced after the previous release.
Structure the notes in a user-friendly way, highlighting the main user-facing changes at the top and
listing minor changes below. The release notes for v2.1.0 provide a useful example for a major
release.
Once the release notes in docs/release_notes/upcoming.md are finalised:
-
Copy
docs/release_notes/upcoming.mdtodocs/release_notes/v2.X.Y.md. -
Update the heading in the new file to include the version and the date on which the release will be published, for example:
# Release notes for MUSE2 v2.3.4 (January 3, 2027) -
Link the new release notes in
docs/SUMMARY.mdanddocs/release_notes/README.md -
Remove the release-note content from
docs/release_notes/upcoming.md, while retaining the headings so it is ready to track changes for the next release.
Update version metadata
Update all release metadata together:
- Set the package version in
Cargo.toml. - Set
versionanddate-releasedinCITATION.cff.
Publish the release
Once the preparation changes have been merged to main and CI has passed, create and publish a
GitHub Release with the exact v2.X.Y tag, such as v2.3.4.
The published release triggers the workflows that build and attach binaries, publish the crate to
crates.io, and deploy the documentation. Tags must use the
v2.X.Y format so that the documentation tooling recognises them as MUSE2 releases.
After publishing, manually check the GitHub releases page, the MUSE2 crates.io page, and the published documentation to confirm that the release is available in each location.
Create a release for the analysis repository
After publishing the MUSE2 release, create and publish a release in the
muse2_data_analysis repository with the same name as the MUSE2 release, such as v2.3.4.
The analysis repository derives its package version from the Git tag, so this tag must exactly
match the MUSE2 release tag.
API documentation
This is a placeholder file for the MUSE2 API documentation. This content will be replaced with the
output of rustdoc when run by GitHub Actions.
To view the latest version of the API documentation online, see here.
Release notes
This is the list of changes for each release of MUSE2.
To download the latest release for your platform, please see our GitHub releases page.
To see the list of changes for the current, in-progress version of MUSE2, see here.
Release notes for MUSE2 v2.0.0 (October 14, 2025)
After nearly 18 months of development and 464 PRs, we are pleased to announce the first release of the all-new MUSE2 🎉! A big thank you to everyone who contributed to this amazing milestone.
MUSE2 is a tool written in Rust for running simulations of energy systems, which is planned to be the eventual successor to the original MUSE. Note that this is a completely new project written from scratch, with both the code and the model underlying it redesigned from the ground up to be simpler and faster. By design, we do not wish to add every feature that MUSE1 has, but we hope to eventually support all the common use cases for those who run these types of models.
As there are some important features still missing from MUSE2 (e.g. #794, #509), we do not recommend that you attempt to use it for research at this stage. However, please do give it a spin! If there are any features missing that you feel are important, let us know by opening an issue.
It is rather unusual to have the first release of a piece of software be 2.0.0. The reason for this is to avoid confusion with the previous MUSE program. Be aware that this project does not follow semantic versioning.
To get started, you can download MUSE2 for your platform below. For more information, please consult the documentation.
Release notes for MUSE2 v2.1.0 (March 31, 2026)
This release represents over 5 months of development since v2.0.0, adding a number of exciting new features, as well as some small improvements, bug fixes, and underlying code changes designed to improve performance and maintainability.
Please be aware that there are some breaking changes in this release, which may require you to update the input files of any existing models. We have tried to outline these in the “Breaking changes” section below, but please do get in touch if you have any issues adapting models to the new version.
Some of the new features in this release are still experimental, and will continue to be worked on for future releases. In the meantime, any feedback on these features is very welcome.
As always, you can keep track of upcoming changes for the next release in the upcoming release notes.
New features
Asset mothballing
Assets can now remain unused for a certain number of years before being decommissioned, via the
global mothball_years parameter defined in model.toml (#1022). The default value for this
parameter is 0 years, meaning that assets are decommissioned immediately when they are no longer
used, which is equivalent to the behaviour in previous versions of MUSE2.
Process investment constraints
Users can now add yearly limits on the maximum amount of investment in each process via the new
process_investment_constraints.csv file (#1020 and #1096).
Circular commodity dependencies
Users can now have circular dependencies between commodities, such as a hydrogen power plant that
itself requires electricity (#999 and #1004). This is currently an experimental feature that may
not work in all cases - feedback is appreciated. There are also known limitations such as
incompatibility with the new commodity pricing strategies (see below, #1168), which we plan to
address in future releases. The main new parameter to be aware of is the capacity_margin parameter
defined in model.toml.
Divisible assets
Assets can now be made divisible to represent many individual sub-assets, such as a fleet of gas
boilers, which can be partially decommissioned (#1030). This is implemented via the new optional
unit_size column in processes.csv. For example, a unit_size of 10 means that the smallest
granular unit of asset capacity represents 10 capacity units (e.g. a single gas boiler, or a defined
aggregate of gas boilers, depending on the desired resolution). Omitting this column or leaving
values empty means that assets are indivisible (i.e. must be decommissioned in one go), which is the
default behaviour and the same as in previous versions of MUSE2.
It is worth noting that the current implementation incurs a potentially significant performance cost
when setting unit_size very small compared to overall capacity, although we plan to address this
in future releases.
New commodity pricing strategies
There are four new pricing strategies for commodities: full, marginal, full_average and
marginal_average (#1021 and #1205). Unlike previously, where the pricing strategy was set at
the model level, users can now specify the pricing strategy for each commodity individually in the
commodities.csv file via the new optional pricing_strategy column. The new strategies
are in addition to the existing shadow and scarcity strategies, as well as an unpriced option
for oth type commodities that should not be priced.
Other new features
- Users can now optionally specify an explicit decommission year for an asset in
assets.csv(#966) - Allow users to specify ranges of years in input files (#1017)
- You can now set both upper and lower bounds in
process_availabilities.csv(#1018) - Availability limits can now be provided at multiple levels for a process (#1018)
- Allow for adding both a
prodandconslevy to a commodity incommodity_levies.csv(#969) - The default output root path is now configurable in
settings.toml(#1003) - If demand cannot be satisfied by the simulation, we now inform users what the offending commodities and time slices are (#767)
- Trim whitespace from fields when reading in CSV files (#976)
- Default to no availability limits if user doesn’t provide any for a process (#1018)
- Users can now omit empty CSV files (#961)
- Several changes to the NPV agent objective designed to improve results (#994 and #997)
Breaking changes
These changes may require users to update the input files for existing models developed using previous versions of MUSE2.
- The previously-named
yearcolumn inprocess_flows.csv,process_availabilities.csv,process_parameters.csvandprocess_investment_constraints.csvhas been renamed tocommission_yearsto better reflect that these parameters refer to the year in which a process is commissioned as an asset (#964). - The
process_availabilities.csvfile follows new syntax that better allows for specifying availabilities as ranges (#1018). - The
commodities.csvfile has a new compulsoryunitscolumn (#1110). - The default value for the
max_ironing_out_iterationsparameter inmodel.tomlhas been changed from 10 to 1 (i.e. turning the ironing-out loop off by default; #1170). If users wish to use the ironing out loop, they must explicitly set this parameter to a higher value. - Pricing strategy is now set per commodity, so there is now a
pricing_strategycolumn incommodities.csvrather than a single parameter inmodel.toml(#1021).
Bug fixes
- Fix: process availability constraints were wrongly being applied to individual time slices, regardless of time slice level (#1018)
- Various fixes to process flows and availabilities input code for non-milestone years (#868, #1000, #1010)
- Users can now set demand to zero in
demand.csv(#871) - Fix: sign for levies of type
netwas wrong for inputs (#969) - Fix
--overwriteoption forsave-graphscommand (#1001)
Release notes for MUSE2 v2.2.0 (September 3, 2026)
This release is the culmination of a huge effort from many contributors over the past 5 months. Whilst there are only a handful of entirely new features (total capacity limits, commodity constraints), the most significant changes in this release come from improvements and refinements to existing features. Almost every aspect of the main algorithm, from investments to dispatch to mothballing and decommissioning has been modified or fundamentally changed, in many cases to address issues in the model formulation brought to light through rigorous testing and assessment of the software in action. In many ways this is not just a software update, but a research update to what is very much a live and evolving project.
Along with changes to the core code, this release also brings significant improvements to the user-facing documentation, bringing this up to date with the current state of the code. In particular, see the Model Description and Glossary.
Please be aware that there are some breaking changes in this release, which may require you to update the input files of any existing models. We have tried to outline these in the “Breaking changes” section below, but please do get in touch if you have any issues adapting models to the new version.
Some of the new features in this release are still experimental, and will continue to be worked on for future releases. In the meantime, any feedback on these features is very welcome.
As always, you can keep track of upcoming changes for the next release in the upcoming release notes.
New features
Investment appraisal
This release fundamentally changes the investment appraisal approaches for both the LCOX and NPV objectives. Appraisal now uses a more consistent approach across objectives: all objectives now appraise a pre-determined capacity (see “tranching” section below), optimise dispatch of that capacity over remaining demand, and calculate an objective-specific metric based on this dispatch (#1319). For LCOX, this replaces the previous approach that used dynamic capacity variables and the Value of Lost Load parameter, which had a tendency to inflate capacities. The NPV formulation has also changed to use SNAS (Specific Net Annualised Surplus) rather than Profitability Index, which is more robust to assets with low/zero fixed costs (#1379). More details can be found in the updated investment documentation.
A further change is to introduce a fallback cost to the activity coefficient calculation, set by the
fallback_pricing_strategy parameter in model.toml (#1420). This reframes the optimisation
problem to consider the alternative cost of choosing not to invest in a process, which encourages
investment in cases where there would otherwise be no incentive to do so. This is also described
in more detail in the investment documentation.
Users may notice changes in asset selection, better matching of capacities to demands, and fewer model failures triggered by an absence of viable investment options.
In addition, investment appraisal is now parallelised over candidate assets, which may improve performance, particularly for large models run on multi-core machines (#1466).
Divisible assets and tranching
v2.1.0 introduced the concept of asset groups as a proxy for modelling asset divisibility, i.e.
the idea that overall asset capacity may be made up of multiple discrete components, or tranches,
(e.g. a group of wind turbines in a wind farm) which can be mothballed and decommissioned
independently. This approach resulted in a (potentially large) number of Assets (with distinct
asset_ids) linked together by a common group_id.
v2.2.0 does away with asset groups, and instead expresses the capacity of each individual Asset as
a collection of tranches, which work in unison in dispatch, but can be independently
mothballed and decommissioned (#1464, #1483).
Tranches are not reported individually in the output results. Rather, results files report
aggregate flows and capacities for the overall Asset, represented by a single asset_id
(#1455). Since capacities for each Asset can now change over time, capacities are moved
from the fixed asset definition in the assets.csv output file over to a new
asset_capacities.csv file, which reports capacity for each asset in each milestone year (#1302).
The size of these tranches can be set on a per-process basis, either to a fixed, predefined capacity, or determined dynamically at the time of investment based on demand. Investment appraisal (see above) works on one tranche at a time. There are several new/renamed parameters to be aware of (#1518):
tranche_size: Replacesunit_sizeinprocesses.csv. An optional parameter to set a fixed tranche size for a process.capacity_tranche_fraction: A factor, defined inmodel.toml, used to infer tranche sizes at the time of investment for assets without a definedtranche_size(#1391, #1395). See investment documentation for more details.num_tranches: The number of equal-capacity tranches making up an overall asset capacity. Reported with overall capacities inasset_capacities.csv, and can be used to split existing assets into tranches in theassets.csvinput file (#1480).
Since all assets can now be divided into tranches and partially decommissioned (not just those
with a defined tranche_size), this should allow capacity to be matched more closely to demand and
stale capacity to be removed more readily.
Total capacity limits
Users can now add hard upper limits on the total amount of capacity that can exist for a certain
process at any one time, via the total_capacity_limit parameter in
process_investment_constraints.csv (#1417, #1496). See investment documentation for more
details.
Please note that these limits may be broken in models with circular commodity dependencies - this is a known limitation (#1498), as circularities remain an experimental feature.
Commodity constraints (experimental)
Users can now add constraints on the total production and/or consumption of SED and OTH commodities
in a given region in a given year/season/time slice, via the commodity_constraints.csv file
(#1487, #1513). See the dispatch documentation for more details.
Please note that this feature is currently experimental, and expected to cause conflicts in
otherwise working models, as these constraints are currently only considered in full-system
dispatch, and not factored in to the investment algorithm. Future releases will hopefully address
this, but for now, in order to use this feature, you must enable the please_give_me_broken_results
option in model.toml.
Dispatch optimisation
Additional constraints and small objective penalties have been added to the dispatch optimisation problem to push solutions towards a more intentional and consistent solution. The penalties are deliberately kept small so that they primarily distinguish between otherwise equivalent solutions while having negligible influence on the primary cost objective. Two such additions, “Equal Utilisation of Equivalent Assets” (#1477) and “Seasonal/Annual Utilisation Penalties” (#1478), are explained in more detail in the dispatch documentation.
As a result of these changes, users should notice more sensible and interpretable asset utilisation patterns, away from arbitrary concentration of activity on certain assets and time slices, now tending to spread utilisation evenly across equally good assets and time slices. In many cases, this will also have knock-on consequences for commodity prices. Additionally, whereas results were previously sensitive to differences between platforms and operating systems, as small floating point differences could push the solver to a different convergence point, these additions constrain the optimisation problem in a way that should substantially reduce the sensitivity of results to these platform-dependent differences. That said, identical results cannot yet be guaranteed across all platforms in all cases - this is an area of ongoing investigation.
Other new features
- Users can now optionally pass custom options to the HiGHS solver (#1276)
- Input files are now copied to the output folder for reference (#1282, #1303)
- Circular commodity markets can now use cost-based pricing strategies (#1322)
- Added a configurable
commodity_balance_epsilonparameter inmodel.tomlto help candidate assets dispatch during investment appraisal (#1387, #1445) - The execution time of key simulation steps (e.g. dispatch, agent investment) is now logged (#1421)
Breaking changes
These changes may require users to update the input files for existing models developed using previous versions of MUSE2.
- Changed the default
pricing_strategyfor SED/SVD commodities from “shadow” to “full_average” (#1281) - The
group_idcolumn has been removed from all output files (#1286, #1302, #1455) - The
agent_search_space.csvinput file has been renamed toagent_search_spaces.csvfor consistency (#1293) - Due to an internal change, some options in CSV files are now case sensitive (e.g. you must put
lcoxrather thanLCOX). The error message you see if it does not match will now be more informative, however. (#1376) - Stricter input validation:
capacity_to_activitymust now be greater than zero. Previously,capacity_to_activity = 0was permitted (#1391) - The
capacity_limit_factorparameter inmodel.tomlhas been removed. Please usecapacity_tranche_fraction(see above) instead (#1518) - The
unit_sizeparameter inprocesses.csvhas been renamed totranche_size(#1518)
Bug fixes
- Fix misleading warning message for assets decommissioned before simulation start (#1259)
- Fix parsing and validation of agent search space file (#1293)
- Fix a bug in asset selection for circular commodity markets when agents in the same cycle have different commodity shares (#1323)
- Prevent commodities of type
OTHfrom being specified inagent_commodity_portions.csv(#1327) - Use shadow prices rather than market prices for appraisal optimisations and dispatch runs during investment (#1349)
- Asset selection can now continue even if it runs out of options, giving dispatch a chance to still meet demand, rather than failing immediately (#1365)
- Calculate commodity prices using the dispatch solution without candidate assets, so that they are
consistent with the flows reported in
commodity_flows.csv(#1389) - Fix price calculations to prioritise active assets over inactive ones when determining commodity prices (#1392)
- The
debug_unmet_demand.csvoutput file is no longer created unless it is needed (#1405) - Mothballed assets are now excluded from the main dispatch run (#1458)
- Disallow negative values for
capital_cost,fixed_operating_costandvariable_operating_costinprocess_parameters.csv(#1485)