@@ -6,32 +6,37 @@ use crate::model::Model;
66use crate :: region:: RegionID ;
77use crate :: simulation:: optimisation:: Solution ;
88use 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 } ;
1010use anyhow:: Result ;
1111use indexmap:: IndexMap ;
1212use 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