Skip to content

Add price strategies using load-weighted averaging#1205

Merged
tsmbland merged 15 commits intomainfrom
average_prices
Mar 20, 2026
Merged

Add price strategies using load-weighted averaging#1205
tsmbland merged 15 commits intomainfrom
average_prices

Conversation

@tsmbland
Copy link
Collaborator

@tsmbland tsmbland commented Mar 17, 2026

Description

Adds two new pricing strategies as per the instructions in #1200

These are very similar to the existing marginal and full strategies, and indeed they recycle a lot of code. We could think about ways to reduce repetition, although I don't want to over-engineer too much as I want each strategy to be understood in isolation.

The approach here is a bit simpler than the previous strategies. Since we're averaging over all assets, we only need one accumulator per (commodity, region, ts selection), rather than an accumulator for each asset. Candidate assets are treated identically is in the previous strategies, i.e. averaging based on activity limits taking the min. This seems appropriate to me as candidate assets don't have any output over which to average, and I've been thinking of this as representing the price if a small amount of demand was added, in which case the cheapest candidate asset would win as the sole provider of this small amount.

Fixes #1200

Type of change

  • Bug fix (non-breaking change to fix an issue)
  • New feature (non-breaking change to add functionality)
  • Refactoring (non-breaking, non-functional change to improve maintainability)
  • Optimization (non-breaking change to speed up the code)
  • Breaking change (whatever its nature)
  • Documentation (improve or add documentation)

Key checklist

  • All tests pass: $ cargo test
  • The documentation builds and looks OK: $ cargo doc
  • Update release notes for the latest release if this PR adds a new feature or fixes a bug
    present in the previous release

Further checks

  • Code is commented, particularly in hard-to-understand areas
  • Tests added that prove fix is effective or that feature works

@tsmbland tsmbland changed the title Add new strategies Price strategies using load-weighted averaging Mar 17, 2026
@codecov
Copy link

codecov bot commented Mar 17, 2026

Codecov Report

❌ Patch coverage is 90.34749% with 25 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.35%. Comparing base (6c9170c) to head (0432a67).
⚠️ Report is 27 commits behind head on main.

Files with missing lines Patch % Lines
src/simulation/prices.rs 89.08% 24 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1205      +/-   ##
==========================================
+ Coverage   89.31%   89.35%   +0.03%     
==========================================
  Files          57       57              
  Lines        7974     8263     +289     
  Branches     7974     8263     +289     
