Skip to content

Commit 95f2083

Browse files
committed
Make WeightedAverageAccumulator generic
1 parent c66970a commit 95f2083

1 file changed

Lines changed: 38 additions & 28 deletions

File tree

src/simulation/prices.rs

Lines changed: 38 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,32 +6,37 @@ use crate::model::Model;
66
use crate::region::RegionID;
77
use crate::simulation::optimisation::Solution;
88
use crate::time_slice::{TimeSliceID, TimeSliceInfo, TimeSliceSelection};
9-
use crate::units::{Activity, Dimensionless, MoneyPerActivity, MoneyPerFlow, Year};
9+
use crate::units::{Activity, Dimensionless, Flow, MoneyPerActivity, MoneyPerFlow, UnitType, Year};
1010
use anyhow::Result;
1111
use indexmap::IndexMap;
1212
use std::collections::{HashMap, HashSet};
13+
use std::marker::PhantomData;
1314

1415
/// Weighted average accumulator for `MoneyPerFlow` prices.
1516
#[derive(Clone, Copy, Debug)]
16-
struct WeightedAverageAccumulator {
17+
struct WeightedAverageAccumulator<W: UnitType> {
1718
/// The numerator of the weighted average, i.e. the sum of value * weight across all entries.
1819
numerator: MoneyPerFlow,
1920
/// The denominator of the weighted average, i.e. the sum of weights across all entries.
2021
denominator: Dimensionless,
22+
/// Marker to bind this accumulator to the configured weight unit type.
23+
_weight_type: PhantomData<W>,
2124
}
2225

23-
impl Default for WeightedAverageAccumulator {
26+
impl<W: UnitType> Default for WeightedAverageAccumulator<W> {
2427
fn default() -> Self {
2528
Self {
2629
numerator: MoneyPerFlow(0.0),
2730
denominator: Dimensionless(0.0),
31+
_weight_type: PhantomData,
2832
}
2933
}
3034
}
3135

32-
impl WeightedAverageAccumulator {
36+
impl<W: UnitType> WeightedAverageAccumulator<W> {
3337
/// Add a weighted value to the accumulator.
34-
fn add(&mut self, value: MoneyPerFlow, weight: Dimensionless) {
38+
fn add(&mut self, value: MoneyPerFlow, weight: W) {
39+
let weight = Dimensionless(weight.value());
3540
self.numerator += value * weight;
3641
self.denominator += weight;
3742
}
@@ -45,17 +50,26 @@ impl WeightedAverageAccumulator {
4550
}
4651

4752
/// Weighted average accumulator with a backup weighting path for `MoneyPerFlow` prices.
48-
#[derive(Clone, Copy, Debug, Default)]
49-
struct WeightedAverageBackupAccumulator {
53+
#[derive(Clone, Copy, Debug)]
54+
struct WeightedAverageBackupAccumulator<W: UnitType> {
5055
/// Primary weighted average path.
51-
primary: WeightedAverageAccumulator,
56+
primary: WeightedAverageAccumulator<W>,
5257
/// Backup weighted average path.
53-
backup: WeightedAverageAccumulator,
58+
backup: WeightedAverageAccumulator<W>,
59+
}
60+
61+
impl<W: UnitType> Default for WeightedAverageBackupAccumulator<W> {
62+
fn default() -> Self {
63+
Self {
64+
primary: WeightedAverageAccumulator::<W>::default(),
65+
backup: WeightedAverageAccumulator::<W>::default(),
66+
}
67+
}
5468
}
5569

56-
impl WeightedAverageBackupAccumulator {
70+
impl<W: UnitType> WeightedAverageBackupAccumulator<W> {
5771
/// Add a weighted value to the accumulator with a backup weight.
58-
fn add(&mut self, value: MoneyPerFlow, weight: Dimensionless, backup_weight: Dimensionless) {
72+
fn add(&mut self, value: MoneyPerFlow, weight: W, backup_weight: W) {
5973
self.primary.add(value, weight);
6074
self.backup.add(value, backup_weight);
6175
}
@@ -565,7 +579,7 @@ where
565579
// the selection depends on the time slice level of the commodity (i.e. individual, season, year).
566580
let mut existing_accum: IndexMap<
567581
(CommodityID, RegionID, TimeSliceSelection),
568-
IndexMap<AssetRef, WeightedAverageBackupAccumulator>,
582+
IndexMap<AssetRef, WeightedAverageBackupAccumulator<Activity>>,
569583
> = IndexMap::new();
570584

571585
// Cache of annual fixed costs per flow for each asset (only used for Full cost pricing)
@@ -620,11 +634,7 @@ where
620634
.or_default()
621635
.entry(asset.clone())
622636
.or_default()
623-
.add(
624-
total_cost,
625-
Dimensionless(activity.value()),
626-
Dimensionless(activity_limit.value()),
627-
);
637+
.add(total_cost, activity, activity_limit);
628638
}
629639
}
630640

@@ -691,7 +701,7 @@ where
691701
// but costs are weighted according to activity limits (i.e. assuming full utilisation).
692702
let mut cand_accum: IndexMap<
693703
(CommodityID, RegionID, TimeSliceSelection),
694-
IndexMap<AssetRef, WeightedAverageAccumulator>,
704+
IndexMap<AssetRef, WeightedAverageAccumulator<Activity>>,
695705
> = IndexMap::new();
696706

697707
// Iterate over candidate assets (assuming full utilisation)
@@ -761,7 +771,7 @@ where
761771
.or_default()
762772
.entry(asset.clone())
763773
.or_default()
764-
.add(total_cost, Dimensionless(activity_limit.value()));
774+
.add(total_cost, activity_limit);
765775
}
766776
}
767777

@@ -874,7 +884,7 @@ where
874884
// level of the commodity (i.e. individual, season, year).
875885
let mut existing_accum: IndexMap<
876886
(CommodityID, RegionID, TimeSliceSelection),
877-
WeightedAverageBackupAccumulator,
887+
WeightedAverageBackupAccumulator<Flow>,
878888
> = IndexMap::new();
879889

880890
// Cache of annual fixed costs per flow for each asset (only used for Full cost pricing)
@@ -927,8 +937,8 @@ where
927937
.get_flow(&commodity_id)
928938
.expect("Commodity should be an output flow for this asset")
929939
.coeff;
930-
let output_weight = Dimensionless((activity * output_coeff).value());
931-
let backup_output_weight = Dimensionless((activity_limit * output_coeff).value());
940+
let output_weight = activity * output_coeff;
941+
let backup_output_weight = activity_limit * output_coeff;
932942

933943
// Accumulate cost for this group, weighted by output with a backup
934944
// potential-output weight.
@@ -1424,14 +1434,14 @@ mod tests {
14241434

14251435
#[test]
14261436
fn weighted_average_accumulator_single_value() {
1427-
let mut accum = WeightedAverageAccumulator::default();
1437+
let mut accum = WeightedAverageAccumulator::<Dimensionless>::default();
14281438
accum.add(MoneyPerFlow(100.0), Dimensionless(1.0));
14291439
assert_eq!(accum.finalise(), Some(MoneyPerFlow(100.0)));
14301440
}
14311441

14321442
#[test]
14331443
fn weighted_average_accumulator_different_weights() {
1434-
let mut accum = WeightedAverageAccumulator::default();
1444+
let mut accum = WeightedAverageAccumulator::<Dimensionless>::default();
14351445
accum.add(MoneyPerFlow(100.0), Dimensionless(1.0));
14361446
accum.add(MoneyPerFlow(200.0), Dimensionless(2.0));
14371447
// (100*1 + 200*2) / (1+2) = 500/3 ≈ 166.667
@@ -1441,13 +1451,13 @@ mod tests {
14411451

14421452
#[test]
14431453
fn weighted_average_accumulator_zero_weight() {
1444-
let accum = WeightedAverageAccumulator::default();
1454+
let accum = WeightedAverageAccumulator::<Dimensionless>::default();
14451455
assert_eq!(accum.finalise(), None);
14461456
}
14471457

14481458
#[test]
14491459
fn weighted_average_backup_accumulator_primary_preferred() {
1450-
let mut accum = WeightedAverageBackupAccumulator::default();
1460+
let mut accum = WeightedAverageBackupAccumulator::<Dimensionless>::default();
14511461
accum.add(MoneyPerFlow(100.0), Dimensionless(3.0), Dimensionless(1.0));
14521462
accum.add(MoneyPerFlow(200.0), Dimensionless(1.0), Dimensionless(1.0));
14531463
// Primary is non-zero, use it: (100*3 + 200*1) / (3+1) = 125
@@ -1457,7 +1467,7 @@ mod tests {
14571467

14581468
#[test]
14591469
fn weighted_average_backup_accumulator_fallback() {
1460-
let mut accum = WeightedAverageBackupAccumulator::default();
1470+
let mut accum = WeightedAverageBackupAccumulator::<Dimensionless>::default();
14611471
accum.add(MoneyPerFlow(100.0), Dimensionless(0.0), Dimensionless(2.0));
14621472
accum.add(MoneyPerFlow(200.0), Dimensionless(0.0), Dimensionless(2.0));
14631473
// Primary is zero, fallback to backup: (100*2 + 200*2) / (2+2) = 150
@@ -1466,7 +1476,7 @@ mod tests {
14661476

14671477
#[test]
14681478
fn weighted_average_backup_accumulator_both_zero() {
1469-
let accum = WeightedAverageBackupAccumulator::default();
1479+
let accum = WeightedAverageBackupAccumulator::<Dimensionless>::default();
14701480
assert_eq!(accum.finalise(), None);
14711481
}
14721482
}

0 commit comments

Comments
 (0)