@@ -127,44 +127,42 @@ pub fn calculate_prices(model: &Model, solution: &Solution, year: u32) -> Result
127127
128128 // Add prices for scarcity-adjusted commodities
129129 if let Some ( scarcity_set) = pricing_sets. get ( & PricingStrategy :: ScarcityAdjusted ) {
130- let scarcity_prices = calculate_scarcity_adjusted_prices (
130+ add_scarcity_adjusted_prices (
131131 solution. iter_activity_duals ( ) ,
132132 & shadow_prices,
133+ & mut result,
133134 scarcity_set,
134135 ) ;
135- result. extend ( scarcity_prices) ;
136136 }
137137
138138 // Add prices for marginal cost commodities
139139 if let Some ( marginal_set) = pricing_sets. get ( & PricingStrategy :: MarginalCost ) {
140- let marginal_cost_prices = calculate_marginal_cost_prices (
140+ add_marginal_cost_prices (
141141 solution. iter_activity_for_existing ( ) ,
142142 solution. iter_activity_keys_for_candidates ( ) ,
143- & result,
143+ & mut result,
144144 year,
145145 marginal_set,
146146 & model. commodities ,
147147 & model. time_slice_info ,
148148 ) ;
149- result. extend ( marginal_cost_prices) ;
150149 }
151150
152151 // Add prices for full cost commodities
153152 if let Some ( fullcost_set) = pricing_sets. get ( & PricingStrategy :: FullCost ) {
154153 let annual_activities = annual_activities. get_or_insert_with ( || {
155154 calculate_annual_activities ( solution. iter_activity_for_existing ( ) )
156155 } ) ;
157- let full_cost_prices = calculate_full_cost_prices (
156+ add_full_cost_prices (
158157 solution. iter_activity_for_existing ( ) ,
159158 solution. iter_activity_keys_for_candidates ( ) ,
160159 annual_activities,
161- & result,
160+ & mut result,
162161 year,
163162 fullcost_set,
164163 & model. commodities ,
165164 & model. time_slice_info ,
166165 ) ;
167- result. extend ( full_cost_prices) ;
168166 }
169167 }
170168
@@ -325,23 +323,20 @@ impl IntoIterator for CommodityPrices {
325323 }
326324}
327325
328- /// Calculate scarcity-adjusted prices for a set of commodities.
326+ /// Calculate scarcity-adjusted prices for a set of commodities and add to an existing prices map .
329327///
330328/// # Arguments
331329///
332330/// * `activity_duals` - Iterator over activity duals from optimisation solution
333331/// * `shadow_prices` - Shadow prices for all commodities
332+ /// * `existing_prices` - Existing prices map to extend with scarcity-adjusted prices
334333/// * `markets_to_price` - Set of markets to calculate scarcity-adjusted prices for
335- ///
336- /// # Returns
337- ///
338- /// A map of scarcity-adjusted prices for the specified markets in all time slices
339- fn calculate_scarcity_adjusted_prices < ' a , I > (
334+ fn add_scarcity_adjusted_prices < ' a , I > (
340335 activity_duals : I ,
341336 shadow_prices : & CommodityPrices ,
337+ existing_prices : & mut CommodityPrices ,
342338 markets_to_price : & HashSet < ( CommodityID , RegionID ) > ,
343- ) -> IndexMap < ( CommodityID , RegionID , TimeSliceID ) , MoneyPerFlow >
344- where
339+ ) where
345340 I : Iterator < Item = ( & ' a AssetRef , & ' a TimeSliceID , MoneyPerActivity ) > ,
346341{
347342 // Calculate highest activity dual for each commodity/region/time slice
@@ -370,45 +365,33 @@ where
370365 }
371366 }
372367
373- // Add this to the shadow price for each commodity/region/time slice
374- let mut scarcity_prices = IndexMap :: new ( ) ;
368+ // Add this to the shadow price for each commodity/region/time slice and insert into the map
375369 for ( ( commodity, region, time_slice) , highest_dual) in & highest_duals {
376370 // There should always be a shadow price for commodities we are considering here, so it
377371 // should be safe to unwrap
378372 let shadow_price = shadow_prices. get ( commodity, region, time_slice) . unwrap ( ) ;
379373 // highest_dual is in units of MoneyPerActivity, and shadow_price is in MoneyPerFlow, but
380374 // this is correct according to Adam
381375 let scarcity_price = shadow_price + MoneyPerFlow ( highest_dual. value ( ) ) ;
382- scarcity_prices. insert (
383- ( commodity. clone ( ) , region. clone ( ) , time_slice. clone ( ) ) ,
384- scarcity_price,
385- ) ;
376+ existing_prices. insert ( commodity, region, time_slice, scarcity_price) ;
386377 }
387-
388- scarcity_prices
389378}
390379
391380/// Extend an existing commodity/region/time-slice price map by applying each
392381/// selection-level price to all time slices within that selection.
393382fn extend_selection_prices (
394- prices : & mut IndexMap < ( CommodityID , RegionID , TimeSliceID ) , MoneyPerFlow > ,
383+ prices : & mut CommodityPrices ,
395384 group_prices : & IndexMap < ( CommodityID , RegionID , TimeSliceSelection ) , MoneyPerFlow > ,
396385 time_slice_info : & TimeSliceInfo ,
397386) {
398387 for ( ( commodity_id, region_id, selection) , & selection_price) in group_prices {
399388 for ( time_slice_id, _) in selection. iter ( time_slice_info) {
400- let key = (
401- commodity_id. clone ( ) ,
402- region_id. clone ( ) ,
403- time_slice_id. clone ( ) ,
404- ) ;
405- let existing = prices. insert ( key. clone ( ) , selection_price) . is_some ( ) ;
406- assert ! ( !existing, "Key {key:?} already exists in the map" ) ;
389+ prices. insert ( commodity_id, region_id, time_slice_id, selection_price) ;
407390 }
408391 }
409392}
410393
411- /// Calculate marginal cost prices for a set of commodities.
394+ /// Calculate marginal cost prices for a set of commodities and add to an existing prices map .
412395///
413396/// This pricing strategy aims to incorporate the marginal cost of commodity production into the price.
414397///
@@ -452,25 +435,21 @@ fn extend_selection_prices(
452435/// * `activity_for_existing` - Iterator over `(asset, time_slice, activity)` from optimisation
453436/// solution for existing assets
454437/// * `activity_keys_for_candidates` - Iterator over `(asset, time_slice)` for candidate assets
455- /// * `upstream_prices` - Prices for commodities upstream of the ones we are calculating prices for
438+ /// * `existing_prices` - Existing prices to use as inputs and extend. This is expected to include
439+ /// prices from all markets upstream of the markets we are calculating for.
456440/// * `year` - The year for which prices are being calculated
457441/// * `markets_to_price` - Set of markets to calculate marginal prices for
458442/// * `commodities` - Map of all commodities (used to look up each commodity's `time_slice_level`)
459443/// * `time_slice_info` - Time slice information (used to expand groups to individual time slices)
460- ///
461- /// # Returns
462- ///
463- /// A map of marginal cost prices for the specified markets in all time slices
464- fn calculate_marginal_cost_prices < ' a , I , J > (
444+ fn add_marginal_cost_prices < ' a , I , J > (
465445 activity_for_existing : I ,
466446 activity_keys_for_candidates : J ,
467- upstream_prices : & CommodityPrices ,
447+ existing_prices : & mut CommodityPrices ,
468448 year : u32 ,
469449 markets_to_price : & HashSet < ( CommodityID , RegionID ) > ,
470450 commodities : & CommodityMap ,
471451 time_slice_info : & TimeSliceInfo ,
472- ) -> IndexMap < ( CommodityID , RegionID , TimeSliceID ) , MoneyPerFlow >
473- where
452+ ) where
474453 I : Iterator < Item = ( & ' a AssetRef , & ' a TimeSliceID , Activity ) > ,
475454 J : Iterator < Item = ( & ' a AssetRef , & ' a TimeSliceID ) > ,
476455{
@@ -495,7 +474,7 @@ where
495474
496475 // Iterate over the marginal costs for commodities we need prices for
497476 for ( commodity_id, marginal_cost) in asset. iter_marginal_costs_with_filter (
498- upstream_prices ,
477+ existing_prices ,
499478 year,
500479 time_slice,
501480 |cid : & CommodityID | markets_to_price. contains ( & ( cid. clone ( ) , region_id. clone ( ) ) ) ,
@@ -550,7 +529,7 @@ where
550529
551530 // Iterate over the marginal costs for commodities we need prices for
552531 for ( commodity_id, marginal_cost) in asset. iter_marginal_costs_with_filter (
553- upstream_prices ,
532+ existing_prices ,
554533 year,
555534 time_slice,
556535 |cid : & CommodityID | markets_to_price. contains ( & ( cid. clone ( ) , region_id. clone ( ) ) ) ,
@@ -588,13 +567,12 @@ where
588567 . map ( |v| ( key, v) )
589568 } ) ;
590569
591- // Merge existing and candidate group prices, then expand to individual time slices
570+ // Merge existing and candidate group prices
592571 let mut all_group_prices = group_prices;
593572 all_group_prices. extend ( cand_group_prices) ;
594573
595- let mut prices = IndexMap :: new ( ) ;
596- extend_selection_prices ( & mut prices, & all_group_prices, time_slice_info) ;
597- prices
574+ // Expand selection-level prices to individual time slices and add to the main prices map
575+ extend_selection_prices ( existing_prices, & all_group_prices, time_slice_info) ;
598576}
599577
600578/// Calculate annual activities for each asset by summing across all time slices
@@ -613,7 +591,7 @@ where
613591 } )
614592}
615593
616- /// Calculate full cost prices for a set of commodities.
594+ /// Calculate full cost prices for a set of commodities and add to an existing prices map .
617595///
618596/// This pricing strategy aims to incorporate the full cost of commodity production into the price.
619597///
@@ -664,27 +642,23 @@ where
664642/// * `activity_for_existing` - Iterator over `(asset, time_slice, activity)` from optimisation
665643/// solution for existing assets
666644/// * `activity_keys_for_candidates` - Iterator over `(asset, time_slice)` for candidate assets
667- /// * `upstream_prices` - Prices for commodities upstream of the ones we are calculating prices for
645+ /// * `existing_prices` - Existing prices to use as inputs and extend. This is expected to include p
646+ /// prices from all markets upstream of the markets we are calculating for.
668647/// * `year` - The year for which prices are being calculated
669648/// * `markets_to_price` - Set of markets to calculate full cost prices for
670649/// * `commodities` - Map of all commodities (used to look up each commodity's `time_slice_level`)
671650/// * `time_slice_info` - Time slice information (used to expand groups to individual time slices)
672- ///
673- /// # Returns
674- ///
675- /// A map of full cost prices for the specified markets in all time slices
676651#[ allow( clippy:: too_many_arguments, clippy:: too_many_lines) ]
677- fn calculate_full_cost_prices < ' a , I , J > (
652+ fn add_full_cost_prices < ' a , I , J > (
678653 activity_for_existing : I ,
679654 activity_keys_for_candidates : J ,
680655 annual_activities : & HashMap < AssetRef , Activity > ,
681- upstream_prices : & CommodityPrices ,
656+ existing_prices : & mut CommodityPrices ,
682657 year : u32 ,
683658 markets_to_price : & HashSet < ( CommodityID , RegionID ) > ,
684659 commodities : & CommodityMap ,
685660 time_slice_info : & TimeSliceInfo ,
686- ) -> IndexMap < ( CommodityID , RegionID , TimeSliceID ) , MoneyPerFlow >
687- where
661+ ) where
688662 I : Iterator < Item = ( & ' a AssetRef , & ' a TimeSliceID , Activity ) > ,
689663 J : Iterator < Item = ( & ' a AssetRef , & ' a TimeSliceID ) > ,
690664{
@@ -719,7 +693,7 @@ where
719693
720694 // Iterate over the marginal costs for commodities we need prices for
721695 for ( commodity_id, marginal_cost) in asset. iter_marginal_costs_with_filter (
722- upstream_prices ,
696+ existing_prices ,
723697 year,
724698 time_slice,
725699 |cid : & CommodityID | markets_to_price. contains ( & ( cid. clone ( ) , region_id. clone ( ) ) ) ,
@@ -779,7 +753,7 @@ where
779753
780754 // Iterate over the marginal costs for commodities we need prices for
781755 for ( commodity_id, marginal_cost) in asset. iter_marginal_costs_with_filter (
782- upstream_prices ,
756+ existing_prices ,
783757 year,
784758 time_slice,
785759 |cid : & CommodityID | markets_to_price. contains ( & ( cid. clone ( ) , region_id. clone ( ) ) ) ,
@@ -830,13 +804,12 @@ where
830804 . map ( |v| ( key, v) )
831805 } ) ;
832806
833- // Merge existing and candidate group prices, then expand to individual time slices
807+ // Merge existing and candidate group prices
834808 let mut all_group_prices = group_prices;
835809 all_group_prices. extend ( cand_group_prices) ;
836810
837- let mut prices = IndexMap :: new ( ) ;
838- extend_selection_prices ( & mut prices, & all_group_prices, time_slice_info) ;
839- prices
811+ // Expand selection-level prices to individual time slices and add to the main prices map
812+ extend_selection_prices ( existing_prices, & all_group_prices, time_slice_info) ;
840813}
841814
842815#[ cfg( test) ]
@@ -920,14 +893,14 @@ mod tests {
920893 }
921894
922895 fn assert_price_approx (
923- prices : & IndexMap < ( CommodityID , RegionID , TimeSliceID ) , MoneyPerFlow > ,
896+ prices : & CommodityPrices ,
924897 commodity : & CommodityID ,
925898 region : & RegionID ,
926899 time_slice : & TimeSliceID ,
927900 expected : MoneyPerFlow ,
928901 ) {
929- let p = prices[ & ( commodity . clone ( ) , region. clone ( ) , time_slice. clone ( ) ) ] ;
930- assert ! ( ( p - expected) . abs ( ) < MoneyPerFlow :: EPSILON ) ;
902+ let p = prices. get ( commodity , region, time_slice) . unwrap ( ) ;
903+ assert_approx_eq ! ( MoneyPerFlow , p , expected) ;
931904 }
932905
933906 #[ rstest]
@@ -1038,10 +1011,11 @@ mod tests {
10381011 let existing = vec ! [ ( & asset_ref, & time_slice, Activity ( 1.0 ) ) ] ;
10391012 let candidates = Vec :: new ( ) ;
10401013
1041- let prices = calculate_marginal_cost_prices (
1014+ let mut prices = shadow_prices. clone ( ) ;
1015+ add_marginal_cost_prices (
10421016 existing. into_iter ( ) ,
10431017 candidates. into_iter ( ) ,
1044- & shadow_prices ,
1018+ & mut prices ,
10451019 2015u32 ,
10461020 & markets,
10471021 & commodities,
@@ -1123,11 +1097,12 @@ mod tests {
11231097 let mut annual_activities = HashMap :: new ( ) ;
11241098 annual_activities. insert ( asset_ref. clone ( ) , Activity ( 2.0 ) ) ;
11251099
1126- let prices = calculate_full_cost_prices (
1100+ let mut prices = shadow_prices. clone ( ) ;
1101+ add_full_cost_prices (
11271102 existing. into_iter ( ) ,
11281103 candidates. into_iter ( ) ,
11291104 & annual_activities,
1130- & shadow_prices ,
1105+ & mut prices ,
11311106 2015u32 ,
11321107 & markets,
11331108 & commodities,
0 commit comments