==========================================
+ Hits         7122     7383     +261     
- Misses        555      582      +27     
- Partials      297      298       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@tsmbland tsmbland requested a review from Copilot March 17, 2026 13:58
@tsmbland tsmbland changed the title Price strategies using load-weighted averaging Add price strategies using load-weighted averaging Mar 17, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds two new commodity pricing strategies—marginal_average and full_average—to complement the existing marginal and full strategies by using load-weighted averaging rather than “highest-cost producer wins”. This extends the model’s ability to represent observed market pricing behaviors (per #1200) and adds regression coverage via new patched examples.

Changes:

  • Introduces PricingStrategy::MarginalCostAverage and PricingStrategy::FullCostAverage and wires them into price calculation.
  • Updates circular-dependency validation to forbid the new cost-based average strategies in SCC cycles.
  • Adds patched examples + regression fixtures for simple_marginal_average and simple_full_average, plus schema/docs updates for the new strategy names.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/commodity.rs Adds new PricingStrategy enum variants with serde names.
src/simulation/prices.rs Implements marginal/full cost average pricing and integrates into calculate_prices.
src/graph/investment.rs Extends cycle validation to include the new average strategies.
src/example/patches.rs Adds two new patched examples selecting the new strategies.
schemas/input/commodities.yaml Documents and validates the new pricing_strategy enum values.
docs/model/investment.md Fixes pricing strategy names to match current inputs (shadow, scarcity).
tests/regression.rs Adds regression tests for the two new patched examples.
tests/data/simple_marginal_average/* Regression fixtures for marginal-average pricing example.
tests/data/simple_full_average/* Regression fixtures for full-average pricing example.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

@tsmbland tsmbland marked this pull request as ready for review March 17, 2026 14:42
@tsmbland tsmbland requested review from Aurashk and alexdewar March 17, 2026 14:42
@alexdewar
Copy link
Collaborator

I'm going to hold off on reviewing this until #1196 is merged, if that's ok, as I think the refactoring there will affect this PR.

@tsmbland
Copy link
Collaborator Author

I'm going to hold off on reviewing this until #1196 is merged, if that's ok, as I think the refactoring there will affect this PR.

@alexdewar Sure. I've done some small refactoring in that PR, but I think any larger refactoring would need to be conscious of the methods introduced here, so might actually be better done as part of this PR

Base automatically changed from buffered_prices to main March 19, 2026 09:29
Copy link
Collaborator

@alexdewar alexdewar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks ok for now, though I do think it would be better if we could dedup some of this code -- this file's getting rather long. Aside from that, the functions themselves are also long, which makes it hard to keep track of everything they're doing. I see you're working on that already though (#1212), so maybe let's hold off discussion until then.

One option would be to define an Accumulator trait and implement it for the weighted average and the min cases (with a wrapper struct for the latter), then you could make these functions generic... I think. There are probably some fiddly details there. Anyway, I'll hold off judgment until #1212!

One thing I've only just thought of is that the WeightedAverageAccumulator uses Dimensionless for the denominator, though in practice we're actually using various other unit types. How about adding a type parameter for the denominator type? That way we can enforce that the same unit type is being supplied to add(), but without mandating that it always be Dimensionless. The compiler will infer the type in practice, so it shouldn't make using the class any uglier.

"ELCTRI,Electricity,sed,daynight,marginal_average,PJ",
"RSHEAT,Residential heating,svd,daynight,shadow,PJ",
"CO2EMT,CO2 emitted,oth,annual,unpriced,ktCO2",
])],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is perhaps a bit out of scope for this PR, but I'm wondering whether we should include the pricing_strategy column in the examples even though it will be empty in every case (for now). It might be good as a kind of documentation.

let output_coeff = asset
.get_flow(&commodity_id)
.expect("Commodity should be an output flow for this asset")
.coeff;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it also be worth asserting that this is an output flow, not an input?

Co-authored-by: Alex Dewar <alexdewar@users.noreply.github.com>
Copilot AI review requested due to automatic review settings March 19, 2026 17:27
Co-authored-by: Alex Dewar <alexdewar@users.noreply.github.com>
Co-authored-by: Alex Dewar <alexdewar@users.noreply.github.com>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds two new cost-based pricing strategies—marginal_average and full_average—that compute load-weighted average prices across contributing assets, extending the model’s pricing options as requested in #1200.

Changes:

  • Introduce PricingStrategy::MarginalCostAverage and PricingStrategy::FullCostAverage (including schema/docs updates).
  • Implement average-price calculation paths in src/simulation/prices.rs and integrate them into the pricing pipeline.
  • Add regression fixtures and example patches for the two new strategies.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/regression.rs Adds regression test cases for the new strategies.
tests/data/simple_marginal_average/* New regression outputs/fixtures for marginal_average.
tests/data/simple_full_average/* New regression outputs/fixtures for full_average.
src/simulation/prices.rs Implements marginal/full load-weighted average pricing and wires them into calculate_prices.
src/graph/investment.rs Extends cycle validation to also forbid the new cost-based strategies in SCC cycles.
src/example/patches.rs Adds example patches enabling the new strategies in the “simple” scenario.
src/commodity.rs Extends PricingStrategy enum with the two new variants and serde names.
schemas/input/commodities.yaml Updates allowed pricing_strategy enum values and documentation text.
docs/model/investment.md Corrects pricing strategy naming in documentation (shadow, scarcity).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

@tsmbland
Copy link
Collaborator Author

One option would be to define an Accumulator trait and implement it for the weighted average and the min cases (with a wrapper struct for the latter), then you could make these functions generic... I think. There are probably some fiddly details there. Anyway, I'll hold off judgment until #1212!

I had considered this, but we use min/max/average in such different contexts that I don't think we can/should make a generic function to cover all of these cases, and at that point the Accumulator trait isn't worth it.

One thing I've only just thought of is that the WeightedAverageAccumulator uses Dimensionless for the denominator, though in practice we're actually using various other unit types. How about adding a type parameter for the denominator type? That way we can enforce that the same unit type is being supplied to add(), but without mandating that it always be Dimensionless. The compiler will infer the type in practice, so it shouldn't make using the class any uglier.

Yeah I guess this is a good idea. We'd still have to convert the denominator to Dimensionless internally because the unit type of the denominator may not be compatible with the numerator (e.g. we use Activity to weight MoneyPerFlow, which is fine mathematically, although we don't have an implementation for MoneyPerFlow * Activity)

@tsmbland tsmbland enabled auto-merge March 20, 2026 11:20
@tsmbland tsmbland merged commit d904081 into main Mar 20, 2026
8 of 9 checks passed
@tsmbland tsmbland deleted the average_prices branch March 20, 2026 11:21
@tsmbland tsmbland mentioned this pull request Mar 20, 2026
11 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add additional pricing strategies - weighted average full cost and weighted average marginal cost

3 participants