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:

    missing_commodity
    muse1_default
    simple
    two_outputs
    two_regions
    

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

    muse2 example info simple
    

    You can run examples like so:

    muse2 example run simple
    

    Building and running your own model

    🚧 Note that this section is under construction! 🚧

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

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

    Creating a new model from an example

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

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

    muse2 example extract simple new_model
    

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

    Running this model

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

    muse2 run new_model
    

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

    The first few lines should look something like:

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

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

    Next steps

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

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

    Visualising commodity graphs

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

    Modifying the program settings

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

    muse2 settings edit
    

    There are also some more commands for working with the settings file; for details, run: muse2 settings help.

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

    Setting the log level

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

    The possible options are:

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

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

    For more information, please consult the fern documentation.

    Example models

    This document describes the various example models included with MUSE2.

    To list the available examples, run:

    muse2 example list
    

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

    muse2 example info simple
    

    circularity example

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

    Note: this model is currently experimental and only included for development purposes. For now, this uses the “shadow” pricing strategy for ELCTRI and H2YPRD, as the default “full_average” strategy is not supported for commodities involved in circularities. This also uses a slightly wider capacity_margin compared to the default value, as this is required for this model to find a solution.

    missing_commodity example

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

    muse1_default example

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

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

    simple example

    A minimal example for a single region.

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

    two_outputs example

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

    two_regions example

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

    Command-Line Help for muse2

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

    Command Overview:

    muse2

    A tool for running simulations of energy systems

    Usage: muse2 [COMMAND]

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

    Subcommands:
    • run — Run a simulation 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 must be in the current working directory.

    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
    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_limit_factorScales the capacity assigned to candidate assetsOptional. Defaults to 0.05.

    Candidate assets for investment are assigned a characteristic capacity scale based on annual demand. This parameter specifies the fraction of that scale appraised each investment round.

    Must be >0 and <=1. Lower values produce smaller investment increments, while higher values produce larger increments.
    fallback_pricing_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.
    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_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.

    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.

    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
    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 slices(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 slices(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).
    unit_sizeCapacity of the units in which an asset for this process will be divided into when commissioned, if any.If present, must be >0. Optional (defaults to None). Assets with a defined unit size are divided into n = ceil(C / U) equal units, where C is overall capacity and U is unit_size (i.e. rounding up the number of units, which may result in a total capacity greater than C, if C is not an exact multiple of U). Note that divisible assets are appraised one unit at a time, so if this number is much smaller than the typical size of an asset, this will potentially lead to many rounds of appraisal, with an adverse effect on performance.

    process_flows.csv

    The commodity flows for each process.

    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_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_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 slices(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).

    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 evenly between all agents using their proportion 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_limitThe hard upper limit on the amount agents can invest in the processThis is currently unused.

    Output Files

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

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

    Metadata: metadata.toml

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

    Top-level table

    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 yearFor divisible assets, this is the total capacity across all units.
    num_unitsThe number of active units making up the asset in this yearFor non-divisible assets, this is always blank.

    commodity_flows.csv

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

    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.

    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 framework is designed to operate sequentially across several distinct milestone years (MSY). For each MSY, it endogenously determines asset decommissioning (both scheduled and economically-driven) and guides new capacity investments. The overarching objective is to simulate agent decision-making to serve commodity (and service) demand.

    A fundamental premise for the investment appraisal is that prices for balanced commodities (Supply Equals Demand: SED) from the previous milestone year (\( \pi_{prevMSY} \)) are considered reliable for economic evaluations. Service demand commodity (SVD) prices from \( MSY_{prev} \) may or may not be reliable (as defined by the user), guiding the choice of appraisal method for assets producing them. It is designed as a recursive dynamic model with imperfect foresight.

    The workflow is structured as follows:

    1. Dispatch is executed for a calibrated base year. Dispatch is executed using the formulation shown in Dispatch Optimisation Formulation. All existing assets are included, and all candidate assets for the \( MSY_{next} \) are included with capacities set to zero. This ensures that all commodity shadow prices are generated for use in the \( MSY_{next} \). \( VoLL \) load shedding variables should be zero after completion, and if they are non-zero then throw an error as the model is not properly calibrated.

    2. Time-travel loop: Move to the next milestone year (\( MSY \)).

      1. Decommission assets that reached their end of life. This establishes the existing asset fleet for the current \( MSY \) by accounting for initial retirements. \( ExistingCapacity \) is the set of existing assets and their capacities available after EOL decommissioning.

      2. Determine SVD demand profiles and run investment appraisal tools for them. SVD demand profiles for the \( MSY \) are determined from user input data. The investment appraisal tools are applied to determine portfolios of existing and new assets to meet demand for each SVD commodity. Prices of input commodities are known from the final dispatch of the previous milestone year. This finalises \( NewCapacity \) and \( AssetChosen \) for each SVD.

      3. Build System Layer-by-Layer loop: Completes the investment pass for the milestone year, progressively adding commodities layer by layer. This step determines new asset capacities (\( NewCapacity \)) and selects existing assets that remain competitive (\( AssetChosen \)). Other existing assets are decommissioned if they are not utilised for \( MothballYears \) years (a user-input asset parameter). This inner loop continues until no different SED commodities (that have not been processed using the investment appraisal tools) are added as commodities of interest for this iteration.

        1. Determine the commodities of interest for investment. In the base year this is all service demand (SVD) commodities. After the base year, the commodities of interest are determined dynamically; they are the set of commodities that are consumed by the assets invested/chosen in the last iteration (layer) of this loop.

        2. Dispatch to determine commodity of interest demand profile. Dispatch is executed using the formulation shown in Dispatch Optimisation Formulation, but only including system elements downstream of the commodities of interest. Commodity prices for upstream/unknown inputs/outputs from assets serving the commodities of interest and assets downstream of the commodities of interest with unknown commodity prices (if any) are assumed to take on shadow prices from the previous MSY. Care must be taken to avoid any double-counting of prices and e.g. commodity levies. Demand profiles for commodities of interest are recorded (\( D[c,r,t] \)).

        3. Run investment appraisal tools for each commodity of interest. The investment appraisal tools are applied to determine portfolios of existing and new assets to meet demand for each commodity of interest. It is necessary to consider the complete demand profile of each commodity of interest, as even where demand can be served with existing assets in the MSY without new investment, economic decommissioning is still possible.

        4. Finalise new capacity and retained assets that produce the commodities of interest. \( NewCapacity \) and \( AssetChosen \) are finalised for the layer.

        5. Check if there are SED commodities that the investment appraisal tools have not been run for. If yes, move to next layer of the layering loop at step 2(c). If no, layering loop ends, break and continue at step 2(d).

      4. Ironing-out loop, with iteration limit \( k_{max} \). For each \( k \):

        1. Execute dispatch as per Dispatch Optimisation Formulation with the complete system, with all candidate assets for the \( MSY_{next} \) included with capacities set to zero to generate prices for the \( MSY_{next} \).

        2. Check if load-weighted average prices for any SED commodity has changed (or changed more than a tolerance) since the last loop (also, possibly check if the 95th percentile of price has changed more than a tolerance). If yes, continue at 2(d)iii. If no, this MSY is complete, and if further MSY exist continue time-travel loop from step 2, or if no further MSY exist then go to step 3.

        3. If \( k = k_{max} \) break with a warning telling the user that this loop did not converge, identifying out-of-balance commodities. If further MSY exist continue time-travel loop from step 2, or if no further MSY exist then go to step 3.

        4. Re-run investment appraisal tools for the assets and commodities that are contributing to the price instability.

    3. Outer loop ends when no further milestone years exist.

    Dispatch Optimisation Formulation

    This dispatch optimisation model calculates the least-cost operation of the energy system for a given configuration of assets and capacities, subject to demands and constraints. It is the core engine used for each dispatch run referenced in the overall MUSE2 workflow. A key general assumption is that SVD commodities represent final demands only and are not consumed as inputs by any asset.

    General Sets

    These define the fundamental categories used to define the energy system.

    • \( \mathbf{R} \): Set of Regions (indexed by \( r \)). Represents distinct geographical or modelling areas.

    • \( \mathbf{T} \): Set of Time Slices (indexed by \( t \)). Discrete operational periods within a year.

    • \( \mathbf{H} \): Set of Seasons (indexed by \( h \)). Collections of time slices.

    • \( \mathbf{A} \): Set of All Assets (indexed by \( a \)). All existing and candidate production, consumption, or conversion technologies.

    • \( \mathbf{A}^{flex} \subseteq \mathbf{A} \): Subset of Flexible Assets (variable input/output ratios).

    • \( \mathbf{A}^{std} = \mathbf{A} \setminus \mathbf{A}^{flex} \): Subset of Standard Assets (fixed input/output coefficients).

    • \( \mathbf{C} \): Set of Commodities (indexed by \( c \)). All energy carriers, materials, or tracked flows. Partitioned into:

      • \( \mathbf{C}^{\mathrm{SVD}} \): Supply-Driven Commodities (final demands; not consumed by assets).

      • \( \mathbf{C}^{\mathrm{SED}} \): Supply-Equals-Demand Commodities (intermediate system flows like grid electricity).

      • \( \mathbf{C}^{\mathrm{OTH}} \): Other Tracked Flows (e.g., losses, raw emissions).

    • \( \mathbf{C}^{VoLL} \subseteq \mathbf{C}^{\mathrm{SVD}} \cup \mathbf{C}^{\mathrm{SED}} \): Subset of commodities where unserved demand is modelled with a penalty.

    • \( \mathbf{P} \): Set of External Pools/Markets (indexed by \( p \)).

    • \( \mathbf{S} \): Set of Scopes (indexed by \( s \)). Sets of \( (r,t) \) pairs for policy application.

    A. Core Model (Standard Assets: \( a \in \mathbf{A}^{std} \))

    Purpose: Defines the operation of assets with fixed, predefined input-output relationships.

    A.1. Parameters (for \( a \in \mathbf{A}^{std} \) or global)

    • \( duration[t] \): Duration of time slice \( t \) as a fraction of the year (\( \in (0,1] \)). Represents the portion of the year covered by this slice.

    • \( season\_ slice[h,t] \): Binary indicator; \( 1 \) if time slice \( t \) is in season \( h \), \( 0 \) otherwise. Facilitates seasonal aggregation.

    • \( balance\_ level[c,r] \): Defines the temporal resolution (‘timeslice’, ‘seasonal’, ‘annual’) at which the supply-demand balance for commodity \( c \) in region \( r \) must be enforced.

    • \( demand[r,c] \): Total annual exogenously specified demand (\( \ge 0 \)) for commodity \( c \in \mathbf{C}^{\mathrm{SVD}} \) in region \( r \). This is the final demand to be met.

    • \( timeslice\_ share[c,t] \): Fraction (\( \in [0,1] \)) of the annual \( demand[r,c] \) for \( c \in \mathbf{C}^{\mathrm{SVD}} \) that occurs during time slice \( t \). (\( \sum_{t}timeslice\_ share[c,t]=1 \)). Defines the demand profile.

    • \( capacity[a,r] \): Installed operational capacity (\( \ge 0 \)) of asset \( a \) in region \( r \) (e.g., MW for power plants). This value is an input to each dispatch run.

    • \( cap2act[a] \): Conversion factor (\( >0 \)) from asset capacity units to activity units, ensuring consistency between capacity (e.g., MW) and activity (e.g., MWh produced in a slice) considering \( duration[t] \).

    • \( avail_{UB}[a,r,t], avail_{LB}[a,r,t], avail_{EQ}[a,r,t] \): Availability factors (\( \in [0,1] \)) for asset \( a \) in time slice \( t \). \( UB \) is maximum availability, \( LB \) is minimum operational level, \( EQ \) specifies exact operation if required.

    • \( cost_{var}[a,r,t] \): Variable operating cost (\( \ge 0 \)) per unit of activity for asset \( a \) (e.g., non-fuel O&M).

    • \( input_{coeff}[a,c] \): Units (\( \ge 0 \)) of commodity \( c \in (\mathbf{C}^{\mathrm{SED}} \cup \mathbf{C}^{\mathrm{OTH}}) \) consumed by asset \( a \) per unit of its activity. (By assumption, \( input_{coeff}[a,c]=0 \) if \( c \in \mathbf{C}^{\mathrm{SVD}} \)).

    • \( output_{coeff}[a,c] \): Units (\( \ge 0 \)) of commodity \( c \in \mathbf{C} \) produced by asset \( a \) per unit of its activity.

    • \( cost_{input}[a,c] \): Specific cost (\( \ge 0 \)) per unit of input commodity \( c \) consumed by asset \( a \). Useful if \( c \) attracts a levy/incentive \( only \) if it is consumed by this type of asset.

    • \( cost_{output}[a,c] \): Specific cost (if positive) or revenue (if negative) per unit of output commodity \( c \) produced by asset \( a \). Useful if levy/incentive applies \( only \) when the commodity is produced by this type of asset.

    • \( VoLL[c,r] \): Value of Lost Load. A very high penalty cost applied per unit of unserved demand for \( c \in \mathbf{C}^{VoLL} \) in region \( r \).

    A.2. Decision Variables

    These are the quantities the dispatch optimisation model determines.

    • \( act[a,r,t]\ge0 \): Activity level of asset \( a \) in region \( r \) during time slice \( t \). This is the primary operational decision for each asset.

    • \( UnmetD[c,r,t]\ge0 \): Unserved demand for commodity \( c \in \mathbf{C}^{VoLL} \) in region \( r \) during time slice \( t \). This variable allows the model to find a solution even if capacity is insufficient.

    A.3. Objective Contribution (for standard assets \( a \in \mathbf{A}^{std} \))

    This term represents the sum of operational costs associated with standard assets, forming a component of the overall system cost that the model seeks to minimise.

    \[ \sum_{a\in \mathbf{A}^{std}}\sum_{r,t} act[a,r,t] \Biggl( cost_{var}[a,r,t] + \sum_{c \notin \mathbf{C}^{\mathrm{SVD}}} cost_{input}[a,c]\,input_{coeff}[a,c] + \sum_{c \in \mathbf{C}} cost_{output}[a,c]\,output_{coeff}[a,c] \Biggr) \]

    A.4. Constraints (Capacity & Availability for standard assets \( a \in \mathbf{A}^{std} \))

    These constraints ensure that each standard asset’s operation respects its physical capacity and time-varying availability limits. For all \( a \in \mathbf{A}^{std}, r, t \):

    • Asset activity \( act[a,r,t] \) is constrained by its available capacity, considering its minimum operational level (lower bound, LB) and maximum availability (upper bound, UB):

      \[ \begin{aligned} capacity[a,r]\,cap2act[a]\,avail_{LB}[a,t]\,duration[t] &\le act[a,r,t] \\ act[a,r,t] &\le capacity[a,r]\,cap2act[a]\,avail_{UB}[a,t]\,duration[t] \end{aligned} \]

    • If an exact operational level is mandated (e.g., for some renewables based on forecast, or fixed generation profiles for specific assets):

      \[ act[a,r,t] = capacity[a,r]\,cap2act[a]\,avail_{EQ}[a,t]\,duration[t] \]

    B. Flexible Assets (\( a \in \mathbf{A}^{flex} \))

    Purpose: This section defines the operational characteristics of flexible assets, which can adjust their input consumption mix and/or output product shares, governed by an overall process efficiency. The generic activity variable \( act[a,r,t] \) (linked to the asset’s physical \( capacity[a,r] \)) is connected to the scale of this flexible conversion process via a reference output parameter. It is assumed that SVD commodities cannot be efficiency-constrained or auxiliary inputs to these assets.

    B.1. Asset-Specific Sets (Defined for each flexible asset \( a \in \mathbf{A}^{flex} \))

    These sets categorize the commodities involved in the flexible asset’s operation.

    • \( \mathbf{C}^{eff\_ in}_a \subseteq (\mathbf{C}^{\mathrm{SED}} \cup \mathbf{C}^{\mathrm{OTH}}) \): Set of input commodities for asset \( a \) whose combined energy or mass content is subject to the main process efficiency \( \eta[a] \).

    • \( \mathbf{C}^{eff\_ out}_a \subseteq \mathbf{C} \): Set of main output commodities from asset \( a \) whose combined energy or mass content is determined by \( \eta[a] \) and the processed inputs.

    • \( \mathbf{C}^{aux\_ in}_a \subseteq (\mathbf{C}^{\mathrm{SED}} \cup \mathbf{C}^{\mathrm{OTH}}) \): Set of auxiliary input commodities for asset \( a \) (e.g., water, catalysts) whose consumption is typically proportional to the main activity \( act[a,r,t] \) but which are not part of the primary energy/mass balance for the efficiency calculation.

    • \( \mathbf{C}^{aux\_ out}_a \subseteq \mathbf{C} \): Set of auxiliary output commodities for asset \( a \) (e.g., specific emissions, waste streams) whose production is typically proportional to \( act[a,r,t] \) but which are not counted in the efficiency calculation for the primary products.

    B.2. Parameters (for \( a \in \mathbf{A}^{flex} \))

    These parameters define the technical and economic behavior of flexible assets.

    • \( \eta[a] \): The overall process efficiency (\( \in (0,1] \)) of flexible asset \( a \), relating total common units of outputs in \( \mathbf{C}^{eff\_out}_a \) to inputs in \( \mathbf{C}^{eff\_in}_a \).

    • \( factor_{CU}[c] \): A commodity-specific factor to convert its native physical units (e.g., tonnes, m³) to a common unit (e.g., MWh of energy content, or tonnes of mass if it’s a mass balance) used for consistent efficiency and input/output share calculations.

    • \( RefEffOutPerAct[a] \): Reference Total Efficiency-constrained Output per unit of Activity. This crucial parameter defines the total quantity of efficiency-constrained outputs (summed in common units using \( factor_{CU}[c] \)) that are produced when asset \( a \) operates at one unit of its generic activity level \( act[a,r,t] \).

    • \( minInputShare[a,c] \) (for \( c \in \mathbf{C}^{eff\_ in}_a \)): Minimum fractional share of input commodity \( c \) (in common units) relative to the total efficiency-constrained input (in common units).

    • \( maxInputShare[a,c] \) (for \( c \in \mathbf{C}^{eff\_ in}_a \)): Maximum fractional share for input \( c \).

    • \( minOutputShare[a,c] \) (for \( c \in \mathbf{C}^{eff\_ out}_a \)): Minimum fractional share of output commodity \( c \) (in common units) relative to the total efficiency-constrained output.

    • \( maxOutputShare[a,c] \) (for \( c \in \mathbf{C}^{eff\_ out}_a \)): Maximum fractional share for output \( c \).

    • \( coeff_{aux\_ in}[a,c] \) (for \( c \in \mathbf{C}^{aux\_ in}_a \)): Quantity of auxiliary input \( c \) consumed per unit of \( act[a,r,t] \).

    • \( coeff_{aux\_ out}[a,c] \) (for \( c \in \mathbf{C}^{aux\_ out}_a \)): Quantity of auxiliary output \( c \) produced per unit of \( act[a,r,t] \).

    B.3. Decision Variables (for \( a \in \mathbf{A}^{flex} \))

    These variables represent the operational choices for flexible assets.

    • \( act[a,r,t]\ge0 \): Generic activity level of flexible asset \( a \) (linked to its \( capacity[a,r] \)).

    • \( InputSpec[a,c,r,t]\ge0 \quad \forall c \in \mathbf{C}^{eff\_ in}_a \): Actual amount of efficiency-constrained input commodity \( c \) consumed by asset \( a \) in region \( r \), time \( t \) (in its native physical units).

    • \( OutputSpec[a,c,r,t]\ge0 \quad \forall c \in \mathbf{C}^{eff\_ out}_a \): Actual amount of efficiency-constrained output commodity \( c \) produced by asset \( a \) in region \( r \), time \( t \) (in its native physical units).

    B.4. Objective Contribution (for \( a \in \mathbf{A}^{flex} \))

    The cost contribution from flexible assets includes costs related to their generic activity, specific costs for efficiency-constrained inputs and outputs (if defined separately from system commodity values), and costs/revenues for auxiliary inputs and outputs.

    \[ \begin{aligned} &act[a,r,t] \cdot cost_{var}[a,r,t] \\ &+ \sum_{c \in \mathbf{C}^{eff\_ in}_a} InputSpec[a,c,r,t] \cdot cost_{input}[a,c] \\ &+ \sum_{c \in \mathbf{C}^{eff\_ out}_a} OutputSpec[a,c,r,t] \cdot cost_{output}[a,c] \\ &+ \sum_{c \in \mathbf{C}^{aux\_ in}_a} (act[a,r,t] \cdot coeff_{aux\_ in}[a,c]) \cdot cost_{input}[a,c] \\ &+ \sum_{c \in \mathbf{C}^{aux\_ out}_a} (act[a,r,t] \cdot coeff_{aux\_ out}[a,c]) \cdot cost_{output}[a,c] \\ \end{aligned} \]

    B.5. Constraints (for \( a \in \mathbf{A}^{flex}, r \in \mathbf{R}, t \in \mathbf{T} \))

    These rules govern the internal operation and conversion process of flexible assets. Let \( ActualTotalEffOutputCU[a,r,t] = RefEffOutPerAct[a] \cdot act[a,r,t] \) (This is the total efficiency-constrained output in common units). Let \( ActualTotalEffInputCU[a,r,t] = (RefEffOutPerAct[a] / \eta[a]) \cdot act[a,r,t] \) (This is the total efficiency-constrained input in common units, derived from the output and efficiency).

    • Capacity & Availability: Standard capacity and availability constraints (as in A.4) apply to the generic activity variable \( act[a,r,t] \) of the flexible asset.

    • Total Efficiency-Constrained Input Definition: The sum of all specific efficiency-constrained inputs, when converted to common units by \( factor_{CU}[c] \), must equal the total efficiency-constrained input derived from \( act[a,r,t] \) and the asset’s efficiency:

      \[ \sum_{c \in \mathbf{C}^{eff\_ in}_a} InputSpec[a,c,r,t] \cdot factor_{CU}[c] = ActualTotalEffInputCU[a,r,t] \]

    • Total Efficiency-Constrained Output Definition: Similarly, the sum of all specific main outputs (in common units) must equal the total defined by \( act[a,r,t] \) and the reference output parameter:

      \[ \sum_{c \in \mathbf{C}^{eff\_out}_a} OutputSpec[a,c,r,t] \cdot factor_{CU}[c] = ActualTotalEffOutputCU[a,r,t] \]

    • Input Share Constraints (for each \( c \in \mathbf{C}^{eff\_in}_a \)): Ensure that each individual efficiency-constrained input (in common units) stays within its defined minimum (\( minInputShare[a,c] \)) and maximum (\( maxInputShare[a,c] \)) fractional share of the \( ActualTotalEffInputCU[a,r,t] \).

      \[ \begin{aligned} InputSpec[a,c,r,t] \cdot factor_{CU}[c] &\ge minInputShare[a,c] \cdot ActualTotalEffInputCU[a,r,t] \\ InputSpec[a,c,r,t] \cdot factor_{CU}[c] &\le maxInputShare[a,c] \cdot ActualTotalEffInputCU[a,r,t] \end{aligned} \]

    • Output Share Constraints (for each \( c \in \mathbf{C}^{eff\_out}_a \)): Ensure that each individual efficiency-constrained output (in common units) stays within its defined minimum (\( minOutputShare[a,c] \)) and maximum (\( maxOutputShare[a,c] \)) fractional share of the \( ActualTotalEffOutputCU[a,r,t] \).

      \[ \begin{aligned} OutputSpec[a,c,r,t] \cdot factor_{CU}[c] &\ge minOutputShare[a,c] \cdot ActualTotalEffOutputCU[a,r,t] \\ OutputSpec[a,c,r,t] \cdot factor_{CU}[c] &\le maxOutputShare[a,c] \cdot ActualTotalEffOutputCU[a,r,t] \end{aligned} \]

    C. Full Model Construction

    Note: This section includes references to many features that are not described elsewhere in this document or implemented yet (e.g. region-to-region trade), but these are included for completeness. This represents the roadmap for future MUSE2 development.

    This section describes how all preceding components are integrated to form the complete dispatch optimisation problem.

    C.1. Objective Function

    The overall objective is to minimise the total system cost, which is the sum of all operational costs from assets (standard and flexible), financial impacts from policy scopes (taxes minus credits), costs of inter-regional trade, costs of pool-based trade, and importantly, the high economic penalties associated with any unserved demand for critical commodities:

    \[ \begin{aligned} \text{Minimise: } &(\text{Core Asset Operational Costs from A.3 and B.4}) \\ &+ \sum_{c \in \mathbf{C}^{VoLL},r,t} UnmetD[c,r,t] \cdot VoLL[c,r] \quad \text{(Penalty for Unserved Demand)} \end{aligned} \]

    Note that the unmet demand variables (\( UnmetD[c,r,t] \)) are normally not included in the optimisation and are currently only used to diagnose the source of errors when running the model.

    C.2. Constraints

    The complete set of constraints that the optimisation must satisfy includes:

    Demand Satisfaction for \( c\in \mathbf{C}^{\mathrm{SVD}} \)

    These constraints ensure that exogenously defined final demands for SVDs are met in each region \( r \) and time slice \( t \), or any shortfall is explicitly accounted for.

    For all \( r,t,c \in \mathbf{C}^{\mathrm{SVD}} \): Let \( TotalSystemProduction_{SVD}[c,r,t] \) be the sum of all production of \( c \) from standard assets (\( output_{coeff}[a,c]\,act[a,r,t] \)) and flexible assets (the relevant \( OutputSpec[a,c,r,t] \) if \( c \in \mathbf{C}_a^{eff\_out} \), or \( act[a,r,t] \cdot coeff_{aux\_out}[a,c] \) if \( c \in \mathbf{C}^{aux\_out}_a \)).

    Let \( NetImports_{SVD}[c,r,t] \) be net imports of \( c \) from R2R and Pool trade if SVDs are tradeable. If \( c \in \mathbf{C}^{VoLL} \) (meaning unserved demand for this SVD is permitted at a penalty):

    \[ TotalSystemProduction_{SVD}[c,r,t] + NetImports_{SVD}[c,r,t] + UnmetD[c,r,t] \ge demand[r,c] \times timeslice\_ share[c,t] \]

    Else (if SVD \( c \) must be strictly met and is not included in \( \mathbf{C}^{VoLL} \)):

    \[ TotalSystemProduction_{SVD}[c,r,t] + NetImports_{SVD}[c,r,t] \ge demand[r,c] \times timeslice\_ share[c,t] \]

    Commodity Balance for \( c\in \mathbf{C}^{\mathrm{SED}} \)

    These constraints ensure that for all intermediate SED commodities, total supply equals total demand within each region \( r \) and for each balancing period defined by \( balance\_ level[c,r] \) (e.g., timeslice, seasonal, annual).

    For a timeslice balance (\( \forall r,t,c \in \mathbf{C}^{\mathrm{SED}} \)):

    Total Inflows (Local Production by all assets + Imports from other regions and pools + Unserved SED if \( c \in \mathbf{C}^{VoLL} \)) = Total Outflows (Local Consumption by all assets + Exports to other regions).

    \[ \begin{aligned} &\sum_{a \in \mathbf{A}^{std}} output_{coeff}[a,c]\,act[a,r,t] && \text{(Std Asset Production)} \\ &+ \sum_{a \in \mathbf{A}^{flex}} \left( \begin{cases} OutputSpec[a,c,r,t] & \text{if } c \in \mathbf{C}^{eff\_out}_a \\ act[a,r,t] \cdot coeff_{aux\_out}[a,c] & \text{if } c \in \mathbf{C}^{aux\_out}_a \\ 0 & \text{otherwise} \end{cases} \right) && \text{(Flex Asset Production)} \\ &-\sum_{a \in \mathbf{A}^{std}} input_{coeff}[a,c]\,act[a,r,t] && \text{(Std Asset Consumption)} \\ &- \sum_{a \in \mathbf{A}^{flex}} \left( \begin{cases} InputSpec[a,c,r,t] & \text{if } c \in \mathbf{C}^{eff\_in}_a \\ act[a,r,t] \cdot coeff_{aux\_in}[a,c] & \text{if } c \in \mathbf{C}^{aux\_in}_a \\ 0 & \text{otherwise} \end{cases} \right) && \text{(Flex Asset Consumption)} \\ &+ \sum_{r’\neq r, c \in \mathbf{C}^R} ship_{R2R}[r’,r,c,t](1 - loss_{R2R}[r’,r,c,t]) && \text{(R2R Imports)} \\ &+ \sum_{p, c \in \mathbf{C}^P} ship_{pool}[p,r,c,t](1 - loss_{pool}[p,r,c,t]) && \text{(Pool Imports)} \\ &- \sum_{r’\neq r, c \in \mathbf{C}^R} ship_{R2R}[r,r’,c,t] && \text{(R2R Exports)} \\ &+ \mathbb{I}(c \in \mathbf{C}^{VoLL}) \cdot UnmetD[c,r,t] && \text{(Unserved SED, if modelled)} \\ &\ge 0 \end{aligned} \]

    (where \( \mathbb{I}(c \in \mathbf{C}^{VoLL}) \) is an indicator function, \( 1 \) if \( c \) is in \( \mathbf{C}^{VoLL} \), \( 0 \) otherwise. Note that SVDs are not consumed by assets, so \( input_{coeff}[a,c] \) and related terms for SVDs on the consumption side are zero).

    Investment Appraisal

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

    Investment Order

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

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

    After each commodity market is settled, a dispatch is run over all assets selected so far. This quantifies the input commodity flows consumed by newly committed assets — for example, a gas generator committed during electricity market investment will consume gas, creating demand that the gas market investment must subsequently meet.

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

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

    Circularities

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

    Commodity Prices Used in Appraisal

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

    • Shadow prices \( \lambda_{c,r,t} \): used for activity coefficients in the mini dispatch optimisation step of each appraisal (see Mini Dispatch Optimisation).
    • Market prices \( \pi_{c,r,t} \): used to calculate the investment metric (Cost Index or SNAS) after dispatch.
    • Fallback prices \( \phi_{c,r,t} \): used to incentivise dispatch in the mini dispatch optimisation when shadow prices alone are insufficient (see Mini Dispatch Optimisation). Calculated using the strategy defined by fallback_pricing_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 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 (\( \text{FOM} \)): \[ \text{AFC}_\text{existing} = \text{FOM} \]

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

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

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

    Asset Capacity

    A process is either divisible or non-divisible:

    • A divisible process has a fixed unit_size (defined in processes.csv). Assets of this type consist of one or more discrete units, each of size unit_size. When commissioned, a divisible asset is split into individual units, each of which is appraised and retained or mothballed independently.
    • A non-divisible process has no unit_size. Assets of this type are treated as a single entity: capacities may take any value within their allowable range, but cannot be split into independently appraised or mothballed units.

    Existing assets

    • Non-divisible: the asset is appraised as a whole at its full installed capacity.
    • Divisible: each individual unit is appraised separately, one at a time. This allows partial retention — for example, some units of a multi-unit plant may be retained while others are mothballed.

    Candidate assets

    Before a candidate asset can be appraised, it is assigned a trial capacity which defines how much capacity can be installed in a single investment round (subject to further demand-limiting capacity and investment constraints, described below)

    • Divisible: the trial capacity is set to one unit (one unit_size), representing a single unit being considered for investment.

    • Non-divisible: the trial capacity is based on the capacity that would satisfy the total remaining demand if the asset operated at its maximum annual rate:

      \[ \text{TrialCapacity} = \frac{\sum_t \text{Demand}_t}{\text{MaxAnnualSupplyPerCapacity}} \times \text{CapacityLimitFactor} \]

      capacity_limit_factor (set in model.toml, must be > 0 and <= 1) controls the size of investment increments relative to total demand. Lower values produce smaller investment increments (requiring more investment rounds), while higher values produce larger increments.

    Demand-limiting capacity (DLC)

    In each investment round, a candidate’s trial capacity is further capped by the demand-limiting capacity, which is the minimum capacity required to satisfy the remaining demand across all time-slice selections:

    \[ \text{DLC} = \max_{\text{selection}} \frac{\sum_{t \in \text{selection}} \text{Demand}_t} {\text{MaxSupplyPerCapacity}_\text{selection}} \]

    Selections where the asset has zero maximum supply are excluded. The cap prevents over-investment (i.e. building more capacity than needed to meet remaining demand).

    Investment constraints

    Processes may have an addition_limit (see process_investment_constraints.csv) specifying the maximum new capacity that can be built per year. The installable capacity limit for a given MSY is:

    \[ \text{MaxInstallableCapacity} = \text{AdditionLimit} \times \Delta_\text{MSY} \times \text{AgentPortion} \]

    where \( \Delta_\text{MSY} \) is the number of years since the previous MSY and \( \text{AgentPortion} \) is the fraction of the commodity market for which this agent is responsible.

    If the remaining installable capacity is exhausted, the candidate is excluded from further consideration.

    Mini Dispatch Optimisation

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

    Activity coefficients

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

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

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

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

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

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

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

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

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

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

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

    Constraints

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

    Objective

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

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

    Metric Calculation

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

    Market costs per time slice

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

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

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

    LCOX metric (objective_type = "lcox")

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

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

    Lower values indicate lower-cost investments.

    NPV metric (objective_type = "npv")

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

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

    Higher values indicate more profitable investments.

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

    Asset Selection

    Sorting and tie-breaking

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

    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 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
    \( \text{OperatingCost} \)£5/MWh activityConstant across time slices

    All per-flow costs (\( cost_\text{input} \), \( cost_\text{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 act_{t_0} + (-10) \cdot act_{t_1} \), so it prefers to dispatch during \( t_0 \) and minimise activity during \( t_1 \), subject to demand and availability constraints.

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

    LCOX Metric

    Market costs (excluding primary output):

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

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

    NPV Metric

    Market costs (including primary output):

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

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

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

    Commodity Prices

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

    Pricing Strategies

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

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

    Cost Calculations

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

    Generic Activity Cost

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

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

    Marginal Cost

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

    Full Cost

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

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

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

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

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

    Note: this only works if all output commodities are measured in the same energy units (e.g. PJ). For this reason, MUSE2 disallows processes that have output commodities with differing units.

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

    Candidate asset fallback

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

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

    Time Slice Aggregation

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

    Price Calculation Order

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

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

    Circularities

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

    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:

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

    Full Cost

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

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

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

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

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

    GASNAT

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

    Marginal Cost

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

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

    Full Cost

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

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

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

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

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

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

    Model diagrams

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

    Functions are described with the following terms:

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

    Overview of MUSE2
    Figure 1: Overview of MUSE2 algorithm

    Dispatch optimisation
    Figure 2: Overview of dispatch optimisation

    Glossary

    Activity: The flow of input/s or output/s of a Process that are limited by its capacity. For example, a 500MW power station can output 500MWh per hour of electrical power, or a 50MW electrolyser consumes up to 50MWh per hour of electrical power to produce hydrogen.

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

    Agent Objective/s: One or more objectives that an Agent considers when deciding which Process to invest in. Objectives can be economic, environmental, or others.

    Asset: Once an Agent makes an investment, the related capacity of their chosen Process becomes an Asset that they own and operate. An Asset is an instance of a Process, it has a specific capacity, and a decommissioning year. A set of Assets must exist in the base year sufficient to serve base year demands (i.e. a calibrated base year, based on user input data).

    Availability: The maximum, minimum or fixed percentage of maximum output (or input) that a Process delivers over a period. The time period could be a single time slice, a season, or a year.

    Base Year: The starting year of a model run. The base year is typically calibrated to known data, including Process stock and commodity consumption/production.

    Calibration: The act of ensuring that the model represents the system being modelled in a historical base year.

    Capacity: The maximum output (or input) of an Asset.

    Capital Cost: The overnight capital cost of a process.

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

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

    Decision Rule: The rule via which an Agent uses the Objective/s to decide between Process options to invest in. Examples include single objective, weighted sum between multiple objectives, or epsilon constraint where a secondary objective is considered if two options with similar primary objectives are identified.

    Dispatch: The way in which Assets are operated to serve demand. MUSE2 uses merit order dispatch, subject to Availability and other constraints that can be defined by the user.

    End Year: The final year in the model time horizon.

    Equivalent Annual Cost (EAC): An Agent objective, representing the annualised cost of serving all or part of an Agent’s demand for a year, considering the Asset’s entire lifetime.

    Fixed Operating Cost: The Asset or Process annual operating cost charged per unit of capacity.

    Input Commodity/ies: The commodities that flow into a Process.

    Levelised Cost of X (LCOX): An Agent objective, representing the discounted cost of 1 unit of output commodity X from a process over its lifetime under a specified discount rate.

    Lifetime: The lifetime of a Process, measured in years.

    Milestone Years: A set of years in the model time horizon where model results are recorded. For example, with a 2025 Base Year and End Year 2100, a user might choose to record outputs in 5-year steps.

    Merit Order: A method of operating Assets when the cheapest is dispatched first, followed by the next most expensive, etc, until demand is served. Also called “unit commitment.”

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

    Output Commodity/ies: The commodities that flow out of a Process.

    Process: A blueprint of an available Process that converts input commodities to output commodities. Processes have economic attributes of capital cost, fixed operating cost per unit capacity, non-fuel variable operating cost per unit activity, and risk discount rate. They have physical attributes of quantity and type of input and output commodities (which implicitly specify efficiency), Availability limits (by time slice, season and/or year), lifetime (years). When a Process is selected by an Agent for investment an instance of it called an Asset is created.

    Region: A geographical area that is modelled. Regions primarily determine trade boundaries.

    Season: A year is usually broken down into seasons in the model. For example, summer, winter, other.

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

    Service Demand (SVD): A Service Demand is a type of commodity that is consumed at the boundary of the modelled system. For example, tonne-kilometers of road freight, PJ of useful heat demand, etc.

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

    Discount Rate: The discount rate used to calculate any process-specific agent economic objectives that require a discount rate. For example, Equivalent Annual Cost, Net Present Value, Levelised Cost of X, etc.

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

    Time Period: Refers to a specific Milestone Year in the time horizon.

    Time Slice: The finest time period in the model. The maximum time slice length is 1 year (where a model does not represent seasons or within-day (diurnal) variation). A typical model will have several diurnal time slices, and several seasonal time slices.

    Utilisation: The percentage of an Asset’s capacity that is actually used to produce its commodities. Must be between 0 and 1, and can be measured at time slice, season, or year level.

    Variable Operating Cost: The variable operating cost charged per unit of activity of the Process.

    Developer Guide

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

    The API documentation is available here.

    Setting up your development environment

    To develop MUSE2 locally you will need the following components:

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

    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_divisible
    

    Example models

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

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

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

    Unit types

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

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

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

    Input and output files

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

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

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


    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.

    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)

    Upcoming release of MUSE2

    New features

    • Users can now optionally pass custom options to the HiGHS solver (#1276)
    • Implement a new appraisal approach for the LCOX objective type (#1319)
    • Memory usage and performance has been drastically improved for divisible assets with many units (#1464)

    Breaking changes

    • Changed the default pricing_strategy for SED/SVD commodities from “shadow” to “full_average” (#1281)
    • 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)

    Bug fixes

    • Fix misleading warning message for assets decommissioned before simulation start (#1259)
    • Fix parsing and validation of agent search space file (#1293)
    • Use shadow prices rather than market prices for appraisal optimisations and dispatch runs during investment (#1349)
    • Updated input data validation to ensure capacity_to_activity > 0. Previously, capacity_to_activity = 0 was permitted.
    • Mothballed assets are now excluded from the main dispatch run (#1458)