Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

    docs: ▲

    MUSE2

    Overview

    ⚠️ Please note that MUSE2 currently only works with simple models and is not yet suitable for use in research. ⚠️

    MUSE2 (ModUlar energy systems Simulation Environment) is a tool for running simulations of energy systems, written in Rust. Its purpose is to provide users with a framework to simulate pathways of energy system transition, usually in the context of climate change mitigation.

    It is the successor to MUSE, which is written in Python. It was developed following re-design of MUSE to address a range of legacy issues that are challenging to address via upgrades to the existing MUSE framework, and to implement the framework in the high-performance Rust language.

    This work was supported via the Climate Compatible Growth (CCG) programme and the EPSRC-funded HI-ACT project (EP/X038823/2). CCG is funded by UK Aid from the UK government. However, the views expressed herein do not necessarily reflect the UK government’s official policies.

    Getting started

    You will first need to install MUSE2. We recommend downloading the latest version for your platform on the releases page. The archive file you download should contain a readme file containing instructions on how to run MUSE2. Alternatively, you can install the muse2 crate from crates.io using cargo (see Setting up your development environment).

    Once you have installed MUSE2, you can look at the user guide and the command line help for details on how to get started with creating and running your own models.

    Detailed information about the model used by MUSE2 is provided in Model Description. For a list of relevant terms, see the glossary.

    If you want to work with the MUSE2 source code, for instance to submit a change to this repository, please see the developer guide.

    User Guide

    Once you have installed MUSE2, you should be able to run it via the muse2 command-line program. For details of the command-line interface, see here or run muse2 help.

    Example models

    MUSE2 comes with a number of example models, partly to demonstrate the various program features as well as to be used as templates for new models. To see the list of available examples, you can run:

    muse2 example list
    

    It should print something like the following:

    circularity
    missing_commodity
    muse1_default
    simple
    two_outputs
    two_regions
    

    To view information about a particular example, you can run, e.g.:

    muse2 example info simple
    

    You can run examples like so:

    muse2 example run simple
    

    Building and running your own model

    🚧 Note that this section is under construction! 🚧

    In the longer term, we plan to have a tutorial describing how to build a model in more detail.

    Models in MUSE2 are defined with one TOML file (model.toml) and many CSV files. For a description of each of the files and the different fields, see the documentation for input files.

    Creating a new model from an example

    We recommend you use one of the examples as starting point for your own model, as there are many required files.

    To create a new model based on the simple example, run:

    muse2 example extract simple new_model
    

    This will create a new subdirectory called new_model in the current folder.

    Validating this model

    Before running a model, you can check that its input files can be read and that the model is internally consistent with:

    muse2 validate new_model
    

    Running this model

    Next, let’s run this model so you can see the output for a working model. You can do this by running:

    muse2 run new_model
    

    If everything works as expected, you should see output on your terminal indicating the progress of the simulation (which should finish very quickly).

    The first few lines should look something like:

    [12:24:20 INFO muse2::cli] Starting MUSE2 v2.X.Y
    [12:24:20 INFO muse2::cli] Loaded model from new_model/
    [12:24:20 INFO muse2::cli] Output folder: muse2_results/new_model
    ...
    

    You should see that a new muse2_results folder has been created. This folder will contain the output for your model in a subfolder called new_model. For information about how to interpret these files, see the documentation for output files. There is also the muse2_data_analysis repository, which contains example Jupyter notebooks for processing output data.

    Next steps

    You will now want to configure the model for your own use case. You should start by looking at the documentation for input files for details of the different data types and parameters for MUSE2.

    Unfortunately, this may not be easy, especially if you are not already familiar with MUSE1. In the longer term, we will have tutorials, so watch this space! In the meantime, if you have a question, feel free to open an issue.

    Visualising commodity graphs

    To visualise the structure of your model, you can use the muse2 save-graphs command to create graphs of commodity/process relationships. This command will output a graph for each region/year in the simulation, where nodes are commodities and edges are processes. Graphs will be saved in DOT format, which can be visualised locally with Graphviz, or online with Graphviz online. Dashed lines are used to indicate flows for non-primary outputs of a process (as defined in the processes.csv input file).

    Modifying the program settings

    You can configure the behaviour of MUSE2 with a settings.toml file. To edit this file, run:

    muse2 settings edit
    

    There are also some more commands for working with the settings file. For details, run muse2 settings help or see the muse2 settings command documentation.

    For information about the available settings, see the documentation for the settings.toml file.

    Setting the log level

    MUSE2 uses the fern crate for logging. The default log level is info, though this can be configured either via the log_level option in settings.toml or by setting the MUSE2_LOG_LEVEL environment variable. (If both are used, the environment variable takes precedence.)

    The possible options are:

    • error
    • warn
    • info
    • debug
    • trace
    • off

    By default, MUSE2 will colourise the log output if this is available (i.e. it is outputting to a terminal rather than a file).

    For more information, please consult the fern documentation.

    Example models

    This document describes the various example models included with MUSE2.

    To list the available examples, run:

    muse2 example list
    

    To view details of a particular example, run e.g.:

    muse2 example info simple
    

    circularity example

    This model extends “two_outputs” to showcase circular commodity flows, adding a hydrogen loop that turns electricity into hydrogen and back again.

    Note: this model is currently experimental and only included for development purposes.

    missing_commodity example

    This model is like the “simple” example, except that it includes a separate biomass supply chain, which has no capacity in the base year. This is partly done to exemplify how MUSE2 can generate prices for commodities not utilised in a given year.

    muse1_default example

    This model is a port of the default model for MUSE v1 (see 1), primarily for comparison (e.g. benchmarking). Note that the model is not an exact port as MUSE v1 and MUSE v2 support different features. Also be aware that due to this and differences in the underlying algorithm, investment behaviour will differ between the two programs.

    It is broadly similar to the “simple” example, in that it contains a single service demand commodity, residential heat, which can be served by either gas boilers or heat pumps, though some of the details, such as parameter values, are different. It also includes a carbon tax.

    simple example

    A minimal example for a single region.

    It includes a single service demand commodity, residential heat, which can be served by a combination of gas boilers and heat pumps. Electricity and natural gas are modelled as upstream commodities, which can be served by various processes. CO2 is modelled as a byproduct of some gas-related processes and a carbon tax is included as a levy.

    two_outputs example

    This model is like the “missing_commodity” example, except that it includes transport related supply chains, and one processes that produces two SED outputs (OILREF), and one process that consumes two SED inputs (THYBCR).

    two_regions example

    This model is based on the “muse1_default” example, except that it includes two regions.

    Command-Line Help for muse2

    This document contains the help content for the muse2 command-line program.

    Command Overview:

    muse2

    A tool for running simulations of energy systems

    Usage: muse2 [COMMAND]

    For more detailed documentation on this version of MUSE2, see: https://energysystemsmodellinglab.github.io/MUSE2/v2.2.0/

    Subcommands:
    • run — Run a simulation model
    • example — Manage example models
    • validate — Validate a model
    • save-graphs — Build and output commodity flow graphs for a model
    • settings — 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 examples
    • info — Provide information about the specified example
    • extract — Extract an example model configuration to a new directory
    • run — 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 file
    • delete — Delete the settings file, if any
    • path — Get the path to where the settings file is read from
    • show — Show the contents of the settings.toml, if present
    • show-default — Show the default settings for settings.toml

    muse2 settings edit

    Edit the program settings file

    Usage: muse2 settings edit

    muse2 settings delete

    Delete the settings file, if any

    Usage: muse2 settings delete

    muse2 settings path

    Get the path to where the settings file is read from

    Usage: muse2 settings path

    muse2 settings show

    Show the contents of the settings.toml, if present

    Usage: muse2 settings show

    muse2 settings show-default

    Show the default settings for settings.toml

    Usage: muse2 settings show-default


    This document was generated automatically by clap-markdown.

    File Formats

    The following chapters describe the formats for the various files that MUSE2 consumes/produces. Files are either in CSV or TOML format.

    Program settings: settings.toml

    User settings for MUSE2. Note that these settings do not include model parameters, which are found in model.toml instead.

    All settings are optional. If the settings.toml file does not exist, the default settings will be used.

    The settings.toml file is read from MUSE2’s platform-specific configuration directory, in a muse2 subdirectory. Use muse2 settings path to find the exact location on your system.

    Top-level table

    FieldDescriptionNotes
    log_levelThe program’s log levelOptional. Defaults to info.

    Must be one of error, warn, info, debug, trace or off (case insensitive)
    overwriteWhether to overwrite output files by defaultOptional. Defaults to false.

    debug_modelWhether to include extra debugging information in the model outputOptional. 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_filesWhether to copy input files to the output folderOptional. Defaults to true.

    results_rootResults root path to save MUSE2 resultsOptional. Defaults to muse2_results.

    Defaults to a “muse2_results” folder within the current working directory.
    graph_results_rootResults root path to save MUSE2 graph outputsOptional. 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

    FieldDescriptionNotes
    milestone_yearsThe milestone years for the simulationMust be positive integers that are sorted and unique
    currencyOptional currency label for monetary model inputsThis is a free-text label used to document the monetary convention for model inputs and outputs. It may include a currency and reference year, such as MUSD2020 (i.e. millions of US dollars in 2020 terms), or take any other form chosen by the modeller. The label is metadata only: MUSE2 does not interpret it, perform currency conversion, or adjust monetary values for inflation. Monetary inputs should use a consistent currency and reference year.
    candidate_asset_capacityCapacity to give to candidate assets in dispatch optimisationOptional. Defaults to 0.0001.

    The default value should work. Do not change this value unless you know what you’re doing!
    commodity_balance_epsilonEpsilon added to commodity balance lower bounds to force some dispatch by candidate assetsOptional. Defaults to 1e-6.

    The default value should work. Do not change this value unless you know what you’re doing!
    capacity_tranche_fractionScales the size of inferred investment tranches for candidate assetsOptional. Defaults to 0.05.

    When a process does not define tranche_size, candidate assets for investment are assigned a tranche whose capacity is this fraction of the characteristic capacity scale based on annual demand. Each investment round appraises one such tranche.

    Must be >0 and <=1. Lower values produce smaller tranches and require more investment rounds, while higher values produce larger tranches.
    fallback_pricing_strategyPricing strategy used to calculate fallback prices in the mini dispatch optimisation during investment appraisalOptional. 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_loadThe cost applied to unmet demandOptional. Defaults to 1e9.

    Currently this only applies to the LCOX appraisal.
    seasonal_utilisation_penaltyPenalty per unit of capacity used within a seasonOptional. Defaults to 1e-6.

    A small additive penalty to discourage assets from using a high capacity-equivalent peak within a season. This should be very small, as it is only intended to break ties between otherwise equivalent solutions.
    annual_utilisation_penaltyPenalty per unit of capacity used across the whole yearOptional. Defaults to 1e-6.

    A small additive penalty to discourage assets from using a high capacity-equivalent peak across the year. This should be very small, as it is only intended to break ties between otherwise equivalent solutions.
    max_ironing_out_iterationsThe maximum number of iterations to run the “ironing out” step of agent investment forOptional. Defaults to 1.

    price_toleranceThe relative tolerance for price convergence in the ironing out loopOptional. Defaults to 1e-6.

    capacity_marginSlack proportion for assets selected during cycle balancing to absorb small demand shiftsOptional. Defaults to 0.2.

    mothball_yearsNumber 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_resultsAllows 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_toleranceAbsolute 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.

    highsUsed 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

    FieldDescriptionNotes
    global_optionsHiGHS options applied to all optimisations
    dispatch_optionsHiGHS options applied only to dispatch optimisations
    appraisal_optionsHiGHS 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).

    FieldDescriptionNotes
    seasonName of season
    time_of_dayName of time of day
    fractionFraction of yearMust be >0 and <=1

    Notes

    • The fractions for the different time slices must sum to one.

    Regions

    regions.csv

    Describes regions in the system.

    FieldDescriptionNotes
    idA unique identifier for a region
    descriptionA human-readable label for the region

    Agents

    agents.csv

    Describes agents in the system.

    FieldDescriptionNotes
    idA unique identifier for an agent
    descriptionA human-readable label for the agent
    regionsThe region(s) in which the agent operatesOne or more region IDs, separated by semicolons or the string all
    decision_ruleThe decision rule applied to objectivesCurrently the only supported rule is single
    decision_lexico_toleranceTolerance for lexico decision ruleCurrently unused

    agent_search_spaces.csv

    Defines the processes in which an agent will invest for given parameters.

    FieldDescriptionNotes
    agent_idAn agent ID
    commodity_idThe commodity to which this entry applies
    yearsThe year(s) to which this entry appliesOne 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_spaceThe processes in which this agent will investOne or more process IDs separated by semicolons or all, meaning all processes producing the relevant commodity in the given year in a relevant region.

    Notes

    • If entries are missing for any combination of agent, commodity or milestone year, then it is assumed that all processes can be considered in this case.

    agent_commodity_portions.csv

    Portions of commodity demand for which agents are responsible.

    FieldDescriptionNotes
    agent_idThe agent to apply these values to
    commodity_idThe commodity for which the agent is responsible
    yearsThe year(s) to which this entry appliesOne 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_portionPortion of commodity demandValue must be >0 and <=1. The portion applies only to the specified years.

    Notes

    • If an entry is specified for one agent and commodity, there must be entries covering all milestone years.
    • For each agent listed in this file, the total portions for each region/commodity/year combination must sum to one.
    • In addition, there must be entries for every SVD and SED commodity for all regions and milestone years.

    agent_objectives.csv

    Describes the agents’ objectives.

    FieldDescriptionNotes
    agent_idAn agent ID
    yearsThe year(s) to which this entry appliesOne 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_typeThe type of objectiveMust be npv (net present value) or lcox (levelised cost of X).
    decision_weightWeight for weighted sum decision ruleCurrently unused
    decision_lexico_orderOrder in which to consider objectives for lexico decision ruleCurrently unused

    Notes

    • Every agent must have one objective for each milestone year.
    • If the weighted sum decision rule is in use, the decision_weight value must be provided, otherwise it must be omitted.
    • If the lexico decision rule is in use, the decision_lexico_order value must be provided, otherwise it must be omitted.

    Assets

    assets.csv

    Defines assets in the system.

    FieldDescriptionNotes
    process_idThe process of which this asset is an instance
    region_idThe region in which this agent operates
    agent_idThe agent to which this asset belongs
    capacityThe capacity of the assetMust be >0
    num_tranchesThe number of tranches comprising the assetOptional. If provided, the asset is split into num_tranches tranches of size capacity / num_tranches (taking precedence over the process’s tranche_size). If omitted and the process has a tranche_size, the asset is divided into ceil(capacity / tranche_size) tranches (note that rounding up the number of tranches can result in a total capacity greater than the specified capacity). If both values are omitted, the asset is represented as one tranche with the full asset capacity. Must be >0 if provided.
    commission_yearThe year in which to commission this assetThis 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_yearThe latest year in which this asset can be decommissionedOptional. 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.

    FieldDescriptionNotes
    idA unique identifier for the commodity
    descriptionA human-readable label for the commodity
    typeThe type of commodityMust be one of svd (service demand), sed (supply equals demand) or oth (other)
    time_slice_levelThe time slice level at which constraints for this commodity are appliedMust be one of annual (whole year), season (whole season) or daynight (a particular time of day)
    pricing_strategyThe pricing strategy for this commodityOptional. 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.
    unitsThe units of measurement for this commodityThis 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).

    FieldDescriptionNotes
    commodity_idThe commodity to which this entry applies
    regionsThe region(s) to which this entry appliesOne or more region IDs, separated by semicolons or the string all
    yearsThe year(s) to which this entry appliesOne 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_sliceThe time slice(s) to which this entry appliesCan be a single time slice (e.g. winter.day), a whole season (e.g. winter) or annual, representing the whole year
    balance_typeThe type of balance to which this is appliedCan 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)
    valueThe 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.

    FieldDescriptionNotes
    commodity_idThe service demand commodity to which this entry applies
    region_idThe region to which this entry appliesA region ID
    yearThe year(s) to which this entry appliesOne 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.
    demandTotal 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).

    FieldDescriptionNotes
    commodity_idThe service demand commodity to which this entry applies
    region_idThe region to which this entry appliesA region ID
    time_sliceThe time slice(s) to which this entry appliesCan be a single time slice (e.g. winter.day), a whole season (e.g. winter) or annual, representing the whole year
    fractionThe fraction of annual demand for this entryMust 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.

    FieldDescriptionNotes
    idA unique identifier for a process
    descriptionA human-readable label for the process
    regionsThe region(s) in which this process can operateOne or more region IDs, separated by semicolons or the string all
    primary_outputThe primary output commodity, if any, for this processThis 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_yearThe first year when this process is available to agents
    end_yearThe last year when this process is available to agentsMust be >= to start_year
    capacity_to_activityFactor relating capacity units (e.g. GW) to activity units (e.g. PJ). It is the maximum activity per year for one unit of capacity.Must be >0. Optional (defaults to 1.0).
    tranche_sizeDefault capacity of the individual tranches making up assets of this processOptional (defaults to None). If present, must be >0. If no tranche size is specified, the tranche size for a MUSE2 investment is inferred from demand at the time of investment and capacity_tranche_fraction. For a user-provided asset, num_tranches in assets.csv can instead determine the tranche size.

    process_parameters.csv

    Parameters for processes which vary by region and year.

    FieldDescriptionNotes
    process_idThe process to which this entry applies
    regionsThe region(s) to which this entry appliesOne or more region IDs, separated by semicolons or the string all. Must be regions in which the process operates.
    commission_yearsThe milestone year(s) to which this entry appliesOne 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_costOvernight capital cost per unit capacity
    fixed_operating_costAnnual operating cost per unit capacity
    variable_operating_costAnnual variable operating cost per unit activity
    lifetimeLifetime in years of an asset created from this processMust be a positive integer
    discount_rateProcess-specific discount rateMust be positive. A warning will be issued if this number is >1.

    Notes

    • For each process, entries must be provided which cover every combination of milestone year and region in which the process can be commissioned.

    process_flows.csv

    The commodity flows for each process.

    FieldDescriptionNotes
    process_idThe process to which this entry applies
    commodity_idThe commodity for this flow
    regionsThe region(s) to which this entry appliesOne or more region IDs, separated by semicolons or the string all. Must be regions in which the process operates.
    commission_yearsThe year(s) to which this entry appliesOne 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.
    coeffThe flow for this commodityCan be <0, indicating an input flow, or >0, indicating an output flow. Cannot be zero.
    typeThe type of commodity flowCurrently this value must be fixed
    costThe cost per unit flowOptional. If present, must be >0.

    Notes

    • Commodity flows can vary by region and year.
    • For each process, there must be entries covering all the years and regions in which the process can be commissioned.

    process_investment_constraints.csv

    Constraints on the amount agents can invest in processes.

    FieldDescriptionNotes
    process_idThe process to which this entry applies
    regionsThe region(s) to which this entry appliesOne or more region IDs, separated by semicolons or the string all. Must be regions in which the process operates.
    commission_yearsThe milestone year(s) to which this entry appliesOne 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_limitYearly constraint on the amount agents can invest in the processThe addition limit is allocated between all agents in proportion to their share of the process’s primary commodity demand.
    capacity_growth_limitThe 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_seedA 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_limitA hard upper limit on the total amount of installed capacity that can exist at any given timeThe total capacity limit is allocated between all agents in proportion to their share of the process’s primary commodity demand.

    process_availabilities.csv

    Defines constraints on process availabilities throughout the year.

    FieldDescriptionNotes
    process_idThe process to which this entry applies
    regionsThe region(s) to which this entry appliesOne or more region IDs, separated by semicolons or the string all
    commission_yearsThe milestone year(s) to which this entry appliesOne 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_sliceThe time slice(s) to which this entry appliesCan 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.
    limitsLower and upper limits on the availability of the process within the specified time slice(s)A string in the format min..max, where min and max are decimal numbers between 0 and 1 inclusive (e.g. “0.2..0.9”). 0 represents no availability, and 1 represents full availability. Either min or max can be omitted (e.g “0.2..” or “..0.9”), which will set the corresponding limit to 0 or 1, respectively.

    Notes

    • Limits can be combined across multiple time periods. For example, users could provide a limit of “..0.9” for “winter.day” and “..0.5” for “winter”, indicating that the activity can reach up to 90% during winter days, so long as activity does not exceed 50% across winter as a whole.
    • Incompatible limits will be flagged at the input validation stage. For example, a limit of “..0.01” for “winter” would likely be incompatible with a limit of “0.9..” for “winter.day”.
    • If a limit is provided for any time slice (or season) for a particular process/region/year, it must be provided for all time slices (or seasons).

    Output Files

    This file contains information about the file formats for MUSE2 output files.

    Note that we only document the main CSV output files, not those produced when the --debug-model option is active.

    Metadata: metadata.toml

    Metadata about the program run, the MUSE2 build and the user’s platform.

    Top-level table

    FieldDescriptionNotes
    runInformation about the program run
    programInformation about MUSE2
    platformInformation about the platform on which the simulation was run

    [run] table

    FieldDescriptionNotes
    model_pathPath to the input model
    datetimeTimestamp for the model runTime at which the model run commenced

    [program] table

    FieldDescriptionNotes
    nameThe name of the program (muse2)
    versionProgram version (e.g. 2.0.0)
    targetThe target triple MUSE2 was built for
    is_debugWhether the current build is a debug or release build
    rustc_versionThe version of rustc used to compile MUSE2
    build_time_utcThe UTC time and date at which MUSE2 was built
    git_commit_hashThe short hash of the git commit from which MUSE2 was builtIf the git working tree was dirty, -dirty is appended to the hash

    [platform] table

    FieldDescriptionNotes
    sysnameThe name of the implementation of the operating system
    nodenameThe hostname of the machine
    releaseThe release level of the operating system
    versionThe version level of the release
    machineThe name of the system’s hardware
    osnameThe name of the current OS

    Main CSV output files

    assets.csv

    Assets commissioned during the course of the simulation.

    FieldDescriptionNotes
    asset_idThe ID number of this asset
    process_idThe ID of this asset’s associated process
    region_idThe region in which this asset exists
    agent_idThe agent to which this asset belongs
    commission_yearThe year in which the asset was commissioned

    asset_capacities.csv

    Capacity of each asset at each milestone year during the simulation.

    FieldDescriptionNotes
    milestone_yearThe milestone year of the simulation
    asset_idThe ID of this asset
    capacityThe total capacity of the asset in this year
    num_tranchesThe total number of tranches making up the asset in this year
    mothballed_capacityThe capacity of the asset that is mothballed in this year
    mothballed_tranchesThe number of tranches that are mothballed in this year

    commodity_flows.csv

    How much of each commodity was produced and consumed in the course of the simulation.

    FieldDescriptionNotes
    milestone_yearMilestone year of the simulation
    asset_idThe asset this entry relates toSee the assets.csv output file for information about assets
    commodity_idThe commodity this entry relates to
    time_sliceThe time slice during which this flow occurred
    flowThe amount of commodity flowIf 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.

    FieldDescriptionNotes
    milestone_yearMilestone year of the simulation
    commodity_idThe commodity this entry relates to
    region_idThe region this entry relates to
    time_sliceThe time slice in which this price holds
    priceThe price of the commodity

    Model Description

    The purpose of MUSE2 (ModUlar energy systems Simulation Environment) is to provide users with a framework to simulate pathways of energy system transition, usually in the context of climate change mitigation.

    Model Concept

    MUSE2 is a bottom-up engineering-economic modelling framework that computes a price-induced supply-demand equilibrium on a set of user-defined commodities. It does this for each milestone time period within a user-defined time horizon. This is a “partial equilibrium” in the sense that the framework equilibrates only the user-defined commodities, as opposed to a whole economy.

    MUSE2 is data-driven in the sense that model processing and data are entirely independent, and user-defined data is at the heart of how the model behaves. It is also “bottom-up” in nature, which means that it requires users to characterise each individual process that produces or consumes each commodity, along with a range of other physical, economic and agent parameters.

    MUSE2 does not require a single set of physical units, but the units used within each model must be consistent. See Units and Dimensions for how capacity, activity, commodity flows, and monetary values are related.

    At a high level, the user defines:

    1. The overall temporal arrangements, including the base time period, milestone time periods and time horizon, and within-period time slice lengths.

    2. 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.

    3. 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.

    4. 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.

    5. The agents that choose between processes by applying search spaces, objectives and decision rules. Portions of demand for each commodity must be assigned to an agent, and the sum of these portions must be one.

    Framework Overview

    The model operates sequentially across a series of milestone years (MSYs). For the base year, existing assets are commissioned and a dispatch optimisation is run to establish commodity prices for the first investment year. For each subsequent MSY, the model performs the following steps:

    1. Decommission end-of-life assets

    Assets whose scheduled decommissioning year has been reached are removed from the active pool.

    2. Agent investment

    Agents select assets to meet commodity demand for the current MSY. Investment decisions follow a pre-computed investment order derived from the topology of the commodity graph: markets are processed deepest-first (most downstream to most upstream), grouped into layers of independent markets at the same depth. See Investment Appraisal for full details.

    After all markets in each layer are settled, a partial system dispatch is run over all assets selected so far. This propagates demand upstream: input commodity flows consumed by newly committed assets become demand targets for the markets not yet invested in.

    3. Mothballing and decommissioning

    Previously commissioned assets that were not selected for retention are mothballed. Any asset that has remained mothballed for longer than mothball_years (defined in model.toml) is permanently decommissioned.

    4. Ironing-out loop

    Investment and dispatch are repeated iteratively to resolve any price instability introduced by new capacity commitments. Each iteration runs the full agent investment pass followed by a full system dispatch. The loop terminates when time-slice-weighted market prices have converged within price_tolerance, or when max_ironing_out_iterations is reached (both defined in model.toml).

    5. Final dispatch

    A full dispatch optimisation is run over the settled asset pool. The resulting flows and commodity prices are written to the output files and carried forward as the price basis for the next MSY’s investment appraisal.

    Units and Dimensions

    MUSE2 does not impose one universal system of physical units. Instead, the input files define the unit conventions used by each model. Commodity units are provided in the units field of commodities.csv, while the optional currency field in model.toml documents the currency and, if needed, its reference year. Process flow coefficients and capacity_to_activity then define the relationships between activity, commodity flows, and capacity.

    MUSE2 requires commodity unit labels to be present and uses them to check consistency, but it does not interpret their contents, convert between units, or perform general dimensional analysis. For example, PJ, tonnes, and ktCO2 are simply labels to MUSE2; their physical meanings come from the conventions chosen by the modeller. Similarly, the currency value is just a label: MUSE2 does not perform currency conversions or adjust monetary values for inflation.

    The main relationships are:

    • Activity describes how much a Process operates.
    • Commodity units label the quantities produced and consumed by a Process.
    • Flow coefficients convert activity into input and output flows of each Commodity.
    • Capacity-to-activity relates installed capacity to maximum annual activity.
    • Capacity limits the activity an Asset can perform.
    • Monetary units describe how costs and prices are expressed.

    For the example models, energy commodities are measured in PJ, generation capacity is expressed in GW, emissions are measured in ktCO2, and monetary values are expressed in millions of US dollars.

    Commodity flows and flow coefficients

    Every commodity must provide a unit label in the units field of commodities.csv. The label is useful for documentation purposes, and is only used by MUSE2 for consistency checks; MUSE2 does not parse it or convert between labels. The “simple” example uses:

    CommodityTypeUnit in the “simple” example
    GASPRDSupply Equals DemandPJ
    GASNATSupply Equals DemandPJ
    ELCTRISupply Equals DemandPJ
    RSHEATService DemandPJ
    CO2EMTOtherktCO2

    If any process has multiple SED or SVD output commodities, these commodities must all use exactly the same unit label - this is checked and enforced during input file loading. Inputs and OTH outputs are not subject to this check.

    A process flow coefficient converts one activity unit into the corresponding commodity flow. Positive coefficients represent production and negative coefficients represent consumption. A flow coefficient therefore maps a dimensionless activity quantity to the corresponding commodity-flow quantity. The coefficient must be interpreted together with the commodity’s unit label.

    For example, the “simple” example defines the gas combined-cycle turbine with these flows:

    FlowCoefficientUnits and meaning
    Natural gas input-1.5-1.5 PJ/activity unit
    Electricity output1.01.0 PJ/activity unit
    CO2 emissions76.69576.695 ktCO2/activity unit

    Activity

    Activity is a dimensionless, process-specific quantity used by MUSE2 to describe how much an asset operates in a time slice. It has no intrinsic physical unit. Its physical interpretation is supplied by the process’s flow coefficients and commodity-unit labels, while its maximum annual amount is constrained by the asset’s capacity and capacity_to_activity.

    Capacity and capacity-to-activity

    Capacity is the installed size of an Asset. Its unit depends on the technology and the model. For example, a power station might have capacity in MW or GW, while a material-processing process might use tonnes per year.

    The capacity_to_activity factor gives the maximum annual activity per unit of capacity. The factor, flow coefficients, and commodity-unit labels work together to define what the capacity unit represents physically.

    In the “simple” example, the wind-farm and gas-turbine processes use capacity_to_activity = 31.54. Since electricity output is labelled PJ and has a coefficient of 1.0, this value was chosen so that one capacity unit corresponds to 1 GW of electricity-generating capacity, able to produce up to 31.54 PJ of electricity per year. This follows from:

    1 GW x 31,536,000 seconds per year = 31,536,000,000,000,000 J per year = 31.536 PJ per year

    The model’s value of 31.54 is rounded from 31.536. At full activity, the gas-turbine coefficients (above) then imply 47.31 PJ of natural-gas consumption per year, and approximately 2,419 ktCO2 of emissions. In practice, availability limits may reduce actual activity below this maximum.

    Conversely, a coal-producing process where coal is measured in tonnes, with an output coefficient of 1.0, might use capacity_to_activity = 1, so that one capacity unit represents an amount of capacity able to produce up to one tonne of coal per year.

    Monetary units

    MUSE2 does not convert currencies or adjust monetary values for inflation. Therefore, all monetary inputs and outputs must be expressed using a consistent currency and reference year. For documentation purposes, the currency and reference year can be included in the currency label in model.toml, such as:

    currency = "MUSD2020"
    

    This is a free-text label for documenting the monetary convention used by a model. It has no prescribed format, so modellers can use it to record a currency, reference year, or any other relevant information.

    Here, MUSD2020 means million US dollars expressed in 2020 terms. The label is descriptive metadata only; it does not determine the units of monetary data or affect model calculations.

    For example, with currency = "MUSD2020" and capacity measured in GW, the monetary quantities would be expressed as follows:

    QuantityUnits
    Capital costMUSD2020/GW
    Fixed operating costMUSD2020/GW/year
    Variable operating costMUSD2020 per unit of activity
    Flow cost or levyMUSD2020 per unit of commodity flow (e.g. MUSD2020/PJ)
    Commodity priceMUSD2020 per unit of commodity flow (e.g. MUSD2020/PJ)

    For example, a variable_operating_cost of 0.55 in a model defined with currency = "MUSD2020" means that one activity unit incurs a variable operating cost of 0.55 MUSD2020 (i.e. $550,000 in 2020 terms).

    Choosing consistent units

    When building a model:

    1. Choose units for each commodity, such as PJ for energy and ktCO2 for emissions.
    2. Choose a capacity unit for each process, such as GW for a power plant.
    3. Set capacity_to_activity so that capacity converts to the appropriate annual activity scale.
    4. Define flow coefficients so that activity produces and consumes the intended commodity quantities.
    5. Express all costs and prices using the same currency convention and the relevant capacity, activity, or commodity-flow denominator.

    MUSE2 validates some unit relationships, but it does not perform general unit conversion. A model can use different physical units for different commodities, provided that each process’s capacity, activity, flow, and cost data are dimensionally consistent.

    Dispatch Optimisation

    This section describes the formulation of the dispatch optimisation model in MUSE2. For a given milestone year (MSY), the dispatch model calculates the least-cost operation of the energy system assets to satisfy commodity demands. It is solved as a Linear Programme (LP) using the HiGHS solver.

    Sets and Indices

    The energy system is defined using the following sets:

    • \( r \in \mathbf{R} \): Regions. Represents distinct geographical areas.
    • \( t \in \mathbf{T} \): Time Slices. Discrete operational sub-periods within a year.
    • \( s \in \mathbf{S} \): Time Slice Selections. Collections of time slices representing temporal groupings at different levels (e.g. a single time slice, a season, or the entire year).
    • \( a \in \mathbf{A} \): Assets. All commissioned production, consumption, or conversion technologies.
    • \( c \in \mathbf{C} \): Commodities. All energy carriers, emissions, or materials, partitioned into:
      • \( \mathbf{C}^{\mathrm{SVD}} \): Service Demand commodities (representing final end-use demands).
      • \( \mathbf{C}^{\mathrm{SED}} \): Supply-Equals-Demand commodities (intermediate carrier flows like electricity or hydrogen).
      • \( \mathbf{C}^{\mathrm{OTH}} \): Other commodities (e.g. emissions or tracked flows not subject to supply-demand balancing).

    Decision Variables

    The dispatch model determines the activity level of all assets \( a \in \mathbf{A} \) in all time slices \( t \in \mathbf{T}\):

    \[ \mathrm{Activity}_{a, t} \ge 0 \text{ for all } a \in \mathbf{A}, t \in \mathbf{T} \]

    Activity is a dimensionless quantity representing the level of operation of the asset. It is multiplied by flow coefficients to give the corresponding commodity flows in the units of that commodity (e.g. PJ of energy for an energy flow).

    Objective Function

    The objective is to minimise the total operating cost of the energy system:

    \[ \text{Minimise } \sum_{a \in \mathbf{A}} \sum_{t \in \mathbf{T}} \mathrm{Activity}_{a, t} \cdot \mathrm{Cost}_{\mathrm{Activity},a,t} \]

    Activity Cost Coefficient \( \mathrm{Cost}_{\mathrm{Activity},a,t} \)

    The cost per unit of activity for asset \( a \) in time slice \( t \) is composed of variable operating costs, flow costs, and levies:

    \[ \mathrm{Cost}_{\mathrm{Activity},a,t} = \mathrm{VariableOpex}_a + \sum_{f \in \mathrm{Flows}_a} |f_{\mathrm{coeff}}| \cdot \left( f_{\mathrm{cost}} + \mathrm{Levy}_{f,t} \right) \]

    • \( \mathrm{VariableOpex} \): The non-fuel variable O&M cost of the process.
    • \( f_{\mathrm{coeff}} \): The flow coefficient for a commodity flow associated with the asset (positive for output/production, negative for input/consumption).
    • \( f_{\mathrm{cost}} \): The cost per unit of commodity flow.
    • \( \mathrm{Levy}_{f,t} \): The regional levy (or subsidy, if negative) per unit of commodity flow in time slice \( t \).

    Constraints

    Asset Activity Limits

    For each asset \( a \) and every time slice selection \( s \):

    \[ \mathrm{Avail}_{\mathrm{LB},a,s} \cdot \Delta_s \cdot \mathrm{Capacity}_a \cdot \mathrm{cap2act}_a \le \sum_{t \in s} \mathrm{Activity}_{a, t} \le \mathrm{Avail}_{\mathrm{UB},a,s} \cdot \Delta_s \cdot \mathrm{Capacity}_a \cdot \mathrm{cap2act}_a \]

    where:

    • \( \mathrm{Capacity}_a \) is the fixed installed capacity of the asset.
    • \( \mathrm{cap2act}_a \) is the conversion factor from capacity to activity units.
    • \( \Delta_s = \sum_{t \in s} \Delta_t \) is the total duration of selection \( s \) as a fraction of the year.
    • \( \mathrm{Avail}_{\mathrm{LB},a,s} \) and \( \mathrm{Avail}_{\mathrm{UB},a,s} \) are the lower and upper availability fractions from process_activity_limits.csv, defaulting to \( 0 \) and \( 1 \) respectively for any selection not explicitly defined.

    Equal Utilisation of Equivalent Assets

    To avoid arbitrarily utilising one asset over another, the dispatch model adds additional constraints to equalise the utilisation of assets with equivalent dispatch properties. Assets are considered equivalent when they are in the same region and have the same variable operating cost, activity limits, and commodity flows. Their capacity, state, and process identity are not considered when determining equivalence.

    For an asset \(a\) in time slice \(t\), utilisation is defined as

    \[ \mathrm{Utilisation}_{a,t} = \frac{\mathrm{Activity}_{a,t}}{\mathrm{Capacity}_a \cdot \mathrm{cap2act}_a} \]

    For every pair of equivalent assets \( x \) and \( y \), and for every time slice \( t \), the optimisation model imposes:

    \[ \mathrm{Utilisation}_{x,t} = \mathrm{Utilisation}_{y,t} \]

    Commodity Balance Constraints

    For each balanced commodity \( c \in \mathbf{C}^{\mathrm{SED}} \cup \mathbf{C}^{\mathrm{SVD}} \) in region \( r \) and time slice selection \( s \) at the commodity’s temporal resolution (time_slice_level), the sum of production across all assets minus consumption must satisfy demands:

    \[ \sum_{a \in \mathbf{A}_r} f_{\mathrm{coeff},a,c} \cdot \sum_{t \in s} \mathrm{Activity}_{a, t} \ge \mathrm{Bound}_{c, r, s} \]

    where:

    • \( f_{\mathrm{coeff},a,c} \) is the flow coefficient of commodity \( c \) for asset \( a \) (positive for outputs, negative for inputs).
    • \( \mathrm{Bound}_{c, r, s} \) is the constraint lower bound:
      • For Service Demand (SVD): \( \mathrm{Demand}_{c, r, s} \)
      • For Supply-Equals-Demand (SED): \( 0 \)

    Commodity Consumption/Production Constraints

    Commodity constraints impose lower and upper limits on the total production or consumption of a commodity in a region over a specified time slice selection:

    \[ L_{c,r,s} \leq \sum_{a \in \mathbf{A}_r^d} |f_{\mathrm{coeff},a,c}| \cdot \sum_{t \in s} \mathrm{Activity}_{a,t} \leq U_{c,r,s} \]

    where:

    • \( d \) is the balance type: production (prod) or consumption (cons).
    • \( \mathbf{A}_r^d \) contains assets in region \( r \) with flows in direction \( d \).
    • \( L_{c,r,s} \) and \( U_{c,r,s} \) are the lower and upper limits.

    These constraints are defined in the optional commodity_constraints.csv file. They can apply to SED and OTH commodities, but not SVD commodities. The feature is experimental and requires please_give_me_broken_results = true in model.toml.

    Shadow Prices

    The dual values (shadow prices) of the commodity balance constraints represent the marginal cost of satisfying an additional unit of demand for that commodity in region \( r \) during selection \( s \). These shadow prices are critical outputs of the dispatch model and are used to seed and guide investment appraisal in subsequent steps.


    Seasonal/Annual Utilisation Penalties

    MUSE2 optionally applies small penalties to the peak capacity required by each asset within a season and across the whole year. These penalties encourage activity to be distributed across time slices within a season and across seasons, respectively. They are particularly useful when commodities are balanced at the seasonal/annual levels and the balance constraint otherwise leaves the intra-seasonal or inter-seasonal production profile undetermined. In real-world terms, this represents a preference to avoid concentrating an asset’s operation into short periods of high utilisation, which may reduce cycling, wear, start-up requirements, or the need to maintain capacity for seasonal peaks.

    For asset \(a\) and time slice \(t\), the capacity required to support its activity is

    \[ \mathrm{RequiredCapacity}_{a,t} = \frac{\mathrm{Activity}_{a,t}} {\mathrm{cap2act}_a \cdot \Delta_t} \]

    For each asset and season, MUSE2 introduces an auxiliary variable \(U_{a,s}\), representing the peak capacity required by the asset during that season. It is constrained by

    \[ U_{a,s} \geq \mathrm{RequiredCapacity}_{a,t} \]

    for every time slice \(t\) in season \(s\). In addition, MUSE2 introduces an auxiliary variable \(U_{a,\mathrm{annual}}\), representing the greatest seasonal peak capacity required by the asset during the year. It is constrained by

    \[ U_{a,\mathrm{annual}} \geq U_{a,s} \]

    for every season \(s\).

    When enabled, the penalties add the following term to the optimisation objective:

    \[ \lambda_{\mathrm{seasonal}} \sum_{a \in \mathbf{A}} \sum_{s \in \mathbf{S}} \Delta_s U_{a,s} + \lambda_{\mathrm{annual}} \sum_{a \in \mathbf{A}} U_{a,\mathrm{annual}} \]

    Here, \(\lambda_{\mathrm{seasonal}}\) and \(\lambda_{\mathrm{annual}}\) are set by the seasonal_utilisation_penalty and annual_utilisation_penalty model parameters, respectively. Setting either parameter to zero disables its corresponding penalty. The seasonal parameter controls how strongly activity is spread within seasons, while the annual parameter controls how strongly it is spread across seasons. Both are weighted objective terms, so their values should be small enough that smoothing dispatch does not outweigh meaningful differences in operating cost (default for both = 1e-6).


    Candidate Dispatch Run

    After the primary dispatch run, MUSE2 performs a second dispatch run that includes candidate assets — technologies not yet commissioned but available for investment — alongside the existing assets. The purpose of this run is to obtain shadow prices for commodities that are not consumed or produced in the primary dispatch solution.

    In the primary dispatch, a commodity balance constraint will only have a non-zero shadow price if the corresponding constraint is binding (i.e. supply exactly meets demand). For a commodity that is neither produced nor consumed by any active asset, the constraint is trivially satisfied and its dual value is zero. This gives no signal to the investment model about the potential value of new capacity for that commodity.

    By including candidate assets in the second run, MUSE2 ensures that every commodity served by at least one candidate process has a balance constraint that can be binding, yielding a meaningful shadow price. To guarantee a non-zero shadow price even when there is no existing demand for the commodity, a small epsilon is added to the lower bound of commodity balance constraints where candidate assets are present:

    \[ \mathrm{Bound}_{c, r, s} = \begin{cases} \max\left( \mathrm{Demand}_{c, r, s}, \epsilon \right) & \text{if } c \in \mathbf{C}^{\mathrm{SVD}} \text{ and candidate assets serve } (c, r, s) \\ \epsilon & \text{if } c \in \mathbf{C}^{\mathrm{SED}} \text{ and candidate assets serve } (c, r, s) \\ \mathrm{Demand}_{c, r, s} & \text{if } c \in \mathbf{C}^{\mathrm{SVD}} \\ 0 & \text{if } c \in \mathbf{C}^{\mathrm{SED}} \end{cases} \]

    where \( \epsilon \) is the commodity_balance_epsilon parameter. The shadow prices from this candidate dispatch run are then used to seed and guide investment appraisal in subsequent steps.


    Diagnosing Infeasible Models

    In practice, a dispatch optimisation run may be infeasible for several reasons, such as insufficient installed asset capacity to meet demand or incompatible commodity production/consumption constraints. When this occurs, MUSE2 performs additional dispatch runs with modified optimisation problems to help identify the cause. The resulting diagnostic information is included in error messages and saved in the dispatch debug files.

    Unmet Demand Diagnostic

    1. Diagnostic Re-Run: MUSE2 reruns the dispatch optimisation with a set of slack variables representing unmet demand, \( \mathrm{UnmetD}_{c, r, t} \ge 0 \), added to the commodity balance constraints: \[ \sum_{a \in \mathbf{A}_r} f_{\mathrm{coeff},a,c} \cdot \sum_{t \in s} \mathrm{Activity}_{a, t} + \sum_{t \in s} \mathrm{UnmetD}_{c, r, t} \ge \mathrm{Bound}_{c, r, s} \]
    2. Objective Penalty: To ensure the solver only leaves demand unmet if it is mathematically impossible to satisfy it, these variables are heavily penalised in the diagnostic objective function using the value_of_lost_load parameter (\( \mathrm{VoLL} \)): \[ \text{Minimise } \sum_{a \in \mathbf{A}} \sum_{t \in \mathbf{T}} \mathrm{Activity}_{a, t} \cdot \mathrm{Cost}_{\mathrm{Activity},a,t} + \mathrm{VoLL} \cdot \sum_{c, r, t} \mathrm{UnmetD}_{c, r, t} \]
    3. Isolating Shortfalls: The addition of \( \mathrm{UnmetD}_{c, r, t} \) guarantees that the LP remains mathematically feasible. When solved, any time slice, region, or commodity with a shortfall will have \( \mathrm{UnmetD}_{c, r, t} > 0 \).
    4. Error Reporting: MUSE2 scans the solution, identifies all balanced markets \( (c, r) \) where unmet demand occurred, outputs detailed diagnostic CSV files, and aborts the simulation with an error identifying the exact out-of-balance markets.

    Commodity Constraints Diagnostic

    If the dispatch optimisation remains infeasible, MUSE2 reruns it with the commodity consumption/production constraints disabled. If this rerun succeeds, the infeasibility is likely caused by one or more constraints defined in commodity_constraints.csv.

    If the rerun remains infeasible, commodity constraints are not identified as the cause. Since commodity constraints are an experimental feature, this diagnosis should be treated as indicative rather than definitive.

    Investment Appraisal

    This section describes the investment and asset retention process applied at each milestone year (MSY). For each commodity market (a commodity–region pair), processed in investment order (see below), agents evaluate every available supply option — existing commissioned assets and new candidate assets from their search space — and select the best option to commit. The committed asset’s production is subtracted from the remaining demand for that market, and the process repeats until demand is met or no feasible options remain. Demands for ServiceDemand (SVD) commodities are fixed from the input data, while demands for SupplyEqualsDemand (SED) commodities accumulate as assets are committed earlier in the investment order.

    Investment Order

    Investment decisions are made sequentially, starting from the most downstream commodity markets and moving upstream. For example, in a model where gas may be used to generate electricity, investment in electricity generation would happen before investment in gas production.

    This ordering ensures that when an upstream market is being invested in, the demand created by already-committed downstream assets is already known.

    Markets are processed in layers: a layer is a set of commodity markets that are independent of one another (i.e. at the same depth in the commodity graph and with no dependencies between them). All markets within a layer are settled before moving to the next layer. After each layer is settled, a single partial system dispatch is run over all assets selected so far. This quantifies the input commodity flows consumed by newly committed assets — for example, a gas generator committed during electricity market investment will consume gas, creating demand that the gas market investment must subsequently meet.

    Only commodities of type ServiceDemand and SupplyEqualsDemand are subject to investment decisions. Other commodity types (e.g. OTH) are excluded.

    Note: the investment order is the reverse of the price calculation order, where prices are computed upstream first.

    Circularities

    When commodity markets form a cycle (e.g. electricity → hydrogen → electricity), the markets in the cycle are resolved in sequence within one pass. After each market in the cycle is visited, a partial system dispatch is run to rebalance demand. Newly committed assets within the cycle are given limited capacity flexibility, controlled by the capacity_margin parameter (defined in model.toml), to absorb small demand shifts caused by later markets in the cycle. If these shifts exceed capacity_margin, the simulation terminates with an error, and the user should increase this parameter.

    Commodity Prices Used in Appraisal

    Investment appraisal uses two distinct price sets, both sourced from the previous MSY’s dispatch (or from the previous ironing-out loop iteration if enabled):

    • Shadow prices \( \lambda_{c,r,t} \): used for activity coefficients in the mini dispatch optimisation step of each appraisal (see Mini Dispatch Optimisation).
    • Market prices \( \pi_{c,r,t} \): used to calculate the investment metric (Cost Index or SNAS) after dispatch.
    • Fallback prices \( \phi_{c,r,t} \): used to incentivise dispatch in the mini dispatch optimisation when shadow prices alone are insufficient (see Mini Dispatch Optimisation). Calculated using the strategy defined by fallback_pricing_strategy in model.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_limit and total_capacity_limit investment constraints (see Investment Constraints).

    Agent portions for each commodity and milestone year are defined in the agent input files.

    Investment Options

    For each commodity market, agents consider two categories of supply option:

    • Existing assets: already-commissioned assets owned by the agent that produce the commodity of interest as their primary output.
    • Candidate assets: processes in the agent’s search space with the commodity of interest as their primary output, available to be newly built.

    Annualised Fixed Cost

    The annualised fixed cost (AFC) per unit of capacity differs between the two categories:

    • Existing assets: AFC comprises only the fixed operations and maintenance (O&M) cost (\( \mathrm{FOM} \)): \[ \mathrm{AFC}_{\mathrm{existing}} = \mathrm{FOM} \]

    • Candidate assets: AFC includes annualised capital expenditure plus fixed O&M: \[ \mathrm{AFC}_{\mathrm{candidate}} = \mathrm{CAPEX} \times \mathrm{CRF} + \mathrm{FOM} \]

      where the Capital Recovery Factor (CRF) annualises the upfront capital cost over the asset’s lifetime \( L \) at discount rate \( d \): \[ \mathrm{CRF} = \frac{d \cdot (1 + d)^L}{(1 + d)^L - 1} \]

      If \( d = 0 \), then \( \mathrm{CRF} = 1/L \).

    Asset Capacities

    Every asset consists of one or more equal-capacity tranches. A single-tranche asset is retained or mothballed as a whole, while the tranches of a multi-tranche asset can be retained or mothballed independently (see Mothballing and Decommissioning).

    • For assets defined in assets.csv, an explicitly supplied num_tranches determines the tranche size. Otherwise, a process tranche_size determines the tranche size. If neither is supplied, the asset consists of one tranche with its full capacity.
    • Assets invested in by MUSE are commissioned one tranche at a time, using the process tranche_size if defined, or a tranche size inferred from demand at the time of investment (see “trial capacity” below).

    Existing assets

    Existing assets (i.e assets that have already been commissioned, whether via assets.csv or by MUSE) are appraised one tranche at a time to decide how many tranches to retain. This allows partial retention — for example, some tranches of a multi-tranche plant may be retained while others are mothballed.

    Candidate assets

    Before a candidate asset for new investment can be appraised, it is assigned a trial capacity. This is the capacity of one tranche, or the amount of capacity that can be installed in a single investment round.

    If a process has a defined tranche_size, the trial capacity is set to that tranche size. Otherwise, the tranche size is inferred from the capacity that would satisfy the total annual demand if the asset operated at its maximum annual rate:

    \[ \mathrm{TrialCapacity} = \frac{\sum_t \mathrm{Demand}_t}{\mathrm{MaxAnnualSupplyPerCapacity}} \times \mathrm{CapacityTrancheFraction} \]

    capacity_tranche_fraction (set in model.toml, must be > 0 and <= 1) controls the size of inferred investment tranches relative to this demand-based capacity. Lower values produce smaller tranches and therefore require more investment rounds, while higher values produce larger tranches.

    Investment constraints

    Processes may be subject to investment constraints defined in process_investment_constraints.csv: an addition_limit constraining new-build capacity, and a total_capacity_limit constraining total installed capacity.

    In multi-agent simulations, both limits are scaled by \( \mathrm{AgentPortion} \), the fraction of the commodity market for which an agent is responsible, to give a limit for each agent. One agent falling short of its own limits does not allow others to exceed theirs. Therefore, the overall limits can only ever be reached if all agents max out their own individual limits.

    Addition limits

    The addition_limit specifies the maximum new capacity that can be built per year, and applies only to candidate (new-build) assets. The installable capacity limit for a given process (\( p \)), agent (\( g \)), region (\( r \)), commodity (\( c \)) and milestone year (\( y \)) is:

    \[ \mathrm{MaxInstallableCapacity}_{p,r,y,g} = \mathrm{AdditionLimit}_{p,r,y} \cdot \Delta_{\mathrm{MSY}} \cdot \mathrm{AgentPortion}_{c,r,y,g} \]

    where \( \Delta_{\mathrm{MSY}} \) is the number of years since the previous milestone year.

    Each selection of a candidate asset tranche counts towards the \(\mathrm{MaxInstallableCapacity} \) of that process. A candidate asset tranche is excluded from consideration if its capacity would exceed the remaining limit.

    Total capacity limits

    The total_capacity_limit specifies a hard upper limit on the total capacity of a process that may exist at any given time. This includes both existing assets which are considered for retention, and candidate assets which are considered for commissioning. The total capacity limit for a given process, agent, region, commodity and milestone year is:

    \[ \mathrm{MaxTotalCapacity}_{p,r,y,g} = \mathrm{TotalCapacityLimit}_{p,r,y} \cdot \mathrm{AgentPortion}_{c,r,y,g} \]

    Each selection of a candidate asset tranche, or retention of an existing asset tranche, counts towards the \(\mathrm{MaxTotalCapacity} \) of their respective process. Any tranche (whether for a candidate asset or an existing asset) is excluded from consideration if it would exceed the remaining limit.

    Mini Dispatch Optimisation

    For each supply option being appraised, a small linear programme (LP) is solved to determine the optimal activity profile given the current remaining demand.

    Activity coefficients

    The mini dispatch optimisation implicitly frames each time slice as a choice: the asset can either produce the commodity of interest, or it can be treated as procured from an alternative source at the fallback price \( \phi_{c,r,t} \). Each unit of activity produces \( f_{c,\mathrm{primary}} \) units of output, displacing that quantity from the fallback source. The optimiser therefore dispatches the asset whenever doing so is cheaper than procuring from elsewhere.

    This is captured by the activity coefficient \( \alpha_t \), which combines two components:

    \[ \mathrm{NetOperatingCost}_t = \mathrm{OperatingCost}(t) - \mathrm{RevenueFromFlows}(\lambda, t) \]

    \[ \mathrm{FallbackCost} = \phi_{c,r,t} \cdot f_{c,\mathrm{primary}} \]

    \[ \alpha_t = \mathrm{FallbackCost} - \mathrm{NetOperatingCost}_t + \varepsilon \]

    where \( \mathrm{RevenueFromFlows} \) is the sum of all commodity flow revenues and costs (positive for outputs, negative for inputs) valued at shadow prices, \( \mathrm{OperatingCost} \) is the variable operating cost plus levies and flow costs, \( f_{c,\mathrm{primary}} \) is the primary output flow coefficient, and \( \varepsilon \) is a small positive constant added to ensure that break-even assets are still dispatched.

    NetOperatingCost is the net cost of running the asset for one unit of activity at shadow prices — negative when the asset is profitable (revenues exceed costs).

    FallbackCost is the cost of procuring one unit of activity’s worth of primary output from an alternative source at the fallback price.

    The asset dispatches when \( \alpha_t > 0 \), i.e. when:

    \[ \mathrm{NetOperatingCost}_t < \mathrm{FallbackCost} \]

    \( \phi_{c,r,t} \) is calculated according to the strategy defined by fallback_pricing_strategy in model.toml.

    Constraints

    • Activity bounds: the sum of activity within each time-slice selection is bounded by the asset’s availability limits multiplied by its capacity.
    • Demand constraints: demand for a commodity is balanced at the commodity’s defined time-slice level (e.g. annual, seasonal, or time-slice). The total supply (activity × flow coefficient) within each balance bucket must not exceed the remaining demand for that bucket.

    Objective

    The optimisation maximises the total net revenue across all time slices, subject to the above constraints:

    \[ \max \sum_t \alpha_t \cdot \mathrm{Activity}_t \]

    Metric Calculation

    After the dispatch LP is solved, an investment metric is calculated from the resulting activity profile using market prices.

    Market costs per time slice

    The market cost \( \mu_t \) is calculated differently depending on the objective type:

    • LCOX: the net cost of operating, excluding revenues from the primary output commodity: \[ \mu_t^{\mathrm{LCOX}} = \mathrm{OperatingCost}(t) - \mathrm{RevenueFromNonPrimaryFlows}(\pi, t) \]

    • NPV: the net cost of operating, including all commodity flows (so negative values represent profit): \[ \mu_t^{\mathrm{NPV}} = \mathrm{OperatingCost}(t) - \mathrm{RevenueFromFlows}(\pi, t) \]

    LCOX metric (objective_type = "lcox")

    The LCOX metric is calculated as the total annualised cost divided by total annual output, using the above defined market costs which exclude the primary output commodity:

    \[ \mathrm{LCOXMetric} = \frac{\mathrm{AFC} \times \mathrm{Capacity} + \sum_t \mathrm{Activity}_t \times \mu_t^{\mathrm{LCOX}}} {\sum_t \mathrm{Activity}_t} \]

    Lower values indicate lower-cost investments.

    NPV metric (objective_type = "npv")

    The NPV metric is based on the Specific Net Annualised Surplus (SNAS). This the net surplus per unit of activity, using market costs that include the primary output commodity:

    \[ \mathrm{SNAS} = \frac{-\left(\mathrm{AFC} \times \mathrm{Capacity} + \sum_t \mathrm{Activity}_t \times \mu_t^{\mathrm{NPV}}\right)}{\sum_t \mathrm{Activity}_t} \]

    Higher values indicate more profitable investments.

    For both metrics, any option with zero total activity after the mini dispatch LP is excluded from consideration, as it cannot contribute to meeting demand. This will generally happen if all time slices have negative activity coefficients, unless the process has lower-bound activity constraints that force activity.

    Asset Selection

    Sorting and tie-breaking

    All feasible options are appraised and ranked by their metric. When two options have approximately equal metrics, the following tie-breaking rules are applied in order:

    1. Existing commissioned assets are preferred over new candidates.
    2. Among existing assets, newer assets (commissioned more recently) are preferred.
    3. 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 (in model.toml), or
    • No feasible options remain. In this case, a warning is logged and the loop ends early. The unmet demand may still be satisfied during the full system dispatch, but is not guaranteed.

    If demand cannot be met at all due to overly restrictive investment constraints, the simulation terminates with an error.

    Note: only production of the primary output commodity is counted against the remaining demand. If a committed asset also produces other commodities as secondary outputs, that side-production does not reduce the demand targets of those other commodity markets. This behaviour may be revised in a future release.

    Mothballing and Decommissioning

    After investment is complete for a given MSY, any previously commissioned assets (or individual tranches making up the asset) that were not selected for retention are mothballed: their mothball year is recorded and they are removed from the active asset pool. They remain available for potential re-selection in future MSYs.

    A mothballed asset that remains unused for mothball_years consecutive years (as defined in model.toml) is decommissioned — permanently removed from the asset pool and excluded from all future investment and dispatch.

    Example: Gas Power Plant

    The following illustrates how LCOX and NPV metrics are calculated for a gas combined-cycle power plant, evaluated across two time slices: \( t_0 \) (peak) and \( t_1 \) (off-peak).

    Parameters

    Asset flows and operating costs

    FlowValueDescription
    Electricity output\( +1.0 \) MWh/MWh activityPrimary output
    Heat output\( +0.5 \) MWh/MWh activityBy-product
    Natural gas input\( -2.5 \) MWh/MWh activityFuel
    \( \mathrm{OperatingCost} \)£5/MWh activityConstant across time slices

    All per-flow costs (\( \mathrm{cost}_{\mathrm{input}} \), \( \mathrm{cost}_{\mathrm{output}} \)) are zero.

    Fixed costs and capacity

    ParameterValue
    AFC£1,000/MW
    Capacity100 MW

    Prices (both shadow and market prices are equal in this example)

    Commodity\( t_0 \) (Peak)\( t_1 \) (Off-peak)
    Electricity£90/MWh£50/MWh
    Heat£25/MWh£15/MWh
    Natural gas£35/MWh£25/MWh

    Mini Dispatch Optimisation (identical for LCOX and NPV)

    Activity coefficients use shadow prices:

    \( t_0 \): \[ \alpha_{t_0} = (1.0 \times 90) + (0.5 \times 25) + (-2.5 \times 35) - 5 = \text{£10/MWh} \]

    \( t_1 \): \[ \alpha_{t_1} = (1.0 \times 50) + (0.5 \times 15) + (-2.5 \times 25) - 5 = \text{£}{-10}\text{/MWh} \]

    The optimiser maximises \( 10 \cdot \mathrm{Activity}{t_0} + (-10) \cdot \mathrm{Activity}{t_1} \), so it prefers to dispatch during \( t_0 \) and minimise activity during \( t_1 \), subject to demand and availability constraints.

    Suppose the optimiser determines \( \mathrm{Activity}{t_0} = 80 \) MWh and \( \mathrm{Activity}{t_1} = 20 \) MWh.

    LCOX Metric

    Market costs (excluding primary output):

    \[ \begin{aligned} \mu_{t_0}^{\mathrm{LCOX}} &= 5 + (2.5 \times 35) - (0.5 \times 25) = \text{£80/MWh} \\ \mu_{t_1}^{\mathrm{LCOX}} &= 5 + (2.5 \times 25) - (0.5 \times 15) = \text{£60/MWh} \end{aligned} \]

    Cost Index: \[ \begin{aligned} \mathrm{CostIndex} &= \frac{(1{,}000 \times 100) + (80 \times 80) + (20 \times 60)}{80 + 20} \\ &= \text{£1,076/MWh} \end{aligned} \]

    NPV Metric

    Market costs (including primary output):

    \[ \begin{aligned} \mu_{t_0}^{\mathrm{NPV}} &= 5 - (1.0 \times 90) - (0.5 \times 25) + (2.5 \times 35) = \text{£}{-10}\text{/MWh} \\ \mu_{t_1}^{\mathrm{NPV}} &= 5 - (1.0 \times 50) - (0.5 \times 15) + (2.5 \times 25) = \text{£10/MWh} \end{aligned} \]

    SNAS: \[ \begin{aligned} \mathrm{SNAS} &= \frac{-\left[(1{,}000 \times 100) + (80 \times (-10)) + (20 \times 10)\right]} {80 + 20} \\ &= \text{£}{-994}\text{/MWh} \end{aligned} \]

    The negative SNAS indicates that at current market prices, this asset does not generate a surplus over its annualised costs. It would still be selected if it has the highest SNAS among all available options.

    Partial System Dispatch

    After each layer of commodity markets is settled during the investment loop, a partial system dispatch is run over all assets selected so far. This is a standard dispatch optimisation solve, with three key modifications described below.

    Market subset

    Only the commodity markets visited so far — i.e. the current market and all those settled in earlier investment rounds — are subject to commodity balance constraints. Markets that are upstream of the current investment frontier are not yet constrained, because no assets have been committed to serve them yet.

    Input prices for upstream commodities

    Because upstream markets are unconstrained, their commodities have no balance constraints and therefore no shadow prices in this dispatch. Without any signal on the cost of upstream inputs, those inputs would appear free to the solver.

    To avoid this, the shadow prices \( \lambda_{c,r,t} \) from the previous MSY (see Commodity Prices Used in Appraisal) are passed as explicit cost penalties on any input flows corresponding to upstream, unconstrained commodities. This ensures the dispatch correctly accounts for the cost of consuming upstream resources, even before those markets have been invested in.

    Capacity flexibility in circularities

    When markets form a cycle, the partial dispatch after each market in the cycle uses flexible capacity variables for all newly committed assets in the cycle. Rather than fixing the capacity of these assets at their committed value, the solver may adjust the number of selected tranches within the bounds:

    \[ \bigl[(1 - \mathrm{capacity_margin}) \cdot \mathrm{NumTranches}_a, \space (1 + \mathrm{capacity_margin}) \cdot \mathrm{NumTranches}_a\bigr] \]

    where \( \mathrm{NumTranches}_a \) is the initially committed number of tranches of asset \( a \). The upper bound is additionally capped by the process’s MaxInstallableCapacity.

    Note: Total capacity limits are not yet accounted for - this is a known bug which will be addressed in future versions.

    Each flexible capacity variable enters the dispatch as an integer variable, with a cost coefficient equal to the asset’s AFC (annualised capital cost plus fixed O&M) multiplied by the tranche size. This flexibility allows the dispatch to absorb small demand shifts caused by subsequent markets in the cycle.

    If the demand shift for a previously settled market in the cycle exceeds what the flexible capacity can accommodate, the simulation terminates with an error. The user should increase the capacity_margin parameter (defined in model.toml) in this case.

    Using the partial dispatch result

    The flow map from the partial dispatch is used to update the net demand seen by each upstream market. For each newly committed asset, its input commodity flows are added to the demand for the corresponding upstream markets, and its primary output flows reduce the remaining demand for the current market. Only primary output flows are counted against demand; secondary outputs do not affect the demand targets of other commodity markets. These updated demands then drive the next investment decisions as the process moves along the investment order, with each market’s committed assets shaping the demand seen by those upstream.

    Commodity Prices

    This section describes how commodity prices are calculated in MUSE2 at the end of each milestone year iteration. The pricing algorithm operates on the results of the dispatch optimisation, translating flows, capacities, and solver dual values (shadow prices) into commodity market prices.

    Pricing Strategies

    Each commodity is configured with a specific pricing_strategy via commodities.csv. The options are:

    • marginal: Prices are set to the marginal cost of the highest-cost active asset producing the commodity.
    • marginal_average: Prices are set to the output-weighted average marginal cost across all active assets.
    • full: Prices are set to the full cost (marginal cost + annual fixed/capital costs) of the highest-cost active asset producing the commodity.
    • shadow: Prices are taken directly from the shadow prices (dual values) of the commodity balance constraints in the dispatch optimisation.
    • scarcity: Prices are set to the shadow price plus the highest activity dual of the assets producing the commodity in that region and time slice.
    • unpriced: No prices are calculated for the commodity. This is mandatory for all OTH (Other)-type commodities.

    Cost Calculations

    Cost-based prices (marginal, marginal_average, full, full_average) are calculated based on process cost parameters, asset capacities and dispatch flows. For assets producing multiple output commodities (e.g. an asset producing both oil and gas), generic activity costs and fixed costs are shared between outputs according to output flow coefficients.

    Generic Activity Cost

    The generic activity cost comprises all operating expenditures, input purchases, and flow costs/levies not associated with specific SED/SVD outputs: \[ \mathrm{GenericActivityCost} = \mathrm{VariableOpex} + \mathrm{InputPurchases} + \sum \mathrm{GenericFlowCosts} \]

    This is shared equally over all SED/SVD outputs in proportion to their output coefficients to compute a generic cost per unit of output flow: \[ \mathrm{GenericCostPerOutput} = \frac{\mathrm{GenericActivityCost}}{\sum_{c \in \mathrm{SED,,SVD}} \mathrm{OutputCoefficient}_c} \]

    Marginal Cost

    The marginal cost of an output commodity \( c \) is the sum of the generic cost per output and any commodity-specific costs (e.g., production levies, flow costs): \[ \mathrm{MarginalCost}_c = \mathrm{GenericCostPerOutput} + \mathrm{SpecificCostPerOutput}_c \]

    Full Cost

    The full cost includes the marginal cost plus annualised capital and fixed operating costs.

    Annualised capital costs are calculated using the Capital Recovery Factor (CRF): \[ \mathrm{AnnualCapitalCostPerCapacity} = \mathrm{TotalCapitalCostPerCapacity} \times \mathrm{CRF} \]

    The annualised fixed cost (AFC) is: \[ \mathrm{AFC} = (\mathrm{AnnualCapitalCostPerCapacity} + \mathrm{AnnualFixedOpex}) \times \mathrm{TotalCapacity} \]

    This is divided by annual activity to get a cost per unit of activity: \[ \mathrm{AnnualFixedCostPerActivity} = \frac{\mathrm{AFC}}{\mathrm{AnnualActivity}} \]

    This is divided again by the sum of SED/SVD output coefficients to get a cost per unit output: \[ \mathrm{AnnualFixedCostPerOutput} = \frac{\mathrm{AnnualFixedCostPerActivity}}{\sum_{c \in \mathrm{SED,,SVD}} \mathrm{OutputCoefficient}_c} \]

    Note: this only works if all SED/SVD output commodities are measured in the same units (e.g. PJ). For this reason, MUSE2 disallows processes that have SED/SVD outputs with differing unit labels. MUSE2 compares the labels but does not interpret or convert them.

    The final full cost of output commodity \( c \) is: \[ \mathrm{FullCost}_c = \mathrm{MarginalCost}_c + \mathrm{AnnualFixedCostPerOutput} \]

    Candidate asset fallback

    For cost-based strategies (marginal, marginal_average, full, full_average), if no active producers exist in the current year, prices are calculated by looking at candidate assets for investment in the following milestone year. Prices are set by the candidate asset with the lowest marginal/full cost, calculated assuming full utilisation (i.e. assuming annual activity equals the maximum annual activity limit).

    In the final milestone year, prices for commodities with no active producer are omitted.

    Time Slice Aggregation

    For commodities defined at coarser time slice levels (e.g. seasonal or annual), prices are calculated by weighting asset costs across time slices by activity to yield a flat price for the season/year. For candidate assets, or assets with zero activity across the selection, upper activity limits are used as weights.

    Price Calculation Order

    Prices are calculated sequentially starting from upstream commodities (e.g. raw fuels) and moving downstream to intermediate energy carriers and final service demands.

    This sequence is the reverse of the investment order. This ensures that the prices of input commodities (from upstream markets) are calculated and known before they are consumed as inputs by downstream processes evaluating marginal or full cost pricing.

    Circularities

    When the energy system includes circular commodity flows (e.g., electricity producing hydrogen, which then generates electricity), the resulting cyclic dependencies are resolved as follows:

    1. Initial Seeding: Cyclic markets are seeded with their shadow prices.
    2. Sequential Evaluation: Commodities in the cycle are evaluated in sequence, starting with those furthest upstream relative to the rest of the system (i.e., furthest from the commodities downstream of the SCC), and working down. Marginal cost calculations use newly updated prices for input commodities already evaluated in the sequence, and fall back to the seeded shadow prices for any not yet evaluated, which may occur due to circular dependencies.

    Example

    The following example demonstrates the calculation of full-cost prices for two commodities using the full pricing strategy.

    GASPRD

    In this scenario, GASPRD (raw gas) is produced by a single GASDRV (dry gas extraction) asset. This asset produces 1 unit of GASPRD per unit of activity, along with 5.113 units of CO₂ emissions. Suppose an annual utilisation of 0.20786471743 units of activity per unit of capacity (based on dispatch results).

    Marginal Cost

    The asset incurs a CO₂ levy (0.04/unit) and a variable operating cost of 2.0:

    \[ \mathrm{MarginalCost}_{\mathrm{GASPRD}} = ( 5.113 \times 0.04) + 2.0 = 2.20452 \]

    Full Cost

    Using a lifetime of 25 years, a discount rate of 10%, annual fixed costs of 0.3 per unit capacity, and a total capital cost of 10 per unit capacity:

    \[ \begin{aligned} \mathrm{CRF} &= 0.11016807219 \\ \mathrm{AnnualFixedCostPerCapacity} &= (10 \times 0.11016807219 + 0.3) = 1.4016807219 \end{aligned} \]

    Accounting for annual utilisation: \[ \mathrm{AnnualFixedCostPerActivity} = \frac{1.4016807219}{0.20786471743} = 6.7432354044 \]

    Accounting for the output coefficient: \[ \mathrm{AnnualFixedCostPerOutput} = \frac{6.7432354044}{1} = 6.7432354044 \]

    The full cost is: \[ \mathrm{FullCost}_{\mathrm{GASPRD}} = 2.20452 + 6.7432354044 = 8.9477554044 \]

    GASNAT

    Following on from the above scenario, GASNAT (processed natural gas) is produced by a single GASPRC (gas processing) asset, which consumes GASPRD as an input fuel. This asset consumes 1.05 units of GASPRD and produces 1 unit of GASNAT per unit of activity, along with 2.5565 units of CO₂ emissions. Suppose an annual utilisation of 0.2094885671 units of activity per unit of capacity (based on dispatch results).

    Marginal Cost

    The asset incurs a CO₂ levy (0.04/unit), a variable operating cost of 0.5 and fuel purchases:

    \[ \begin{aligned} \mathrm{CO2Levy} &= 2.5565 \times 0.04 = 0.10226 \\ \mathrm{InputPurchases} &= 8.9477554044 \times 1.05 = 9.39514317462 \\ \mathrm{MarginalCost}_{\mathrm{GASNAT}} &= 0.10226 + 0.5 + 9.39514317462 = 9.99740317462 \end{aligned} \]

    Full Cost

    Using the same lifetime and discount rate, along with annual fixed costs of 0.21 per unit capacity, and a total capital cost of 7 per unit capacity:

    \[ \begin{aligned} \mathrm{CRF} &= 0.11016807219 \\ \mathrm{AnnualFixedCostPerCapacity} &= (7 \times 0.11016807219 + 0.21) = 0.98117650533 \end{aligned} \]

    Accounting for annual utilisation: \[ \mathrm{AnnualFixedCostPerActivity} = \frac{0.98117650533}{0.2094885671} = 4.6836756722 \]

    Accounting for the output coefficient: \[ \mathrm{AnnualFixedCostPerOutput} = \frac{4.6836756722}{1} = 4.6836756722 \]

    The full cost is: \[ \mathrm{FullCost}_{\mathrm{GASNAT}} = 9.99740317462 + 4.6836756722 = 14.6810788468 \]

    This example illustrates the sequential nature of price calculation: the GASPRD price is calculated first and then used as an input cost when calculating the GASNAT price.

    Model diagrams

    This document contains diagrams showing the algorithm used by MUSE2. It is likely to contain errors and omissions and will change as the code is developed. It is principally aimed at MUSE developers.

    Functions are described with the following terms:

    • Inputs: immutable input arguments; values not modified by function
    • Outputs: values returned from function
    • Modifies: mutable input arguments; values modified by function

    Overview of MUSE2
    Figure 1: Overview of MUSE2 algorithm

    Dispatch optimisation
    Figure 2: Overview of dispatch optimisation

    Glossary

    Activity: The quantity representing how much an Asset operates during a given period. Its flow coefficients determine the resulting input and output flows of each Commodity.

    Agent: A decision-making entity in the system. An Agent is responsible for serving a user-specified portion of a Commodity demand or Service Demand. Agents invest in and operate Assets to serve demands and produce Commodities.

    Agent Objective: One or more objectives that an Agent considers when deciding which Process to invest in. Objectives can be economic, environmental, or others. An Agent can use multiple objectives when applying a Decision Rule.

    Agent Share: The portion of a Commodity demand for which an Agent is responsible. Agent shares determine how total demand is divided between agents and can also scale investment limits. For example, if total electricity demand is 100 PJ and an Agent has a 25% Agent Share, that Agent is responsible for 25 PJ of demand.

    Annualised Capital Cost: The annual cost equivalent of an upfront Capital Cost, calculated using the Capital Recovery Factor for the relevant Asset Lifetime and Discount Rate. For example, an Asset with a £1 million Capital Cost, a 10-year Lifetime, and a 5% Discount Rate has an annualised Capital Cost of approximately £129,500 per year.

    Asset: A specific instance of a Process, representing installed or candidate capacity. It carries the Process’s technical and economic properties, and adds instance-specific information such as Capacity, ownership, location, commissioning Year, and operating state.

    Asset Commissioning: The point at which an Asset becomes active and available for Dispatch. An Asset is commissioned according to its commission Year, either from user input or following an investment decision.

    Asset Decommissioning: The permanent removal of an Asset from the active model. An Asset can be decommissioned when it reaches the end of its allowed Lifetime or after remaining mothballed for the configured period.

    Asset Mothballing: The temporary withdrawal of an Asset or some of its tranches from the active pool when it is not selected for retention. A mothballed Asset is not dispatched, but may be reactivated in a later investment period; it is permanently decommissioned if it remains mothballed for longer than a configured number of years.

    Asset Tranche: A discrete portion of an Asset’s Capacity. An Asset can consist of multiple Asset Tranches of equal size, which can be retained, mothballed, or decommissioned independently.

    Availability: A Process property specifying the maximum, minimum, or fixed percentage of maximum Activity that its Assets can deliver over a period. It can be specified for a single Time Slice, a Season, or a Year. For example, an Asset with 100 MW of Capacity and 50% annual Availability can provide at most 50 MW over the year. This is a limit, not a measure of how much the Asset is actually used; that is its Utilisation.

    Base Year: The starting Year of a model run. The Base Year is typically calibrated to known data, including Asset stock and Commodity consumption/production.

    Calibration: The act of ensuring that the model represents the system being modelled in a historical base year. A set of Assets must exist in the Base Year sufficient to serve Base Year demands.

    Candidate Asset: A hypothetical Asset representing a Process that is available for an Agent to invest in, but has not yet been selected or commissioned. Its Capacity is assigned for investment appraisal and can be adjusted during the investment process.

    Capacity: The installed size of an Asset. Together with its Capacity-to-activity factor, it determines the maximum Activity the Asset can perform.

    Capacity-to-activity factor: The factor that converts an Asset’s Capacity into its maximum annual Activity.

    Capital Cost: The upfront cost per unit of Capacity established in an Asset. It does not vary directly with short-term Activity.

    Capital Recovery Factor: The factor used to convert an upfront Capital Cost into an annualised cost over an Asset’s Lifetime at a given Discount Rate. It represents the proportion of the upfront capital cost that is paid each year to spread that cost over the asset’s lifetime. The factor increases with the discount rate and decreases as the lifetime increases. When the discount rate is zero, it is the reciprocal of the lifetime.

    Circular Commodity Dependency: A dependency cycle in which the production or consumption of one Commodity depends, directly or indirectly, on another Commodity that eventually depends on the first. For example, electricity can be used to produce hydrogen, which is then used to produce electricity.

    Commodity: A substance (e.g. CO₂) or form of energy (e.g. electricity) that can be produced and/or consumed by Processes in the model. A Service Demand is a type of Commodity that is defined at the end point of the system.

    Commodity Levy: Represents a tax, levy or other external cost on a Commodity. Levies can be applied to all Commodity production (sum of output of all Assets for that Commodity), net production (sum of output and input for all Assets), or all consumption (sum of input for all Assets). It can also be negative, indicating an incentive on Commodity production/consumption/net.

    Commodity Market: The market for a particular Commodity in a Region. MUSE2 evaluates each market during investment and calculates prices for the Commodity based on the operation of relevant Assets and the applicable Pricing Strategy.

    Decision Rule: The rule via which an Agent uses the Agent Objective(s) to decide between Process options to invest in.

    Demand Distribution: The allocation of annual Service Demand across Time Slices within a Year. It specifies how much of the annual demand occurs in each Time Slice, while the total across the year remains equal to the annual demand.

    Discount Rate: The proportion used to convert a Process’s future costs and benefits into present-value terms. For example, a discount rate of 0.05 means that £100 received in one year’s time is valued at approximately £95.24 today (100 / (1 + 0.05)).

    Dispatch: The way in which Assets are operated to serve demand. Within the limits imposed by Availability, Commodity balances, and other user-defined constraints, MUSE2 effectively dispatches Assets in Merit Order: cheaper Assets are used first, up to their available capacity or until demand is met. The resulting operation is calculated by a least-cost linear programme.

    End Year: The final year in the model Time Horizon.

    Equivalent Annual Cost (EAC): The constant annual cost equivalent of an Asset’s upfront capital cost and operating costs over its Lifetime, accounting for the Discount Rate.

    Fixed Operating Cost: The annual operating cost charged per unit of an Asset’s Capacity. Unlike Variable Operating Cost, it does not vary directly with Activity.

    Flow Coefficient: The factor that converts one unit of Activity into the corresponding input or output flow of a Commodity. Positive coefficients represent production and negative coefficients represent consumption. For example, a flow coefficient of 2 means that one unit of Activity produces two units of that Commodity.

    Full Cost: The cost of producing a unit of output including Marginal Cost, annualised Capital Cost, and Fixed Operating Cost. For example, a Process with low fuel costs but high Capital Cost may have a low Marginal Cost but a higher Full Cost.

    Input Commodity: A Commodity that flows into a Process. A Process may have multiple input commodities.

    Investment Appraisal: The process of evaluating existing and Candidate Assets to determine which investments an Agent should make in a particular Commodity Market and Milestone Year. Appraisal considers the Agent Objective, expected costs, commodity prices, and the asset’s expected operation.

    Investment Order: The sequence in which Commodity markets are considered for investment. MUSE2 generally processes downstream markets before upstream markets, so that demand for input Commodities created by committed Assets is known when the upstream markets are evaluated. For example, investment in electricity generation is considered before investment in gas production when gas is used to generate electricity.

    Ironing-out Loop: The repeated sequence of Agent investment and Dispatch used to resolve price instability introduced by new capacity commitments. The loop stops when time-slice-weighted market prices converge within the configured tolerance or the maximum number of iterations is reached.

    Levelised Cost of X (LCOX): The cost per unit of output Commodity X from an Asset, calculated by dividing its discounted lifetime costs by its discounted lifetime output under a specified Discount Rate.

    Lifetime: The expected operational duration of a Process, measured in years.

    Marginal Cost: The cost of producing one additional unit of an output Commodity from an Asset. It combines the Process’s Variable Operating Cost with activity-dependent costs associated with the Asset’s commodity flows, such as input prices and levies. It excludes Capital Cost and Fixed Operating Cost.

    Merit Order: A method of operating Assets where the cheapest is dispatched first, followed by the next most expensive, etc, until demand is served.

    Milestone Year: A Year in the Time Horizon where model results are recorded. A model can have multiple Milestone Years. For example, with a 2025 Base Year and End Year 2100, a user might choose to record outputs in 5-year steps.

    Net Present Value (NPV): The value of future costs and benefits expressed in the present, calculated by discounting them using a specified Discount Rate.

    OTH (other)-type commodity: A Commodity that is not supply-demand balanced in the model. Includes side products and emissions such as CO₂, or any raw materials that are not output by modelled Processes.

    Output Commodity: A Commodity that flows out of a Process. A Process may have multiple output commodities.

    Partial Equilibrium: An equilibrium in which only the user-defined Commodities and markets are balanced. MUSE2 does not attempt to equilibrate the whole economy.

    Pricing Strategy: The method used to calculate a Commodity’s price. This includes Marginal Cost pricing, Full Cost pricing, and Shadow Price pricing.

    Process: A blueprint of an available Process that converts Input Commodities to Output Commodities. Processes have economic attributes of Capital Cost, Fixed Operating Cost per unit Capacity, Variable Operating Cost per unit Activity, and risk Discount Rate. They have physical attributes of quantity and type of Input Commodities and Output Commodities (which implicitly specify efficiency), Availability limits (by Time Slice, Season, and/or Year), and Lifetime (years). When a Process is selected by an Agent for investment an instance of it called an Asset is created.

    Region: A geographical area represented in the model. Regions contain the Assets, Processes, Agents, demands, and Commodity markets that apply in that area, and define the boundaries within which trade and other regional interactions are modelled.

    Search Space: The set of Processes an Agent is allowed to consider for investment, defined for a particular Commodity, Region, and Year. It determines which Candidate Assets are available during investment appraisal.

    Season: A subdivision of a Year that groups related Time Slices. For example, summer, winter, or other.

    Sector: Models are often broken down into sectors, each of which is associated with specific Service Demands or specific Commodity production. For example, the residential sector, the power sector, etc.

    Service Demand (SVD): A Commodity consumed at the boundary of the modelled system to represent demand for a service. For example, tonne-kilometres of road freight or PJ of useful heat demand.

    Shadow Price: The value of relaxing a Commodity balance constraint by one unit, as calculated by the Dispatch optimisation. For example, a high Shadow Price indicates that one additional unit of a Commodity would substantially improve the optimisation objective because supply is scarce.

    Supply Equals Demand (SED): An SED Commodity is a type of Commodity that is both consumed and produced by Assets in the system. In fully resolved systems, supply of these Commodities is constrained to be equal to or greater than demand.

    Time Horizon: The overall period modelled. For example, 2025–2100.

    Time Slice: A discrete operational sub-period within a Year, identified by a Season and a time-of-day category. For example, a Time Slice might represent winter-day or summer-night. Time Slices are the within-year periods over which Activity, demand, and flows are modelled. A Time Slice can be grouped with other Time Slices in a Time-slice Selection to represent a Season, an entire Year, or another temporal grouping.

    Time-slice Level: The temporal resolution at which a Commodity’s balance constraints are applied. MUSE2 supports annual, seasonal, and day-night levels, from a whole year to a particular time of day. For example, an annual level balances total supply and demand across the year, whereas a day-night level requires the balance to be met separately in each time-of-day group.

    Time-slice Selection: A group of model Time Slices treated as one period when applying an Asset’s Activity or Availability constraints, or balancing a Commodity at its Time-slice Level. For example, a selection can group all winter Time Slices so that a seasonal Availability limit applies to their combined Activity.

    Tranche Size: The capacity of one tranche of an Asset. Every Asset has a Tranche Size, either defined when the Asset is created or inferred when an investment is made. Tranche Size defines the granularity at which retention, mothballing, and decommissioning decisions are made. For example, a 100 MW Asset with a 25 MW Tranche Size has four tranches, which can be retained or mothballed independently.

    Utilisation: The proportion of an Asset’s Capacity that is actually used to produce its Commodities, ranging from 0 to 1 inclusive. Can be measured at Time Slice, Season, or Year level. For example, an Asset operating at 25 MW from 100 MW of available Capacity has 0.25 Utilisation. Availability describes what the Asset could provide; Utilisation describes what it actually provides.

    Variable Operating Cost: A Process parameter specifying the generic operating cost per unit of Activity. It excludes costs associated with commodity flows, such as input prices and levies.

    Year: A calendar year used to identify a Base Year, Milestone Year, or End Year and to specify when an Asset is commissioned or decommissioned.

    Developer Guide

    This is a guide for those who wish to contribute to the MUSE2 project or make local changes to the code.

    The API documentation is available here.

    Setting up your development environment

    To develop MUSE2 locally you will need the following components:

    • Git
    • Rust development tools
    • C++ development tools (needed for bindings to the HiGHS solver)

    Optional requirements:

    • Just (recommended)
    • uv (required for file format documentation)

    You can either install the necessary developer tools locally on your machine manually (a bare metal installation) or use the provided development container. Bare metal installation should generally be preferred if you plan on doing substantial development, as you should get better performance locally (and therefore shorter compile times), as well as making it easier to interact with your local filesystem. Development containers provide a mechanism for installing all the tools you will need (into a Docker container) and are generally used either via Visual Studio Code running locally or GitHub Codespaces, which run on GitHub-provided virtual machines running remotely.

    We provide a justfile for some common developer tasks.

    Installing tools

    Option 1: Bare metal installation

    Installing the Rust toolchain

    We recommend that developers use rustup to install the Rust toolchain. Follow the instructions on the rustup website.

    This project defines the required version of the Rust toolchain in rust-toolchain.toml. The first time you attempt to run cargo (see Building and developing MUSE2), this toolchain should be downloaded and installed automatically by rustup.

    Installing C++ tools for HiGHS

    The highs-sys crate requires a C++ compiler and CMake to be installed on your system. These may be installed already, but if you encounter errors during the build process for highs-sys (e.g. “Unable to find libclang”), you should follow the instructions in the highs-sys repository.

    Option 2: Developing inside a container

    If you wish to use the development container locally with Visual Studio Code, you should first install the Dev Containers extension. Note you will also need Docker to be installed locally. For more information, see the documentation.

    You can use GitHub Codespaces to develop directly from your web browser. For more information, please see GitHub’s guide. When you first create your Codespace, you will be asked whether you wish to install the recommended extensions and you should choose “yes”.

    Downloading the MUSE2 source code

    Unless you are developing in a GitHub Codespace (see above), you will need to download the MUSE2 source code to your local machine before you can develop it. Like many projects, MUSE2 is stored in a Git repository hosted on GitHub. Many IDEs, such as Visual Studio Code, provide an interface to clone Git repositories, but you can also use the Git command-line tool (see installation instructions), like so:

    git clone https://github.com/EnergySystemsModellingLab/MUSE2
    

    The source code will now be available in a folder named MUSE2.

    Pre-Commit hooks

    We use prek to automatically run a number of hooks for this repository when a new Git commit is made. prek is a Rust rewrite of the pre-commit tool (which you can use instead, if you prefer).

    prek can be installed via cargo:

    cargo install --locked prek
    

    You can enable prek for this repository with:

    prek install
    

    Thereafter, a series of checks should be run every time you commit with Git. In addition, the pre-commit hooks are also run as part of the CI pipeline.

    Note: you may get errors due to the clippy hook failing. In this case, you may be able to automatically fix them by running cargo clipfix (which we have defined as an alias in .cargo/config.toml).

    Building and developing MUSE2

    Background: The Rust programming language

    MUSE2 is written using Rust, which is a high-performance, compiled language. If you have used other compiled languages, such as C++, many of the concepts will be familiar. One feature which distinguishes it from other languages like C and C++, however, is that undefined behaviour, such as memory safety bugs, are not possible, provided you keep to the safe subset of the language. This means you can have the performance benefits of using a low-level language like C, with the safety guarantees and much of the convenience of a higher-level language like Python.

    There is much high quality documentation available for learning Rust, but it is probably best to start with The Rust Programming Language book, which is freely available online.

    Building MUSE2

    To build the project, run:

    cargo build
    

    Note that if you just want to build-test the project (i.e. check for errors and warnings) without building an executable, you can use the cargo check command, which is much faster.

    To run MUSE2 with the “simple” example, you can run:

    cargo run run examples/simple
    

    (Note the two runs. The first is for cargo and the second is passed as an argument to the built muse2 program.)

    Tests can be run with:

    cargo test
    

    More information is available in the official cargo book.

    Checking test coverage

    We use Codecov to check whether pull requests introduce code without tests.

    To check coverage locally (i.e. to make sure newly written code has tests), we recommend using cargo-llvm-cov.

    It can be installed with:

    cargo install cargo-llvm-cov
    

    Once installed, you can use it like so:

    cargo llvm-cov --open
    

    Alternatively, you can use Just:

    just coverage --open
    

    This will generate a report in HTML format showing which lines are not currently covered by tests and open it in your default browser.

    Working with the benchmarking framework

    MUSE2 includes a benchmarking framework to measure the performance of various components using the Criterion.rs library. To run all the available benchmarks, use the following command:

    cargo bench --features bench
    

    The --features bench flag disables some aspects of the code that are incompatible with the benchmarking, like singleton objects that can be only instantiated once and throw an error otherwise. See rust documentation for more information about features.

    You can also run specific benchmarks by specifying their names:

    cargo bench --features bench --bench <benchmark_name>
    

    For example, to run the examples benchmark, you would use:

    cargo bench --features bench --bench examples
    

    If run more than once, the benchmarking framework will compare the results of the current run with the previous run and report the differences. This is useful for checking whether a change has introduced a performance regression.

    Criterion.rs automatically generates an HTML report that can be found in the target/criterion/report directory. You can open it with your web browser to see the results and navigate through the different benchmarks.

    Adding new benchmarks

    To add a new benchmark, i.e. a collection of related performance tests, create a new file in the benches directory with the name of your benchmark. For example, to add a benchmark for the foo functionality, you would create a file called benches/foo.rs. Then foo will need to be added to cargo.toml under the [bench] section, like so:

    [[bench]]
    name = "foo"
    harness = false
    

    The harness = false line is necessary because Criterion.rs provides its own test harness, which is incompatible with the default Rust test harness. Within the new benchmark file, you can then add your performance tests using the Criterion.rs API. Check benches/examples.rs for an example of how to do this. More information about writing benchmarks can be found in the Criterion.rs documentation.

    Architecture and coding style

    This document describes the overall architecture of the MUSE2 project, as well as the coding style used for Rust. This document is intended to help new contributors get started with the codebase more quickly rather than to be a set of prescriptions.

    User interface and deployment

    MUSE2 is a command-line utility, designed to be built and run as a single, standalone executable file (called muse2 on Unix platforms and muse2.exe on Windows). The normal way in which users will obtain MUSE2 is by downloading a pre-built binary for their platform (currently, Linux, Windows and macOS ARM binaries are provided). The user should not need to install any additional dependencies in order to use MUSE2. All assets (e.g. example models; see below) should be bundled into this executable file.

    For information on the command-line interface, see the documentation. The clap crate is used to provide this interface.

    Using external crates

    With Rust it is easy to add external dependencies (called “crates”) from crates.io. It is preferable to make use of external crates for additional required functionality rather than reimplementing this by hand.

    Some crates which we make heavy use of are:

    • anyhow - Ergonomic error handling in Rust (see below)
    • float-cmp - For approximate comparison of floating-point types
    • indexmap - Provides hash table and set types which preserve insertion order
    • itertools - Provides extra features for iterator types (consider using to simplify code using iterators)

    Error handling

    One of the distinctive features of the Rust programming language is its approach to error handling. There are two usual ways to signal that an error has occurred: either by returning it from a function like any other value (usually via the Result enum) or by “panicking”, which usually results in termination of the program1. See the official docs for more information.

    In the case of MUSE2, we use the anyhow crate for error handling, which provides some useful helpers for passing error messages up the call stack and attaching additional context. For any user-facing error (e.g. caused by a malformed input file), you should return an error wrapped in a Result, so that it can be logged. For purely developer-facing errors, such as functions called with bad arguments, you should instead panic!. Note that users should NEVER be able to trigger a panic in MUSE2 and any case where this happens should be treated as a bug. We use the human-panic crate to direct users to report the bug if a panic occurs in a release build.

    Logging

    MUSE2 makes use of the log crate, which provides a number of macros for logging at different levels (e.g. info!, warn! etc.). This crate just provides the helper macros and does not provide a logging backend. For the backend, we use fern, which deals with formatting and (in the case of simulation runs) writing to log files. A few simple commands print to the console directly without using the logging framework (e.g. muse2 example list), but for code run as part of model validation and simulation runs, you should use the log macros rather than printing to the console directly. Note that you should generally not use the error! macro directly, but should instead pass these errors up the call stack via anyhow (see above).

    Note that the log level is configurable at runtime; see user guide for details.

    Writing tests

    This repository includes tests for many aspects of MUSE2’s functionality (both unit tests and integration tests). These can be run with cargo test. All tests must pass for submitted code; this is enforced via a GitHub Actions workflow. Newly added code should include tests, wherever feasible. Code coverage is tracked with Codecov. There is good documentation on how to write tests in Rust in the Rust book.

    You may wish to use test fixtures for your unit tests. While Rust’s built-in testing framework does not support test fixtures directly, the rstest crate, which is already included as a dependency for MUSE2, provides this functionality. You should prefer adding test fixtures over copy-pasting the same data structures between different tests. For common data structures (e.g. commodities, assets etc.), there are fixtures for these already provided in fixture.rs. You should use these where possible rather than creating new fixtures.

    As the fixtures needed for many tests are potentially complicated, there are also helper macros for testing that validation/running fails/succeeds for modified versions of example models. For more information, see fixture.rs. As this method is likely to lead to terser code compared to using fixtures, it should be preferred for new tests.

    We check whether each of the bundled example models (see below) runs successfully to completion as regression tests. We also check that the output has not substantially changed (i.e. that the numbers in the outputs are within a tolerance), which helps catch accidental changes to the behaviour of MUSE2. Of course, often we do want to change the behaviour of MUSE2 as the model evolves. In this case, you can regenerate test data by running:

    just regenerate_test_data
    

    If you do so, please verify that the changes to the output files are at least roughly what was expected, before you commit these updated test files.

    Note that if you only need to regenerate the data for some of the models, you can specify this with additional arguments, e.g.:

    just regenerate_test_data simple muse1_default
    

    This avoids regenerating data for other models unnecessarily, which can result in negligible differences in floating-point values in the output files.

    If the model is a patched example, then you need to pass the --patch flag, e.g.:

    just regenerate_test_data --patch simple_tranche_size
    

    Example models

    MUSE2 provides a number of example models, to showcase its functionality and help users get started with creating their own. These models live in the examples folder of the repository and are also bundled with the MUSE2 executable (see user guide for more detail).

    As these are intended as both a kind of documentation and templates, they should ideally be kept as simple as possible.

    If you add a new example model, please also add a regression test to tests/regression.rs.

    Unit types

    We define a number of types for units used commonly in MUSE2, such as activity, capacity etc. These are simple wrappers around f64s, but provide additional type safety, ensuring that the wrong types are not passed to functions, for example. Certain arithmetic operations involving types are also defined: for example, if you divide a variable of type Money by one of type Activity, you get a result of type MoneyPerActivity.

    These types should be used in preference to plain f64s, where possible. For variables which are unitless, there is a Dimensionless type to make this explicit.

    For more information, consult the documentation for the units module.

    Input and output files

    Input and output files for MUSE2 are either in CSV or TOML format. Users provide model definitions via a number of input files and the simulation results are written to files in an output folder. The code responsible for reading and validating input files and writing output files is in the input and output modules, respectively.

    The file formats for MUSE2 input and output files are described in the documentation. This documentation is generated from schema files (JSON schemas for TOML files and table schemas for CSV files); these schemas MUST be updated when the file format changes (i.e. when a field is added/removed/changed). (For details of how to generate this documentation locally, see Developing the documentation.)

    When it comes to reading input files, we try to perform as much validation as possible within the input layer, so that we can provide users with detailed error messages, rather than waiting until errors in the input data become apparent in the simulation run (or, worse, are missed altogether!). A certain amount of type safety is given by the serde crate (e.g. checking that fields which should be integers are really integers), but we also carry out many other validation checks (e.g. checking that there is a producer for every required commodity in the first year).


    1. Technically, panics can be caught, but this is unusual and we don’t do it in MUSE2 ↩

    Applying custom HiGHS options

    As part of development, you may wish to directly set custom options for the HiGHS solver. Note that while some of these options will not affect results of simulations (e.g. to enable console logging for HiGHS), as we cannot guarantee this for all options, in order to use this feature, you have to set please_give_me_broken_results = true in your model.toml file.

    You can change any of the options exposed by the HiGHS solver; for more information, see the HiGHS documentation.

    You can set options to be applied to all optimisations, just dispatch or just appraisal.

    Here is an example:

    please_give_me_broken_results = true
    milestone_years = [2020, 2030, 2040]
    
    # These options are applied to all optimisations
    [highs.global_options]
    # These two options are required to be enabled to log to console
    log_to_console = true
    output_flag = true
    
    # These ones are just applied to dispatch
    [highs.dispatch_options]
    # Increase to higher than default
    primal_feasibility_tolerance = 10e-6
    
    # These ones are just applied to appraisal
    [highs.appraisal_options]
    optimality_tolerance = 10e-6
    

    Developing the documentation

    You can make changes to the documentation without installing any additional tools, however, you may wish to do so in order to view your changes locally.

    For developing the main documentation, you will need mdBook installed (see below) and for the file format documentation, you will need uv. We also recommend that you install Just (the instructions below assume it is present).

    If you have all the necessary tools installed and wish to build all of the documentation, you can run:

    just build-docs
    

    However, you will likely want to build only part of the documentation, which you can do by passing another argument to this command, e.g.:

    just build-docs file-format
    

    To see the possible recipes, run:

    just --list build-docs
    

    The book

    We use mdBook for generating technical documentation.

    If you are developing the documentation locally, you may want to check that your changes render correctly (especially if you are working with equations).

    To do this, you first need to install mdBook:

    cargo install mdbook
    

    You can then view the documentation in your browser like so:

    mdbook serve -o
    

    Documenting file formats

    Documentation for file formats of different input and output files used by MUSE2 is automatically generated from schemas, stored in the schemas/ folder.

    Files are either in TOML or CSV format. For TOML files, we use JSON schemas and for CSV files we use table schemas (a similar format).

    The documentation is generated with the docs/file_formats/generate_docs.py script. To generate all file format docs, run:

    just build-docs file-format
    

    To generate just one kind of docs (e.g. for input files only), run:

    just build-docs file-format input
    

    Generating the command_line_help.md file

    This file is created automatically. In order to examine the output locally, run:

    just build-docs cli-help
    

    The file will be written to docs/command_line_help.md.

    Making a release

    This page describes the steps for making a new MUSE2 release.

    Before you start

    Before preparing the release, manually run the Test with MUSE2 workflow on the main branch of the muse2_data_analysis repository and check that all jobs pass. This workflow tests the analysis repository’s main branch against MUSE2’s main branch. If it does not pass, identify and fix the errors there before proceeding with the release.

    Choose a version number

    MUSE2 version numbers have the form 2.X.Y. The first digit is fixed at 2 to distinguish MUSE2 releases from the Python-based MUSE_OS. The second digit represents major changes, while the third digit represents minor changes. This is a looser versioning scheme than Semantic Versioning, where “major” and “minor” have stricter definitions.

    Finalise the release notes

    All releases should have comprehensive release notes explaining the main user-facing changes since the last release.

    Developers are encouraged to add a note to docs/release_notes/upcoming.md for each user-facing change as it is made. This file should therefore provide a good starting point, but it should not be assumed to be complete or up to date. Review the full PR history since the last release and make sure that all user-facing changes introduced since then are included. Do not include changes that are only relevant to developers, or fixes for bugs that were introduced after the previous release.

    Structure the notes in a user-friendly way, highlighting the main user-facing changes at the top and listing minor changes below. The release notes for v2.1.0 provide a useful example for a major release.

    Once the release notes in docs/release_notes/upcoming.md are finalised:

    1. Copy docs/release_notes/upcoming.md to docs/release_notes/v2.X.Y.md.

    2. Update the heading in the new file to include the version and the date on which the release will be published, for example:

      # Release notes for MUSE2 v2.3.4 (January 3, 2027)
      
    3. Link the new release notes in docs/SUMMARY.md and docs/release_notes/README.md

    4. Remove the release-note content from docs/release_notes/upcoming.md, while retaining the headings so it is ready to track changes for the next release.

    Update version metadata

    Update all release metadata together:

    • Set the package version in Cargo.toml.
    • Set version and date-released in CITATION.cff.

    Publish the release

    Once the preparation changes have been merged to main and CI has passed, create and publish a GitHub Release with the exact v2.X.Y tag, such as v2.3.4.

    The published release triggers the workflows that build and attach binaries, publish the crate to crates.io, and deploy the documentation. Tags must use the v2.X.Y format so that the documentation tooling recognises them as MUSE2 releases.

    After publishing, manually check the GitHub releases page, the MUSE2 crates.io page, and the published documentation to confirm that the release is available in each location.

    Create a release for the analysis repository

    After publishing the MUSE2 release, create and publish a release in the muse2_data_analysis repository with the same name as the MUSE2 release, such as v2.3.4. The analysis repository derives its package version from the Git tag, so this tag must exactly match the MUSE2 release tag.

    API documentation

    This is a placeholder file for the MUSE2 API documentation. This content will be replaced with the output of rustdoc when run by GitHub Actions.

    To view the latest version of the API documentation online, see here.

    Release notes

    This is the list of changes for each release of MUSE2.

    To download the latest release for your platform, please see our GitHub releases page.

    To see the list of changes for the current, in-progress version of MUSE2, see here.

    Release notes for MUSE2 v2.0.0 (October 14, 2025)

    After nearly 18 months of development and 464 PRs, we are pleased to announce the first release of the all-new MUSE2 🎉! A big thank you to everyone who contributed to this amazing milestone.

    MUSE2 is a tool written in Rust for running simulations of energy systems, which is planned to be the eventual successor to the original MUSE. Note that this is a completely new project written from scratch, with both the code and the model underlying it redesigned from the ground up to be simpler and faster. By design, we do not wish to add every feature that MUSE1 has, but we hope to eventually support all the common use cases for those who run these types of models.

    As there are some important features still missing from MUSE2 (e.g. #794, #509), we do not recommend that you attempt to use it for research at this stage. However, please do give it a spin! If there are any features missing that you feel are important, let us know by opening an issue.

    It is rather unusual to have the first release of a piece of software be 2.0.0. The reason for this is to avoid confusion with the previous MUSE program. Be aware that this project does not follow semantic versioning.

    To get started, you can download MUSE2 for your platform below. For more information, please consult the documentation.

    Release notes for MUSE2 v2.1.0 (March 31, 2026)

    This release represents over 5 months of development since v2.0.0, adding a number of exciting new features, as well as some small improvements, bug fixes, and underlying code changes designed to improve performance and maintainability.

    Please be aware that there are some breaking changes in this release, which may require you to update the input files of any existing models. We have tried to outline these in the “Breaking changes” section below, but please do get in touch if you have any issues adapting models to the new version.

    Some of the new features in this release are still experimental, and will continue to be worked on for future releases. In the meantime, any feedback on these features is very welcome.

    As always, you can keep track of upcoming changes for the next release in the upcoming release notes.

    New features

    Asset mothballing

    Assets can now remain unused for a certain number of years before being decommissioned, via the global mothball_years parameter defined in model.toml (#1022). The default value for this parameter is 0 years, meaning that assets are decommissioned immediately when they are no longer used, which is equivalent to the behaviour in previous versions of MUSE2.

    Process investment constraints

    Users can now add yearly limits on the maximum amount of investment in each process via the new process_investment_constraints.csv file (#1020 and #1096).

    Circular commodity dependencies

    Users can now have circular dependencies between commodities, such as a hydrogen power plant that itself requires electricity (#999 and #1004). This is currently an experimental feature that may not work in all cases - feedback is appreciated. There are also known limitations such as incompatibility with the new commodity pricing strategies (see below, #1168), which we plan to address in future releases. The main new parameter to be aware of is the capacity_margin parameter defined in model.toml.

    Divisible assets

    Assets can now be made divisible to represent many individual sub-assets, such as a fleet of gas boilers, which can be partially decommissioned (#1030). This is implemented via the new optional unit_size column in processes.csv. For example, a unit_size of 10 means that the smallest granular unit of asset capacity represents 10 capacity units (e.g. a single gas boiler, or a defined aggregate of gas boilers, depending on the desired resolution). Omitting this column or leaving values empty means that assets are indivisible (i.e. must be decommissioned in one go), which is the default behaviour and the same as in previous versions of MUSE2.

    It is worth noting that the current implementation incurs a potentially significant performance cost when setting unit_size very small compared to overall capacity, although we plan to address this in future releases.

    New commodity pricing strategies

    There are four new pricing strategies for commodities: full, marginal, full_average and marginal_average (#1021 and #1205). Unlike previously, where the pricing strategy was set at the model level, users can now specify the pricing strategy for each commodity individually in the commodities.csv file via the new optional pricing_strategy column. The new strategies are in addition to the existing shadow and scarcity strategies, as well as an unpriced option for oth type commodities that should not be priced.

    Other new features

    • Users can now optionally specify an explicit decommission year for an asset in assets.csv (#966)
    • Allow users to specify ranges of years in input files (#1017)
    • You can now set both upper and lower bounds in process_availabilities.csv (#1018)
    • Availability limits can now be provided at multiple levels for a process (#1018)
    • Allow for adding both a prod and cons levy to a commodity in commodity_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.

    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 net was wrong for inputs (#969)
    • Fix --overwrite option for save-graphs command (#1001)

    Release notes for MUSE2 v2.2.0 (September 3, 2026)

    This release is the culmination of a huge effort from many contributors over the past 5 months. Whilst there are only a handful of entirely new features (total capacity limits, commodity constraints), the most significant changes in this release come from improvements and refinements to existing features. Almost every aspect of the main algorithm, from investments to dispatch to mothballing and decommissioning has been modified or fundamentally changed, in many cases to address issues in the model formulation brought to light through rigorous testing and assessment of the software in action. In many ways this is not just a software update, but a research update to what is very much a live and evolving project.

    Along with changes to the core code, this release also brings significant improvements to the user-facing documentation, bringing this up to date with the current state of the code. In particular, see the Model Description and Glossary.

    Please be aware that there are some breaking changes in this release, which may require you to update the input files of any existing models. We have tried to outline these in the “Breaking changes” section below, but please do get in touch if you have any issues adapting models to the new version.

    Some of the new features in this release are still experimental, and will continue to be worked on for future releases. In the meantime, any feedback on these features is very welcome.

    As always, you can keep track of upcoming changes for the next release in the upcoming release notes.

    New features

    Investment appraisal

    This release fundamentally changes the investment appraisal approaches for both the LCOX and NPV objectives. Appraisal now uses a more consistent approach across objectives: all objectives now appraise a pre-determined capacity (see “tranching” section below), optimise dispatch of that capacity over remaining demand, and calculate an objective-specific metric based on this dispatch (#1319). For LCOX, this replaces the previous approach that used dynamic capacity variables and the Value of Lost Load parameter, which had a tendency to inflate capacities. The NPV formulation has also changed to use SNAS (Specific Net Annualised Surplus) rather than Profitability Index, which is more robust to assets with low/zero fixed costs (#1379). More details can be found in the updated investment documentation.

    A further change is to introduce a fallback cost to the activity coefficient calculation, set by the fallback_pricing_strategy parameter in model.toml (#1420). This reframes the optimisation problem to consider the alternative cost of choosing not to invest in a process, which encourages investment in cases where there would otherwise be no incentive to do so. This is also described in more detail in the investment documentation.

    Users may notice changes in asset selection, better matching of capacities to demands, and fewer model failures triggered by an absence of viable investment options.

    In addition, investment appraisal is now parallelised over candidate assets, which may improve performance, particularly for large models run on multi-core machines (#1466).

    Divisible assets and tranching

    v2.1.0 introduced the concept of asset groups as a proxy for modelling asset divisibility, i.e. the idea that overall asset capacity may be made up of multiple discrete components, or tranches, (e.g. a group of wind turbines in a wind farm) which can be mothballed and decommissioned independently. This approach resulted in a (potentially large) number of Assets (with distinct asset_ids) linked together by a common group_id.

    v2.2.0 does away with asset groups, and instead expresses the capacity of each individual Asset as a collection of tranches, which work in unison in dispatch, but can be independently mothballed and decommissioned (#1464, #1483).

    Tranches are not reported individually in the output results. Rather, results files report aggregate flows and capacities for the overall Asset, represented by a single asset_id (#1455). Since capacities for each Asset can now change over time, capacities are moved from the fixed asset definition in the assets.csv output file over to a new asset_capacities.csv file, which reports capacity for each asset in each milestone year (#1302).

    The size of these tranches can be set on a per-process basis, either to a fixed, predefined capacity, or determined dynamically at the time of investment based on demand. Investment appraisal (see above) works on one tranche at a time. There are several new/renamed parameters to be aware of (#1518):

    • tranche_size: Replaces unit_size in processes.csv. An optional parameter to set a fixed tranche size for a process.
    • capacity_tranche_fraction: A factor, defined in model.toml, used to infer tranche sizes at the time of investment for assets without a defined tranche_size (#1391, #1395). See investment documentation for more details.
    • num_tranches: The number of equal-capacity tranches making up an overall asset capacity. Reported with overall capacities in asset_capacities.csv, and can be used to split existing assets into tranches in the assets.csv input file (#1480).

    Since all assets can now be divided into tranches and partially decommissioned (not just those with a defined tranche_size), this should allow capacity to be matched more closely to demand and stale capacity to be removed more readily.

    Total capacity limits

    Users can now add hard upper limits on the total amount of capacity that can exist for a certain process at any one time, via the total_capacity_limit parameter in process_investment_constraints.csv (#1417, #1496). See investment documentation for more details.

    Please note that these limits may be broken in models with circular commodity dependencies - this is a known limitation (#1498), as circularities remain an experimental feature.

    Commodity constraints (experimental)

    Users can now add constraints on the total production and/or consumption of SED and OTH commodities in a given region in a given year/season/time slice, via the commodity_constraints.csv file (#1487, #1513). See the dispatch documentation for more details.

    Please note that this feature is currently experimental, and expected to cause conflicts in otherwise working models, as these constraints are currently only considered in full-system dispatch, and not factored in to the investment algorithm. Future releases will hopefully address this, but for now, in order to use this feature, you must enable the please_give_me_broken_results option in model.toml.

    Dispatch optimisation

    Additional constraints and small objective penalties have been added to the dispatch optimisation problem to push solutions towards a more intentional and consistent solution. The penalties are deliberately kept small so that they primarily distinguish between otherwise equivalent solutions while having negligible influence on the primary cost objective. Two such additions, “Equal Utilisation of Equivalent Assets” (#1477) and “Seasonal/Annual Utilisation Penalties” (#1478), are explained in more detail in the dispatch documentation.

    As a result of these changes, users should notice more sensible and interpretable asset utilisation patterns, away from arbitrary concentration of activity on certain assets and time slices, now tending to spread utilisation evenly across equally good assets and time slices. In many cases, this will also have knock-on consequences for commodity prices. Additionally, whereas results were previously sensitive to differences between platforms and operating systems, as small floating point differences could push the solver to a different convergence point, these additions constrain the optimisation problem in a way that should substantially reduce the sensitivity of results to these platform-dependent differences. That said, identical results cannot yet be guaranteed across all platforms in all cases - this is an area of ongoing investigation.

    Other new features

    • Users can now optionally pass custom options to the HiGHS solver (#1276)
    • Input files are now copied to the output folder for reference (#1282, #1303)
    • Circular commodity markets can now use cost-based pricing strategies (#1322)
    • Added a configurable commodity_balance_epsilon parameter in model.toml to help candidate assets dispatch during investment appraisal (#1387, #1445)
    • The execution time of key simulation steps (e.g. dispatch, agent investment) is now logged (#1421)

    Breaking changes

    These changes may require users to update the input files for existing models developed using previous versions of MUSE2.

    • Changed the default pricing_strategy for SED/SVD commodities from “shadow” to “full_average” (#1281)
    • The group_id column has been removed from all output files (#1286, #1302, #1455)
    • The agent_search_space.csv input file has been renamed to agent_search_spaces.csv for consistency (#1293)
    • Due to an internal change, some options in CSV files are now case sensitive (e.g. you must put lcox rather than LCOX). The error message you see if it does not match will now be more informative, however. (#1376)
    • Stricter input validation: capacity_to_activity must now be greater than zero. Previously, capacity_to_activity = 0 was permitted (#1391)
    • The capacity_limit_factor parameter in model.toml has been removed. Please use capacity_tranche_fraction (see above) instead (#1518)
    • The unit_size parameter in processes.csv has been renamed to tranche_size (#1518)

    Bug fixes

    • Fix misleading warning message for assets decommissioned before simulation start (#1259)
    • Fix parsing and validation of agent search space file (#1293)
    • Fix a bug in asset selection for circular commodity markets when agents in the same cycle have different commodity shares (#1323)
    • Prevent commodities of type OTH from being specified in agent_commodity_portions.csv (#1327)
    • Use shadow prices rather than market prices for appraisal optimisations and dispatch runs during investment (#1349)
    • Asset selection can now continue even if it runs out of options, giving dispatch a chance to still meet demand, rather than failing immediately (#1365)
    • Calculate commodity prices using the dispatch solution without candidate assets, so that they are consistent with the flows reported in commodity_flows.csv (#1389)
    • Fix price calculations to prioritise active assets over inactive ones when determining commodity prices (#1392)
    • The debug_unmet_demand.csv output file is no longer created unless it is needed (#1405)
    • Mothballed assets are now excluded from the main dispatch run (#1458)
    • Disallow negative values for capital_cost, fixed_operating_cost and variable_operating_cost in process_parameters.csv (#1485)

    Upcoming release of MUSE2

    New features

    Breaking changes

    Bug fixes