From a98c2cc5a7cb742e32d1a2ad68d68ef03623e5ff Mon Sep 17 00:00:00 2001 From: Tom Bland Date: Mon, 22 Jun 2026 16:25:14 +0100 Subject: [PATCH 01/12] Add documentation for commodity price calculations --- docs/SUMMARY.md | 1 + docs/model/prices.md | 127 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+) create mode 100644 docs/model/prices.md diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 38e092116..44a6542ee 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -12,6 +12,7 @@ - [Model Description](model/README.md) - [Dispatch Optimisation](model/dispatch_optimisation.md) - [Investment Appraisal](model/investment.md) + - [Commodity Prices](model/prices.md) - [Model Diagrams](model/model_diagrams.md) - [Glossary](glossary.md) - [Developer Guide](developer_guide/README.md) diff --git a/docs/model/prices.md b/docs/model/prices.md new file mode 100644 index 000000000..69a66287a --- /dev/null +++ b/docs/model/prices.md @@ -0,0 +1,127 @@ +# 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. +- **`full_average`**: Prices are set to the output-weighted average full cost across all active assets. +- **`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. + +### 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 and input purchases not associated +with specific SED/SVD outputs: +\\[ +\text{GenericActivityCost} = \text{VariableOperatingCost} + \text{InputPurchases} + \sum \text{GenericLevies} +\\] + +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, MUSE 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, these fall back to the candidate asset with the lowest marginal/full cost, +calculated assuming full utilisation (i.e. assuming annual activity equals the maximum annual +activity limit). + +### 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 (or activity limits +for candidates) to yield a flat price for the season/year. + +## 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. + +## Price Calculation Loop (Cycles) + +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). Price calculations use newly updated market prices for commodities already +evaluated in the sequence, and fall back to the seeded shadow prices for any not yet evaluated. + + + +[Capital Recovery Factor]: (https://homerenergy.com/products/pro/docs/latest/capital_recovery_factor.html) From d297ee237c57458462035082ec7729c4ba8b5eac Mon Sep 17 00:00:00 2001 From: Tom Bland Date: Mon, 22 Jun 2026 16:27:04 +0100 Subject: [PATCH 02/12] Rename section --- docs/model/prices.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/model/prices.md b/docs/model/prices.md index 69a66287a..6d0a8b105 100644 --- a/docs/model/prices.md +++ b/docs/model/prices.md @@ -108,7 +108,7 @@ This sequence is the **reverse** of the investment order. This ensures that the commodities (from upstream markets) are calculated and known before they are consumed as inputs by downstream processes evaluating marginal or full cost pricing. -## Price Calculation Loop (Cycles) +## 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: From 01473c06517d7394fabc17d87aff9dccb4584988 Mon Sep 17 00:00:00 2001 From: Tom Bland Date: Mon, 22 Jun 2026 16:35:24 +0100 Subject: [PATCH 03/12] Fix link --- docs/model/prices.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/model/prices.md b/docs/model/prices.md index 6d0a8b105..1436e5d7e 100644 --- a/docs/model/prices.md +++ b/docs/model/prices.md @@ -124,4 +124,4 @@ re-evaluating each market in the cycle in reverse order (starting with those furthest from commodities downstream of the SCC) to propagate the feedback effects through the circular markets. --> -[Capital Recovery Factor]: (https://homerenergy.com/products/pro/docs/latest/capital_recovery_factor.html) +[Capital Recovery Factor]: https://homerenergy.com/products/pro/docs/latest/capital_recovery_factor.html From e90ae4e107e4c9e071c55fe75cd9f5ef803371e9 Mon Sep 17 00:00:00 2001 From: Tom Bland Date: Tue, 23 Jun 2026 12:10:40 +0100 Subject: [PATCH 04/12] Clarification about assets with zero activity --- docs/model/prices.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/model/prices.md b/docs/model/prices.md index 1436e5d7e..9ce0b27ba 100644 --- a/docs/model/prices.md +++ b/docs/model/prices.md @@ -96,8 +96,9 @@ activity limit). ### 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 (or activity limits -for candidates) to yield a flat price for the season/year. +calculated by weighting asset costs across time slices by activity to yield a flat price for the +season/year. For candidates, or assets with zero activity across the selection, upper activity +limits are used as weights. ## Price Calculation Order From 365929d641d8392d01101974b7f8f663d07656eb Mon Sep 17 00:00:00 2001 From: Tom Bland Date: Tue, 23 Jun 2026 14:16:11 +0100 Subject: [PATCH 05/12] Link to input file docs --- docs/model/prices.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/model/prices.md b/docs/model/prices.md index 9ce0b27ba..9f2ebb094 100644 --- a/docs/model/prices.md +++ b/docs/model/prices.md @@ -7,7 +7,7 @@ market prices. ## Pricing Strategies -Each commodity is configured with a specific `pricing_strategy` via `commodities.csv`. +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 @@ -126,3 +126,4 @@ furthest from commodities downstream of the SCC) to propagate the feedback effec circular markets. --> [Capital Recovery Factor]: https://homerenergy.com/products/pro/docs/latest/capital_recovery_factor.html +[`commodities.csv`]: ../file_formats/input_files#commoditiescsv From 47d80d72579afd039fd42fec1998299eb7b25412 Mon Sep 17 00:00:00 2001 From: Tom Bland Date: Tue, 23 Jun 2026 14:29:03 +0100 Subject: [PATCH 06/12] Clearer wording --- docs/model/prices.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/docs/model/prices.md b/docs/model/prices.md index 9f2ebb094..4959a3abf 100644 --- a/docs/model/prices.md +++ b/docs/model/prices.md @@ -89,9 +89,12 @@ The final full cost of output commodity \\( c \\) is: ### Candidate asset fallback For cost-based strategies (`marginal`, `marginal_average`, `full`, `full_average`), if no active -producers exist, these fall back to the candidate asset with the lowest marginal/full cost, -calculated assuming full utilisation (i.e. assuming annual activity equals the maximum annual -activity limit). +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 @@ -117,8 +120,9 @@ which then generates electricity), the resulting cyclic dependencies are resolve 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). Price calculations use newly updated market prices for commodities already -evaluated in the sequence, and fall back to the seeded shadow prices for any not yet evaluated. +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 is produced by a single GASDRV 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 (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: + +\\[ +\text{CRF} = 0.11016807219 +\\] + +\\[ +\text{AnnualFixedCostPerCapacity} = (10 \times 0.11016807219 + 0.3) = 1.4016807219 +\\] + +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 is produced by a single GASPRC 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 (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: + +\\[ +\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 +\\] + +#### 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: + +\\[ +\text{CRF} = 0.11016807219 +\\] + +\\[ +\text{AnnualFixedCostPerCapacity} = (7 \times 0.11016807219 + 0.21) = 0.98117650533 +\\] + +Accounting for annual utilisation: + +\\[ +\text{AnnualFixedCostPerActivity} = \frac{0.98117650533}{0.2094885671} = 4.6836756722 +\\] + +Accounting for the output coefficient: + +\\[ +\text{AnnualFixedCostPerOutput} = \frac{4.6836756722}{1} = 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. + [Capital Recovery Factor]: https://homerenergy.com/products/pro/docs/latest/capital_recovery_factor.html [`commodities.csv`]: ../file_formats/input_files#commoditiescsv From 2686346719a8979bf467463adacc7dfff35a399f Mon Sep 17 00:00:00 2001 From: Tom Bland Date: Tue, 23 Jun 2026 15:30:40 +0100 Subject: [PATCH 08/12] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- docs/model/prices.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/model/prices.md b/docs/model/prices.md index 5f0c670d3..0dc9d55dc 100644 --- a/docs/model/prices.md +++ b/docs/model/prices.md @@ -33,11 +33,11 @@ shared between outputs according to output flow coefficients. #### Generic Activity Cost -The generic activity cost comprises all operating expenditures and input purchases not associated -with specific SED/SVD outputs: -\\[ -\text{GenericActivityCost} = \text{VariableOperatingCost} + \text{InputPurchases} + \sum \text{GenericLevies} -\\] +The generic activity cost comprises all operating expenditures, input purchases, and flow costs/levies +not associated with specific SED/SVD outputs: +\[ +\text{GenericActivityCost} = \text{VariableOperatingCost} + \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: From f4458700d8fa5656bdefbdc3a31226609190c3cf Mon Sep 17 00:00:00 2001 From: Tom Bland Date: Tue, 23 Jun 2026 15:31:02 +0100 Subject: [PATCH 09/12] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- docs/model/prices.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/model/prices.md b/docs/model/prices.md index 0dc9d55dc..631e92d07 100644 --- a/docs/model/prices.md +++ b/docs/model/prices.md @@ -224,9 +224,9 @@ Accounting for annual utilisation: Accounting for the output coefficient: -\\[ -\text{AnnualFixedCostPerOutput} = \frac{4.6836756722}{1} = 6836756722 -\\] +\[ +\text{AnnualFixedCostPerOutput} = \frac{4.6836756722}{1} = 4.6836756722 +\] The full cost is: From d7bf93002579f418fb8c272bd94cad8f95c3f332 Mon Sep 17 00:00:00 2001 From: Tom Bland Date: Tue, 23 Jun 2026 15:31:20 +0100 Subject: [PATCH 10/12] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- docs/model/prices.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/model/prices.md b/docs/model/prices.md index 631e92d07..5a81da859 100644 --- a/docs/model/prices.md +++ b/docs/model/prices.md @@ -238,4 +238,4 @@ This example illustrates the sequential nature of price calculation: the GASPRD calculated first and then used as an input cost when calculating the GASNAT price. [Capital Recovery Factor]: https://homerenergy.com/products/pro/docs/latest/capital_recovery_factor.html -[`commodities.csv`]: ../file_formats/input_files#commoditiescsv +[`commodities.csv`]: ../file_formats/input_files.md#commoditiescsv From 3c9c53c0ec27d5d6c8c319da0e3072cefa443ac8 Mon Sep 17 00:00:00 2001 From: Tom Bland Date: Tue, 23 Jun 2026 15:32:00 +0100 Subject: [PATCH 11/12] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- docs/model/prices.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/docs/model/prices.md b/docs/model/prices.md index 5a81da859..dadfbbe81 100644 --- a/docs/model/prices.md +++ b/docs/model/prices.md @@ -14,10 +14,7 @@ The options are: 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. -- **`full_average`**: Prices are set to the output-weighted average full 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 From 019e9e3f087be5a29b5f0757155def3284865ac1 Mon Sep 17 00:00:00 2001 From: Tom Bland Date: Tue, 23 Jun 2026 15:32:52 +0100 Subject: [PATCH 12/12] =?UTF-8?q?Lint=20fix=20(sorry=20for=20all=20the=20c?= =?UTF-8?q?ommits=20=F0=9F=98=82)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/model/prices.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/model/prices.md b/docs/model/prices.md index dadfbbe81..0c78566e9 100644 --- a/docs/model/prices.md +++ b/docs/model/prices.md @@ -14,7 +14,8 @@ The options are: 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. +- **`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