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:
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.
Running this model
First, 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.1.0
[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 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.
For information about the available settings, see the documentation for the settings.toml
file.
Setting the log level
MUSE 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, MUSE 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.
For now, this uses the “shadow” pricing strategy for ELCTRI and H2YPRD, as the default
“full_average” strategy is not supported for commodities involved in circularities.
This also uses a slightly wider capacity_margin compared to the default value, as this is
required for this model to find a solution.
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.1.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 must be in the current working directory.
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 |
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_limit_factor | Scales the capacity assigned to candidate assets | Optional. Defaults to 0.05.Candidate assets for investment are assigned a characteristic capacity scale based on annual demand. This parameter specifies the fraction of that scale appraised each investment round. Must be >0 and <=1. Lower values produce smaller investment increments, while higher values produce larger increments. |
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. |
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_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.
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.
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 |
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 slices(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 slices(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). |
unit_size | Capacity of the units in which an asset for this process will be divided into when commissioned, if any. | If present, must be >0. Optional (defaults to None). Assets with a defined unit size are divided into n = ceil(C / U) equal units, where C is overall capacity and U is unit_size (i.e. rounding up the number of units, which may result in a total capacity greater than C, if C is not an exact multiple of U). Note that divisible assets are appraised one unit at a time, so if this number is much smaller than the typical size of an asset, this will potentially lead to many rounds of appraisal, with an adverse effect on performance. |
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_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_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 slices(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).
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 evenly between all agents using their proportion 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 | The hard upper limit on the amount agents can invest in the process | This is currently unused. |
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 | For divisible assets, this is the total capacity across all units. |
num_units | The number of active units making up the asset in this year | For non-divisible assets, this is always blank. |
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.
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 framework is designed to operate sequentially across several distinct milestone years (MSY). For each MSY, it endogenously determines asset decommissioning (both scheduled and economically-driven) and guides new capacity investments. The overarching objective is to simulate agent decision-making to serve commodity (and service) demand.
A fundamental premise for the investment appraisal is that prices for balanced commodities (Supply Equals Demand: SED) from the previous milestone year (\( \pi_{prevMSY} \)) are considered reliable for economic evaluations. Service demand commodity (SVD) prices from \( MSY_{prev} \) may or may not be reliable (as defined by the user), guiding the choice of appraisal method for assets producing them. It is designed as a recursive dynamic model with imperfect foresight.
The workflow is structured as follows:
-
Dispatch is executed for a calibrated base year. Dispatch is executed using the formulation shown in Dispatch Optimisation Formulation. All existing assets are included, and all candidate assets for the \( MSY_{next} \) are included with capacities set to zero. This ensures that all commodity shadow prices are generated for use in the \( MSY_{next} \). \( VoLL \) load shedding variables should be zero after completion, and if they are non-zero then throw an error as the model is not properly calibrated.
-
Time-travel loop: Move to the next milestone year (\( MSY \)).
-
Decommission assets that reached their end of life. This establishes the existing asset fleet for the current \( MSY \) by accounting for initial retirements. \( ExistingCapacity \) is the set of existing assets and their capacities available after EOL decommissioning.
-
Determine SVD demand profiles and run investment appraisal tools for them. SVD demand profiles for the \( MSY \) are determined from user input data. The investment appraisal tools are applied to determine portfolios of existing and new assets to meet demand for each SVD commodity. Prices of input commodities are known from the final dispatch of the previous milestone year. This finalises \( NewCapacity \) and \( AssetChosen \) for each SVD.
-
Build System Layer-by-Layer loop: Completes the investment pass for the milestone year, progressively adding commodities layer by layer. This step determines new asset capacities (\( NewCapacity \)) and selects existing assets that remain competitive (\( AssetChosen \)). Other existing assets are decommissioned if they are not utilised for \( MothballYears \) years (a user-input asset parameter). This inner loop continues until no different SED commodities (that have not been processed using the investment appraisal tools) are added as commodities of interest for this iteration.
-
Determine the commodities of interest for investment. In the base year this is all service demand (SVD) commodities. After the base year, the commodities of interest are determined dynamically; they are the set of commodities that are consumed by the assets invested/chosen in the last iteration (layer) of this loop.
-
Dispatch to determine commodity of interest demand profile. Dispatch is executed using the formulation shown in Dispatch Optimisation Formulation, but only including system elements downstream of the commodities of interest. Commodity prices for upstream/unknown inputs/outputs from assets serving the commodities of interest and assets downstream of the commodities of interest with unknown commodity prices (if any) are assumed to take on shadow prices from the previous MSY. Care must be taken to avoid any double-counting of prices and e.g. commodity levies. Demand profiles for commodities of interest are recorded (\( D[c,r,t] \)).
-
Run investment appraisal tools for each commodity of interest. The investment appraisal tools are applied to determine portfolios of existing and new assets to meet demand for each commodity of interest. It is necessary to consider the complete demand profile of each commodity of interest, as even where demand can be served with existing assets in the MSY without new investment, economic decommissioning is still possible.
-
Finalise new capacity and retained assets that produce the commodities of interest. \( NewCapacity \) and \( AssetChosen \) are finalised for the layer.
-
Check if there are SED commodities that the investment appraisal tools have not been run for. If yes, move to next layer of the layering loop at step 2(c). If no, layering loop ends, break and continue at step 2(d).
-
-
Ironing-out loop, with iteration limit \( k_{max} \). For each \( k \):
-
Execute dispatch as per Dispatch Optimisation Formulation with the complete system, with all candidate assets for the \( MSY_{next} \) included with capacities set to zero to generate prices for the \( MSY_{next} \).
-
Check if load-weighted average prices for any SED commodity has changed (or changed more than a tolerance) since the last loop (also, possibly check if the 95th percentile of price has changed more than a tolerance). If yes, continue at 2(d)iii. If no, this MSY is complete, and if further MSY exist continue time-travel loop from step 2, or if no further MSY exist then go to step 3.
-
If \( k = k_{max} \) break with a warning telling the user that this loop did not converge, identifying out-of-balance commodities. If further MSY exist continue time-travel loop from step 2, or if no further MSY exist then go to step 3.
-
Re-run investment appraisal tools for the assets and commodities that are contributing to the price instability.
-
-
-
Outer loop ends when no further milestone years exist.
Dispatch Optimisation Formulation
This dispatch optimisation model calculates the least-cost operation of the energy system for a given configuration of assets and capacities, subject to demands and constraints. It is the core engine used for each dispatch run referenced in the overall MUSE2 workflow. A key general assumption is that SVD commodities represent final demands only and are not consumed as inputs by any asset.
General Sets
These define the fundamental categories used to define the energy system.
-
\( \mathbf{R} \): Set of Regions (indexed by \( r \)). Represents distinct geographical or modelling areas.
-
\( \mathbf{T} \): Set of Time Slices (indexed by \( t \)). Discrete operational periods within a year.
-
\( \mathbf{H} \): Set of Seasons (indexed by \( h \)). Collections of time slices.
-
\( \mathbf{A} \): Set of All Assets (indexed by \( a \)). All existing and candidate production, consumption, or conversion technologies.
-
\( \mathbf{A}^{flex} \subseteq \mathbf{A} \): Subset of Flexible Assets (variable input/output ratios).
-
\( \mathbf{A}^{std} = \mathbf{A} \setminus \mathbf{A}^{flex} \): Subset of Standard Assets (fixed input/output coefficients).
-
\( \mathbf{C} \): Set of Commodities (indexed by \( c \)). All energy carriers, materials, or tracked flows. Partitioned into:
-
\( \mathbf{C}^{\mathrm{SVD}} \): Supply-Driven Commodities (final demands; not consumed by assets).
-
\( \mathbf{C}^{\mathrm{SED}} \): Supply-Equals-Demand Commodities (intermediate system flows like grid electricity).
-
\( \mathbf{C}^{\mathrm{OTH}} \): Other Tracked Flows (e.g., losses, raw emissions).
-
-
\( \mathbf{C}^{VoLL} \subseteq \mathbf{C}^{\mathrm{SVD}} \cup \mathbf{C}^{\mathrm{SED}} \): Subset of commodities where unserved demand is modelled with a penalty.
-
\( \mathbf{P} \): Set of External Pools/Markets (indexed by \( p \)).
-
\( \mathbf{S} \): Set of Scopes (indexed by \( s \)). Sets of \( (r,t) \) pairs for policy application.
A. Core Model (Standard Assets: \( a \in \mathbf{A}^{std} \))
Purpose: Defines the operation of assets with fixed, predefined input-output relationships.
A.1. Parameters (for \( a \in \mathbf{A}^{std} \) or global)
-
\( duration[t] \): Duration of time slice \( t \) as a fraction of the year (\( \in (0,1] \)). Represents the portion of the year covered by this slice.
-
\( season\_ slice[h,t] \): Binary indicator; \( 1 \) if time slice \( t \) is in season \( h \), \( 0 \) otherwise. Facilitates seasonal aggregation.
-
\( balance\_ level[c,r] \): Defines the temporal resolution (‘timeslice’, ‘seasonal’, ‘annual’) at which the supply-demand balance for commodity \( c \) in region \( r \) must be enforced.
-
\( demand[r,c] \): Total annual exogenously specified demand (\( \ge 0 \)) for commodity \( c \in \mathbf{C}^{\mathrm{SVD}} \) in region \( r \). This is the final demand to be met.
-
\( timeslice\_ share[c,t] \): Fraction (\( \in [0,1] \)) of the annual \( demand[r,c] \) for \( c \in \mathbf{C}^{\mathrm{SVD}} \) that occurs during time slice \( t \). (\( \sum_{t}timeslice\_ share[c,t]=1 \)). Defines the demand profile.
-
\( capacity[a,r] \): Installed operational capacity (\( \ge 0 \)) of asset \( a \) in region \( r \) (e.g., MW for power plants). This value is an input to each dispatch run.
-
\( cap2act[a] \): Conversion factor (\( >0 \)) from asset capacity units to activity units, ensuring consistency between capacity (e.g., MW) and activity (e.g., MWh produced in a slice) considering \( duration[t] \).
-
\( avail_{UB}[a,r,t], avail_{LB}[a,r,t], avail_{EQ}[a,r,t] \): Availability factors (\( \in [0,1] \)) for asset \( a \) in time slice \( t \). \( UB \) is maximum availability, \( LB \) is minimum operational level, \( EQ \) specifies exact operation if required.
-
\( cost_{var}[a,r,t] \): Variable operating cost (\( \ge 0 \)) per unit of activity for asset \( a \) (e.g., non-fuel O&M).
-
\( input_{coeff}[a,c] \): Units (\( \ge 0 \)) of commodity \( c \in (\mathbf{C}^{\mathrm{SED}} \cup \mathbf{C}^{\mathrm{OTH}}) \) consumed by asset \( a \) per unit of its activity. (By assumption, \( input_{coeff}[a,c]=0 \) if \( c \in \mathbf{C}^{\mathrm{SVD}} \)).
-
\( output_{coeff}[a,c] \): Units (\( \ge 0 \)) of commodity \( c \in \mathbf{C} \) produced by asset \( a \) per unit of its activity.
-
\( cost_{input}[a,c] \): Specific cost (\( \ge 0 \)) per unit of input commodity \( c \) consumed by asset \( a \). Useful if \( c \) attracts a levy/incentive \( only \) if it is consumed by this type of asset.
-
\( cost_{output}[a,c] \): Specific cost (if positive) or revenue (if negative) per unit of output commodity \( c \) produced by asset \( a \). Useful if levy/incentive applies \( only \) when the commodity is produced by this type of asset.
-
\( VoLL[c,r] \): Value of Lost Load. A very high penalty cost applied per unit of unserved demand for \( c \in \mathbf{C}^{VoLL} \) in region \( r \).
A.2. Decision Variables
These are the quantities the dispatch optimisation model determines.
-
\( act[a,r,t]\ge0 \): Activity level of asset \( a \) in region \( r \) during time slice \( t \). This is the primary operational decision for each asset.
-
\( UnmetD[c,r,t]\ge0 \): Unserved demand for commodity \( c \in \mathbf{C}^{VoLL} \) in region \( r \) during time slice \( t \). This variable allows the model to find a solution even if capacity is insufficient.
A.3. Objective Contribution (for standard assets \( a \in \mathbf{A}^{std} \))
This term represents the sum of operational costs associated with standard assets, forming a component of the overall system cost that the model seeks to minimise.
\[ \sum_{a\in \mathbf{A}^{std}}\sum_{r,t} act[a,r,t] \Biggl( cost_{var}[a,r,t] + \sum_{c \notin \mathbf{C}^{\mathrm{SVD}}} cost_{input}[a,c]\,input_{coeff}[a,c] + \sum_{c \in \mathbf{C}} cost_{output}[a,c]\,output_{coeff}[a,c] \Biggr) \]
A.4. Constraints (Capacity & Availability for standard assets \( a \in \mathbf{A}^{std} \))
These constraints ensure that each standard asset’s operation respects its physical capacity and time-varying availability limits. For all \( a \in \mathbf{A}^{std}, r, t \):
-
Asset activity \( act[a,r,t] \) is constrained by its available capacity, considering its minimum operational level (lower bound, LB) and maximum availability (upper bound, UB):
\[ \begin{aligned} capacity[a,r]\,cap2act[a]\,avail_{LB}[a,t]\,duration[t] &\le act[a,r,t] \\ act[a,r,t] &\le capacity[a,r]\,cap2act[a]\,avail_{UB}[a,t]\,duration[t] \end{aligned} \]
-
If an exact operational level is mandated (e.g., for some renewables based on forecast, or fixed generation profiles for specific assets):
\[ act[a,r,t] = capacity[a,r]\,cap2act[a]\,avail_{EQ}[a,t]\,duration[t] \]
B. Flexible Assets (\( a \in \mathbf{A}^{flex} \))
Purpose: This section defines the operational characteristics of flexible assets, which can adjust their input consumption mix and/or output product shares, governed by an overall process efficiency. The generic activity variable \( act[a,r,t] \) (linked to the asset’s physical \( capacity[a,r] \)) is connected to the scale of this flexible conversion process via a reference output parameter. It is assumed that SVD commodities cannot be efficiency-constrained or auxiliary inputs to these assets.
B.1. Asset-Specific Sets (Defined for each flexible asset \( a \in \mathbf{A}^{flex} \))
These sets categorize the commodities involved in the flexible asset’s operation.
-
\( \mathbf{C}^{eff\_ in}_a \subseteq (\mathbf{C}^{\mathrm{SED}} \cup \mathbf{C}^{\mathrm{OTH}}) \): Set of input commodities for asset \( a \) whose combined energy or mass content is subject to the main process efficiency \( \eta[a] \).
-
\( \mathbf{C}^{eff\_ out}_a \subseteq \mathbf{C} \): Set of main output commodities from asset \( a \) whose combined energy or mass content is determined by \( \eta[a] \) and the processed inputs.
-
\( \mathbf{C}^{aux\_ in}_a \subseteq (\mathbf{C}^{\mathrm{SED}} \cup \mathbf{C}^{\mathrm{OTH}}) \): Set of auxiliary input commodities for asset \( a \) (e.g., water, catalysts) whose consumption is typically proportional to the main activity \( act[a,r,t] \) but which are not part of the primary energy/mass balance for the efficiency calculation.
-
\( \mathbf{C}^{aux\_ out}_a \subseteq \mathbf{C} \): Set of auxiliary output commodities for asset \( a \) (e.g., specific emissions, waste streams) whose production is typically proportional to \( act[a,r,t] \) but which are not counted in the efficiency calculation for the primary products.
B.2. Parameters (for \( a \in \mathbf{A}^{flex} \))
These parameters define the technical and economic behavior of flexible assets.
-
\( \eta[a] \): The overall process efficiency (\( \in (0,1] \)) of flexible asset \( a \), relating total common units of outputs in \( \mathbf{C}^{eff\_out}_a \) to inputs in \( \mathbf{C}^{eff\_in}_a \).
-
\( factor_{CU}[c] \): A commodity-specific factor to convert its native physical units (e.g., tonnes, m³) to a common unit (e.g., MWh of energy content, or tonnes of mass if it’s a mass balance) used for consistent efficiency and input/output share calculations.
-
\( RefEffOutPerAct[a] \): Reference Total Efficiency-constrained Output per unit of Activity. This crucial parameter defines the total quantity of efficiency-constrained outputs (summed in common units using \( factor_{CU}[c] \)) that are produced when asset \( a \) operates at one unit of its generic activity level \( act[a,r,t] \).
-
\( minInputShare[a,c] \) (for \( c \in \mathbf{C}^{eff\_ in}_a \)): Minimum fractional share of input commodity \( c \) (in common units) relative to the total efficiency-constrained input (in common units).
-
\( maxInputShare[a,c] \) (for \( c \in \mathbf{C}^{eff\_ in}_a \)): Maximum fractional share for input \( c \).
-
\( minOutputShare[a,c] \) (for \( c \in \mathbf{C}^{eff\_ out}_a \)): Minimum fractional share of output commodity \( c \) (in common units) relative to the total efficiency-constrained output.
-
\( maxOutputShare[a,c] \) (for \( c \in \mathbf{C}^{eff\_ out}_a \)): Maximum fractional share for output \( c \).
-
\( coeff_{aux\_ in}[a,c] \) (for \( c \in \mathbf{C}^{aux\_ in}_a \)): Quantity of auxiliary input \( c \) consumed per unit of \( act[a,r,t] \).
-
\( coeff_{aux\_ out}[a,c] \) (for \( c \in \mathbf{C}^{aux\_ out}_a \)): Quantity of auxiliary output \( c \) produced per unit of \( act[a,r,t] \).
B.3. Decision Variables (for \( a \in \mathbf{A}^{flex} \))
These variables represent the operational choices for flexible assets.
-
\( act[a,r,t]\ge0 \): Generic activity level of flexible asset \( a \) (linked to its \( capacity[a,r] \)).
-
\( InputSpec[a,c,r,t]\ge0 \quad \forall c \in \mathbf{C}^{eff\_ in}_a \): Actual amount of efficiency-constrained input commodity \( c \) consumed by asset \( a \) in region \( r \), time \( t \) (in its native physical units).
-
\( OutputSpec[a,c,r,t]\ge0 \quad \forall c \in \mathbf{C}^{eff\_ out}_a \): Actual amount of efficiency-constrained output commodity \( c \) produced by asset \( a \) in region \( r \), time \( t \) (in its native physical units).
B.4. Objective Contribution (for \( a \in \mathbf{A}^{flex} \))
The cost contribution from flexible assets includes costs related to their generic activity, specific costs for efficiency-constrained inputs and outputs (if defined separately from system commodity values), and costs/revenues for auxiliary inputs and outputs.
\[ \begin{aligned} &act[a,r,t] \cdot cost_{var}[a,r,t] \\ &+ \sum_{c \in \mathbf{C}^{eff\_ in}_a} InputSpec[a,c,r,t] \cdot cost_{input}[a,c] \\ &+ \sum_{c \in \mathbf{C}^{eff\_ out}_a} OutputSpec[a,c,r,t] \cdot cost_{output}[a,c] \\ &+ \sum_{c \in \mathbf{C}^{aux\_ in}_a} (act[a,r,t] \cdot coeff_{aux\_ in}[a,c]) \cdot cost_{input}[a,c] \\ &+ \sum_{c \in \mathbf{C}^{aux\_ out}_a} (act[a,r,t] \cdot coeff_{aux\_ out}[a,c]) \cdot cost_{output}[a,c] \\ \end{aligned} \]
B.5. Constraints (for \( a \in \mathbf{A}^{flex}, r \in \mathbf{R}, t \in \mathbf{T} \))
These rules govern the internal operation and conversion process of flexible assets. Let \( ActualTotalEffOutputCU[a,r,t] = RefEffOutPerAct[a] \cdot act[a,r,t] \) (This is the total efficiency-constrained output in common units). Let \( ActualTotalEffInputCU[a,r,t] = (RefEffOutPerAct[a] / \eta[a]) \cdot act[a,r,t] \) (This is the total efficiency-constrained input in common units, derived from the output and efficiency).
-
Capacity & Availability: Standard capacity and availability constraints (as in A.4) apply to the generic activity variable \( act[a,r,t] \) of the flexible asset.
-
Total Efficiency-Constrained Input Definition: The sum of all specific efficiency-constrained inputs, when converted to common units by \( factor_{CU}[c] \), must equal the total efficiency-constrained input derived from \( act[a,r,t] \) and the asset’s efficiency:
\[ \sum_{c \in \mathbf{C}^{eff\_ in}_a} InputSpec[a,c,r,t] \cdot factor_{CU}[c] = ActualTotalEffInputCU[a,r,t] \]
-
Total Efficiency-Constrained Output Definition: Similarly, the sum of all specific main outputs (in common units) must equal the total defined by \( act[a,r,t] \) and the reference output parameter:
\[ \sum_{c \in \mathbf{C}^{eff\_out}_a} OutputSpec[a,c,r,t] \cdot factor_{CU}[c] = ActualTotalEffOutputCU[a,r,t] \]
-
Input Share Constraints (for each \( c \in \mathbf{C}^{eff\_in}_a \)): Ensure that each individual efficiency-constrained input (in common units) stays within its defined minimum (\( minInputShare[a,c] \)) and maximum (\( maxInputShare[a,c] \)) fractional share of the \( ActualTotalEffInputCU[a,r,t] \).
\[ \begin{aligned} InputSpec[a,c,r,t] \cdot factor_{CU}[c] &\ge minInputShare[a,c] \cdot ActualTotalEffInputCU[a,r,t] \\ InputSpec[a,c,r,t] \cdot factor_{CU}[c] &\le maxInputShare[a,c] \cdot ActualTotalEffInputCU[a,r,t] \end{aligned} \]
-
Output Share Constraints (for each \( c \in \mathbf{C}^{eff\_out}_a \)): Ensure that each individual efficiency-constrained output (in common units) stays within its defined minimum (\( minOutputShare[a,c] \)) and maximum (\( maxOutputShare[a,c] \)) fractional share of the \( ActualTotalEffOutputCU[a,r,t] \).
\[ \begin{aligned} OutputSpec[a,c,r,t] \cdot factor_{CU}[c] &\ge minOutputShare[a,c] \cdot ActualTotalEffOutputCU[a,r,t] \\ OutputSpec[a,c,r,t] \cdot factor_{CU}[c] &\le maxOutputShare[a,c] \cdot ActualTotalEffOutputCU[a,r,t] \end{aligned} \]
C. Full Model Construction
Note: This section includes references to many features that are not described elsewhere in this document or implemented yet (e.g. region-to-region trade), but these are included for completeness. This represents the roadmap for future MUSE2 development.
This section describes how all preceding components are integrated to form the complete dispatch optimisation problem.
C.1. Objective Function
The overall objective is to minimise the total system cost, which is the sum of all operational costs from assets (standard and flexible), financial impacts from policy scopes (taxes minus credits), costs of inter-regional trade, costs of pool-based trade, and importantly, the high economic penalties associated with any unserved demand for critical commodities:
\[ \begin{aligned} \text{Minimise: } &(\text{Core Asset Operational Costs from A.3 and B.4}) \\ &+ \sum_{c \in \mathbf{C}^{VoLL},r,t} UnmetD[c,r,t] \cdot VoLL[c,r] \quad \text{(Penalty for Unserved Demand)} \end{aligned} \]
Note that the unmet demand variables (\( UnmetD[c,r,t] \)) are normally not included in the optimisation and are currently only used to diagnose the source of errors when running the model.
C.2. Constraints
The complete set of constraints that the optimisation must satisfy includes:
Demand Satisfaction for \( c\in \mathbf{C}^{\mathrm{SVD}} \)
These constraints ensure that exogenously defined final demands for SVDs are met in each region \( r \) and time slice \( t \), or any shortfall is explicitly accounted for.
For all \( r,t,c \in \mathbf{C}^{\mathrm{SVD}} \): Let \( TotalSystemProduction_{SVD}[c,r,t] \) be the sum of all production of \( c \) from standard assets (\( output_{coeff}[a,c]\,act[a,r,t] \)) and flexible assets (the relevant \( OutputSpec[a,c,r,t] \) if \( c \in \mathbf{C}_a^{eff\_out} \), or \( act[a,r,t] \cdot coeff_{aux\_out}[a,c] \) if \( c \in \mathbf{C}^{aux\_out}_a \)).
Let \( NetImports_{SVD}[c,r,t] \) be net imports of \( c \) from R2R and Pool trade if SVDs are tradeable. If \( c \in \mathbf{C}^{VoLL} \) (meaning unserved demand for this SVD is permitted at a penalty):
\[ TotalSystemProduction_{SVD}[c,r,t] + NetImports_{SVD}[c,r,t] + UnmetD[c,r,t] \ge demand[r,c] \times timeslice\_ share[c,t] \]
Else (if SVD \( c \) must be strictly met and is not included in \( \mathbf{C}^{VoLL} \)):
\[ TotalSystemProduction_{SVD}[c,r,t] + NetImports_{SVD}[c,r,t] \ge demand[r,c] \times timeslice\_ share[c,t] \]
Commodity Balance for \( c\in \mathbf{C}^{\mathrm{SED}} \)
These constraints ensure that for all intermediate SED commodities, total supply equals total demand within each region \( r \) and for each balancing period defined by \( balance\_ level[c,r] \) (e.g., timeslice, seasonal, annual).
For a timeslice balance (\( \forall r,t,c \in \mathbf{C}^{\mathrm{SED}} \)):
Total Inflows (Local Production by all assets + Imports from other regions and pools + Unserved SED if \( c \in \mathbf{C}^{VoLL} \)) = Total Outflows (Local Consumption by all assets + Exports to other regions).
\[ \begin{aligned} &\sum_{a \in \mathbf{A}^{std}} output_{coeff}[a,c]\,act[a,r,t] && \text{(Std Asset Production)} \\ &+ \sum_{a \in \mathbf{A}^{flex}} \left( \begin{cases} OutputSpec[a,c,r,t] & \text{if } c \in \mathbf{C}^{eff\_out}_a \\ act[a,r,t] \cdot coeff_{aux\_out}[a,c] & \text{if } c \in \mathbf{C}^{aux\_out}_a \\ 0 & \text{otherwise} \end{cases} \right) && \text{(Flex Asset Production)} \\ &-\sum_{a \in \mathbf{A}^{std}} input_{coeff}[a,c]\,act[a,r,t] && \text{(Std Asset Consumption)} \\ &- \sum_{a \in \mathbf{A}^{flex}} \left( \begin{cases} InputSpec[a,c,r,t] & \text{if } c \in \mathbf{C}^{eff\_in}_a \\ act[a,r,t] \cdot coeff_{aux\_in}[a,c] & \text{if } c \in \mathbf{C}^{aux\_in}_a \\ 0 & \text{otherwise} \end{cases} \right) && \text{(Flex Asset Consumption)} \\ &+ \sum_{r’\neq r, c \in \mathbf{C}^R} ship_{R2R}[r’,r,c,t](1 - loss_{R2R}[r’,r,c,t]) && \text{(R2R Imports)} \\ &+ \sum_{p, c \in \mathbf{C}^P} ship_{pool}[p,r,c,t](1 - loss_{pool}[p,r,c,t]) && \text{(Pool Imports)} \\ &- \sum_{r’\neq r, c \in \mathbf{C}^R} ship_{R2R}[r,r’,c,t] && \text{(R2R Exports)} \\ &+ \mathbb{I}(c \in \mathbf{C}^{VoLL}) \cdot UnmetD[c,r,t] && \text{(Unserved SED, if modelled)} \\ &\ge 0 \end{aligned} \]
(where \( \mathbb{I}(c \in \mathbf{C}^{VoLL}) \) is an indicator function, \( 1 \) if \( c \) is in \( \mathbf{C}^{VoLL} \), \( 0 \) otherwise. Note that SVDs are not consumed by assets, so \( input_{coeff}[a,c] \) and related terms for SVDs on the consumption side are zero).
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.
After each commodity market is settled, a 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
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_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 (\( \text{FOM} \)): \[ \text{AFC}_\text{existing} = \text{FOM} \]
-
Candidate assets: AFC includes annualised capital expenditure plus fixed O&M: \[ \text{AFC}_\text{candidate} = \text{CAPEX} \times \text{CRF} + \text{FOM} \]
where the Capital Recovery Factor (CRF) annualises the upfront capital cost over the asset’s lifetime \( L \) at discount rate \( d \): \[ \text{CRF} = \frac{d \cdot (1 + d)^L}{(1 + d)^L - 1} \]
If \( d = 0 \), then \( \text{CRF} = 1/L \).
Asset Capacity
A process is either divisible or non-divisible:
- A divisible process has a fixed
unit_size(defined inprocesses.csv). Assets of this type consist of one or more discrete units, each of sizeunit_size. When commissioned, a divisible asset is split into individual units, each of which is appraised and retained or mothballed independently. - A non-divisible process has no
unit_size. Assets of this type are treated as a single entity: capacities may take any value within their allowable range, but cannot be split into independently appraised or mothballed units.
Existing assets
- Non-divisible: the asset is appraised as a whole at its full installed capacity.
- Divisible: each individual unit is appraised separately, one at a time. This allows partial retention — for example, some units of a multi-unit plant may be retained while others are mothballed.
Candidate assets
Before a candidate asset can be appraised, it is assigned a trial capacity which defines how much capacity can be installed in a single investment round (subject to further demand-limiting capacity and investment constraints, described below)
-
Divisible: the trial capacity is set to one unit (one
unit_size), representing a single unit being considered for investment. -
Non-divisible: the trial capacity is based on the capacity that would satisfy the total remaining demand if the asset operated at its maximum annual rate:
\[ \text{TrialCapacity} = \frac{\sum_t \text{Demand}_t}{\text{MaxAnnualSupplyPerCapacity}} \times \text{CapacityLimitFactor} \]
capacity_limit_factor(set inmodel.toml, must be > 0 and <= 1) controls the size of investment increments relative to total demand. Lower values produce smaller investment increments (requiring more investment rounds), while higher values produce larger increments.
Demand-limiting capacity (DLC)
In each investment round, a candidate’s trial capacity is further capped by the demand-limiting capacity, which is the minimum capacity required to satisfy the remaining demand across all time-slice selections:
\[ \text{DLC} = \max_{\text{selection}} \frac{\sum_{t \in \text{selection}} \text{Demand}_t} {\text{MaxSupplyPerCapacity}_\text{selection}} \]
Selections where the asset has zero maximum supply are excluded. The cap prevents over-investment (i.e. building more capacity than needed to meet remaining demand).
Investment constraints
Processes may have an addition_limit (see
process_investment_constraints.csv) specifying the
maximum new capacity that can be built per year. The installable capacity limit for a given MSY is:
\[ \text{MaxInstallableCapacity} = \text{AdditionLimit} \times \Delta_\text{MSY} \times \text{AgentPortion} \]
where \( \Delta_\text{MSY} \) is the number of years since the previous MSY and \( \text{AgentPortion} \) is the fraction of the commodity market for which this agent is responsible.
If the remaining installable capacity is exhausted, the candidate is excluded from further consideration.
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,\text{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:
\[ \text{NetOperatingCost}_t = \text{OperatingCost}(t) - \text{RevenueFromFlows}(\lambda, t) \]
\[ \text{FallbackCost} = \phi_{c,r,t} \cdot f_{c,\text{primary}} \]
\[ \alpha_t = \text{FallbackCost} - \text{NetOperatingCost}_t + \varepsilon \]
where \( \text{RevenueFromFlows} \) is the sum of all commodity flow revenues and costs (positive for outputs, negative for inputs) valued at shadow prices, \( \text{OperatingCost} \) is the variable operating cost plus levies and flow costs, \( f_{c,\text{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:
\[ \text{NetOperatingCost}_t < \text{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 \text{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^\text{LCOX} = \text{OperatingCost}(t) - \text{RevenueFromNonPrimaryFlows}(\pi, t) \]
-
NPV: the net cost of operating, including all commodity flows (so negative values represent profit): \[ \mu_t^\text{NPV} = \text{OperatingCost}(t) - \text{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:
\[ \text{LCOXMetric} = \frac{\text{AFC} \times \text{cap} + \sum_t \text{Activity}_t \times \mu_t^\text{LCOX}} {\sum_t \text{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:
\[ \text{SNAS} = \frac{-\left(\text{AFC} \times \text{cap} + \sum_t \text{Activity}_t \times \mu_t^\text{NPV}\right)}{\sum_t \text{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 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 |
| \( \text{OperatingCost} \) | £5/MWh activity | Constant across time slices |
All per-flow costs (\( cost_\text{input} \), \( cost_\text{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 act_{t_0} + (-10) \cdot act_{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 \( act_{t_0} = 80 \) MWh and \( act_{t_1} = 20 \) MWh.
LCOX Metric
Market costs (excluding primary output):
\[ \begin{aligned} \mu_{t_0}^\text{LCOX} &= 5 + (2.5 \times 35) - (0.5 \times 25) = \text{£80/MWh} \\ \mu_{t_1}^\text{LCOX} &= 5 + (2.5 \times 25) - (0.5 \times 15) = \text{£60/MWh} \end{aligned} \]
Cost Index: \[ \begin{aligned} \text{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}^\text{NPV} &= 5 - (1.0 \times 90) - (0.5 \times 25) + (2.5 \times 35) = \text{£}{-10}\text{/MWh} \\ \mu_{t_1}^\text{NPV} &= 5 - (1.0 \times 50) - (0.5 \times 15) + (2.5 \times 25) = \text{£10/MWh} \end{aligned} \]
SNAS: \[ \begin{aligned} \text{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.
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: \[ \text{GenericActivityCost} = \text{VariableOpex} + \text{InputPurchases} + \sum \text{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: \[ \text{GenericCostPerOutput} = \frac{\text{GenericActivityCost}}{\sum_{c \in \text{SED, SVD}} \text{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): \[ \text{MarginalCost}_c = \text{GenericCostPerOutput} + \text{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): \[ \text{AnnualCapitalCostPerCapacity} = \text{TotalCapitalCostPerCapacity} \times \text{CRF} \]
The annualised fixed cost (AFC) is: \[ \text{AFC} = (\text{AnnualCapitalCostPerCapacity} + \text{AnnualFixedOpex}) \times \text{TotalCapacity} \]
This is divided by annual activity to get a cost per unit of activity: \[ \text{AnnualFixedCostPerActivity} = \frac{\text{AFC}}{\text{AnnualActivity}} \]
This is divided again by the sum of SED/SVD output coefficients to get a cost per unit output: \[ \text{AnnualFixedCostPerOutput} = \frac{\text{AnnualFixedCostPerActivity}}{\sum_{c \in \text{SED, SVD}} \text{OutputCoefficient}_c} \]
Note: this only works if all output commodities are measured in the same energy units (e.g. PJ). For this reason, MUSE2 disallows processes that have output commodities with differing units.
The final full cost of output commodity \( c \) is: \[ \text{FullCost}_c = \text{MarginalCost}_c + \text{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:
\[ \text{MarginalCost}_{\text{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} \text{CRF} &= 0.11016807219 \\ \text{AnnualFixedCostPerCapacity} &= (10 \times 0.11016807219 + 0.3) = 1.4016807219 \end{aligned} \]
Accounting for annual utilisation: \[ \text{AnnualFixedCostPerActivity} = \frac{1.4016807219}{0.20786471743} = 6.7432354044 \]
Accounting for the output coefficient: \[ \text{AnnualFixedCostPerOutput} = \frac{6.7432354044}{1} = 6.7432354044 \]
The full cost is: \[ \text{FullCost}_{\text{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} \text{CO2Levy} &= 2.5565 \times 0.04 = 0.10226 \\ \text{InputPurchases} &= 8.9477554044 \times 1.05 = 9.39514317462 \\ \text{MarginalCost}_{\text{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} \text{CRF} &= 0.11016807219 \\ \text{AnnualFixedCostPerCapacity} &= (7 \times 0.11016807219 + 0.21) = 0.98117650533 \end{aligned} \]
Accounting for annual utilisation: \[ \text{AnnualFixedCostPerActivity} = \frac{0.98117650533}{0.2094885671} = 4.6836756722 \]
Accounting for the output coefficient: \[ \text{AnnualFixedCostPerOutput} = \frac{4.6836756722}{1} = 4.6836756722 \]
The full cost is: \[ \text{FullCost}_{\text{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 flow of input/s or output/s of a Process that are limited by its capacity. For example, a 500MW power station can output 500MWh per hour of electrical power, or a 50MW electrolyser consumes up to 50MWh per hour of electrical power to produce hydrogen.
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/s: One or more objectives that an Agent considers when deciding which Process to invest in. Objectives can be economic, environmental, or others.
Asset: Once an Agent makes an investment, the related capacity of their chosen Process becomes an Asset that they own and operate. An Asset is an instance of a Process, it has a specific capacity, and a decommissioning year. A set of Assets must exist in the base year sufficient to serve base year demands (i.e. a calibrated base year, based on user input data).
Availability: The maximum, minimum or fixed percentage of maximum output (or input) that a Process delivers over a period. The time period could be a single time slice, a season, or a year.
Base Year: The starting year of a model run. The base year is typically calibrated to known data, including Process stock and commodity consumption/production.
Calibration: The act of ensuring that the model represents the system being modelled in a historical base year.
Capacity: The maximum output (or input) of an Asset.
Capital Cost: The overnight capital cost of a process.
Commodity: A substance (e.g. CO2) 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 processes for that commodity), net production (sum of output and input for all processes), or all consumption (sum of input for all processes). It can also be negative, indicating an incentive on commodity production/consumption/net.
Decision Rule: The rule via which an Agent uses the Objective/s to decide between Process options to invest in. Examples include single objective, weighted sum between multiple objectives, or epsilon constraint where a secondary objective is considered if two options with similar primary objectives are identified.
Dispatch: The way in which Assets are operated to serve demand. MUSE2 uses merit order dispatch, subject to Availability and other constraints that can be defined by the user.
End Year: The final year in the model time horizon.
Equivalent Annual Cost (EAC): An Agent objective, representing the annualised cost of serving all or part of an Agent’s demand for a year, considering the Asset’s entire lifetime.
Fixed Operating Cost: The Asset or Process annual operating cost charged per unit of capacity.
Input Commodity/ies: The commodities that flow into a Process.
Levelised Cost of X (LCOX): An Agent objective, representing the discounted cost of 1 unit of output commodity X from a process over its lifetime under a specified discount rate.
Lifetime: The lifetime of a Process, measured in years.
Milestone Years: A set of years in the model time horizon where model results are recorded. For example, with a 2025 Base Year and End Year 2100, a user might choose to record outputs in 5-year steps.
Merit Order: A method of operating Assets when the cheapest is dispatched first, followed by the next most expensive, etc, until demand is served. Also called “unit commitment.”
OTH (other)-type commodity: A commoodity 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/ies: The commodities that flow out of a Process.
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, non-fuel variable operating cost per unit activity, and risk discount rate. They have physical attributes of quantity and type of input and output commodities (which implicitly specify efficiency), Availability limits (by time slice, season and/or year), 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 that is modelled. Regions primarily determine trade boundaries.
Season: A year is usually broken down into seasons in the model. For example, summer, winter, 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 Service Demand is a type of commodity that is consumed at the boundary of the modelled system. For example, tonne-kilometers of road freight, PJ of useful heat demand, etc.
Supply Equals Demand (SED):: An SED commodity is a type of commodity that is both consumed and produced by processes in the system. In fully resolved systems, supply of these commodities is constrained to be equal to or greater than demand.
Discount Rate: The discount rate used to calculate any process-specific agent economic objectives that require a discount rate. For example, Equivalent Annual Cost, Net Present Value, Levelised Cost of X, etc.
Time Horizon: The overall period modelled. For example, 2025–2100.
Time Period: Refers to a specific Milestone Year in the time horizon.
Time Slice: The finest time period in the model. The maximum time slice length is 1 year (where a model does not represent seasons or within-day (diurnal) variation). A typical model will have several diurnal time slices, and several seasonal time slices.
Utilisation: The percentage of an Asset’s capacity that is actually used to produce its commodities. Must be between 0 and 1, and can be measured at time slice, season, or year level.
Variable Operating Cost: The variable operating cost charged per unit of activity of the Process.
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.
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_divisible
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.
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)
Upcoming release of MUSE2
New features
- Users can now optionally pass custom options to the HiGHS solver (#1276)
- Implement a new appraisal approach for the LCOX objective type (#1319)
- Memory usage and performance has been drastically improved for divisible assets with many units (#1464)
Breaking changes
- Changed the default
pricing_strategyfor SED/SVD commodities from “shadow” to “full_average” (#1281) - 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)
Bug fixes
- Fix misleading warning message for assets decommissioned before simulation start (#1259)
- Fix parsing and validation of agent search space file (#1293)
- Use shadow prices rather than market prices for appraisal optimisations and dispatch runs during investment (#1349)
- Updated input data validation to ensure
capacity_to_activity > 0. Previously,capacity_to_activity = 0was permitted. - Mothballed assets are now excluded from the main dispatch run (#1458)