Skip to content

Commit 7c2e460

Browse files
committed
Add prices to existing maps rather than creating new maps
1 parent 2e177d5 commit 7c2e460

1 file changed

Lines changed: 46 additions & 71 deletions

File tree

src/simulation/prices.rs

Lines changed: 46 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
393382
fn 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

Comments
 (0)