-
Notifications
You must be signed in to change notification settings - Fork 1
Calculate reduced costs and add scarcity_pricing option
#684
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Also emit a warning if the user disables it.
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #684 +/- ##
==========================================
- Coverage 87.78% 85.87% -1.92%
==========================================
Files 38 39 +1
Lines 3439 3589 +150
Branches 3439 3589 +150
==========================================
+ Hits 3019 3082 +63
- Misses 249 337 +88
+ Partials 171 170 -1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
tsmbland
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like it's on the right track, but seems very convoluted. Maybe you understand something that I don't, but why are we keeping both the adjusted and unadjusted prices?
Wouldn't it be better to have several price calculation methods, a switch that toggles which one to use, and then proceed using the resulting prices from the chosen method. We calculate the reduced costs using these prices, and then pass the prices and reduced costs to the investment algorithm. Am I missing something?
src/model.rs
Outdated
| /// | ||
| /// Don't disable unless you know what you're doing. | ||
| #[serde(default = "return_true")] | ||
| pub scarcity_pricing: bool, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Funnily enough I'd find this more intuitive if instead we had scarcity_adjusted_pricing (= false by default). Maybe that's just me
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you're right. It's a bit weird that the code does an extra thing if you set this option to false -- not v intuitive.
How about calling it adjust_prices_for_scarcity, to make it clear the code does something extra in this case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, I'm wondering if we should have a pricing_strategy option instead. See below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, I think that's the best option
src/simulation/investment.rs
Outdated
| reduced_costs.extend(reduced_costs_for_existing( | ||
| &model.time_slice_info, | ||
| assets, | ||
| adjusted_prices.unwrap_or(unadjusted_prices), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should move some of this logic outside of this function and have perform_agent_investment take prices and reduced_costs directly as arguments. Would that work?
Especially if we'll want to add more options for calculating prices in the future, this could get very messy otherwise
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea.
Tbh it's not entirely clear in my head what the point of the perform_agent_investment function should be, but I left it there because that's the structure we have already. The reduced costs stuff probably shouldn't live in there though.
src/model.rs
Outdated
| /// A function which always returns true. | ||
| /// | ||
| /// Used for setting options to default to "on". | ||
| const fn return_true() -> bool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yuck. But if this is the only way then so be it!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah 😞. Rust isn't always that ergonomic. But if we change the default to be false, then we can just mark the attribute as #[serde(default)], which is less gross (but perhaps not super clear).
|
Hmm okay I'm going to give #661 a read and get back to this - think some of my uncertainties are addressed there! |
src/simulation/prices.rs
Outdated
| } | ||
|
|
||
| /// Remove the effect of scarcity on candidate assets' reduced costs | ||
| pub fn remove_scarcity_influence_from_candidate_reduced_costs( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see there's a way to calculate this directly using the duals. I think we should do this, then we won't need to keep both sets of prices any more and can hopefully make it a bit more modular
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did consider this, but Adam's response (on #661) was:
We subtract the scarcity-inclusive prices, and add back the scarcity-removed prices. It just adjusts RC a bit (to remove scarcity element of price). True maybe there is some other way to represent this with the capacity duals, etc. But I think the way I've done it is more intuitive - working with the prices.
We don't have to take that suggestion, obviously, but I figured it might make sense to implement things in the same way as in the formulation for now and maybe revise it later if we want.
Something that I forgot until after the discussion with Adam is that prices are also influenced by levies. If a commodity has a commodity balance constraint and a levy, then we choose the higher of the two. I'm not sure how we could control for this in the calculation in an obvious way, so maybe Adam's suggestion is easier in that respect too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, but are we sure the levy should be factored into this calculation? All seems a bit strange
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even still, I think there would be a way to account for this even without keeping both sets of prices.
Bottom line: if we can do some of the other changes suggested, and ensure that only the prices (and reduced costs) corresponding the the chosen method exists outside of the prices module, then I think I'll be happy
tsmbland
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Accidentally hit approve, but I do think this needs some work as discussed
Because we need them in the (catchily named)
You're not missing anything and this is probably a cleaner way of doing things. Adam did say he'd like the pricing strategy to be modular too, which implies we might want to add another strategy in future. How about instead of the What do you think @ahawkes? Would that flexibility be useful? |
|
I've tidied it up and changed the |
There was a problem hiding this 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 a configurable scarcity‐pricing option to the model, refactors how commodity prices and reduced costs are computed, and updates test data accordingly
- Introduce
PricingStrategyenum with two modes (ShadowPrices,ScarcityAdjusted) and wire it through model and simulation - Refactor
CommodityPricesand addget_prices_and_reduced_coststo produce both prices and reduced costs in one call - Update test CSV fixtures to reflect scarcity‐adjusted price values
Reviewed Changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| tests/data/simple_mc/commodity_prices.csv | Updated expected prices for scarcity‐adjusted mode |
| tests/data/simple/commodity_prices.csv | Same updates for the simpler test dataset |
| src/simulation/prices.rs | Extracted pricing logic into get_prices_and_reduced_costs, added scarcity logic |
| src/simulation/optimisation.rs | Simplified cost coefficient by delegating to new get_operating_cost |
| src/asset.rs | Added get_operating_cost to Asset |
| src/simulation/investment.rs | Updated perform_agent_investment signature to accept ReducedCosts and year |
| src/simulation.rs | Switched to get_prices_and_reduced_costs and pass reduced costs to investment |
| src/model.rs | Added pricing_strategy config, default, and warning for scarcity adjustments |
Comments suppressed due to low confidence (3)
src/simulation/prices.rs:215
- [nitpick] This function name is quite long and could be more concise; consider renaming it to something like
adjust_reduced_costs_for_scarcityto improve readability.
fn remove_scarcity_influence_from_candidate_reduced_costs(
src/simulation/prices.rs:20
- Add unit tests to cover both
ShadowPricesandScarcityAdjustedpricing strategies inget_prices_and_reduced_coststo ensure correct price and reduced cost calculations.
pub fn get_prices_and_reduced_costs(
src/simulation/prices.rs:14
- [nitpick] Consider adding a doc comment for the
ReducedCoststype alias to explain its purpose and units for future maintainers.
pub type ReducedCosts = HashMap<(AssetRef, TimeSliceID), MoneyPerActivity>;
| ) -> MoneyPerActivity { | ||
| let adjusted = adjusted_prices | ||
| .get(&flow.commodity.id, region_id, time_slice) | ||
| .expect("No adjusted price found"); | ||
| let unadjusted = unadjusted_prices | ||
| .get(&flow.commodity.id, region_id, time_slice) | ||
| .expect("No unadjusted price found"); | ||
| flow.coeff * (unadjusted - adjusted) |
Copilot
AI
Jul 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using expect here can cause a panic if a price entry is missing; consider returning a Result or providing a default value to handle missing prices gracefully.
| ) -> MoneyPerActivity { | |
| let adjusted = adjusted_prices | |
| .get(&flow.commodity.id, region_id, time_slice) | |
| .expect("No adjusted price found"); | |
| let unadjusted = unadjusted_prices | |
| .get(&flow.commodity.id, region_id, time_slice) | |
| .expect("No unadjusted price found"); | |
| flow.coeff * (unadjusted - adjusted) | |
| ) -> Result<MoneyPerActivity, String> { | |
| let adjusted = adjusted_prices | |
| .get(&flow.commodity.id, region_id, time_slice) | |
| .ok_or(format!( | |
| "No adjusted price found for commodity: {:?}, region: {:?}, time slice: {:?}", | |
| flow.commodity.id, region_id, time_slice | |
| ))?; | |
| let unadjusted = unadjusted_prices | |
| .get(&flow.commodity.id, region_id, time_slice) | |
| .ok_or(format!( | |
| "No unadjusted price found for commodity: {:?}, region: {:?}, time slice: {:?}", | |
| flow.commodity.id, region_id, time_slice | |
| ))?; | |
| Ok(flow.coeff * (unadjusted - adjusted)) |
tsmbland
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is way better, thanks!
It was a bit hacky before 😆 |
Description
Reduced costs are needed for the investment appraisal step.
For candidate assets, we get reduced costs as direct output of the optimisation step, however we need to adjust them because of the adjustment for scarcity prices that we do (i.e. adding in activity duals). For existing assets, we don't get the result from the optimisation and it is a separate calculation step.
After I'd written this code, it became apparent that we probably actually don't want to adjust prices for scarcity anyway, as this approach has problems (#677). In any case, there was an open issue to make the scarcity pricing thing a model option (#625), so rather than just removing the code I've added this option and set the default to
true(meaning we don't do this adjustment by default any more). I think this is a good compromise.Closes #647. Closes #625.
Type of change
Key checklist
$ cargo test$ cargo docFurther checks