@@ -556,7 +556,7 @@ where
556556 assert ! ( matches!(
557557 ( pricing_strategy, annual_activities) ,
558558 ( PricingStrategy :: MarginalCost , _) | ( PricingStrategy :: FullCost , Some ( _) )
559- ) , ) ;
559+ ) ) ;
560560
561561 // Accumulator map to collect costs from existing assets. For each (commodity, region,
562562 // ts selection), this maps each asset to a weighted average of the costs for that
@@ -653,7 +653,7 @@ where
653653/// * `markets_to_price` - Set of (commodity, region) pairs to attempt to price
654654/// * `existing_prices` - Current commodity prices (used to calculate marginal costs)
655655/// * `priced_groups` - Set of (commodity, region, time slice selection) groups that have already
656- /// been prices using existing assets, so should be skipped when looking at candidates
656+ /// been priced using existing assets, so should be skipped when looking at candidates
657657/// * `year` - Year for which prices are being calculated
658658/// * `commodities` - Commodity map
659659/// * `pricing_strategy` - Pricing strategy, either `MarginalCost` or `FullCost`
@@ -684,6 +684,9 @@ where
684684 // Cache of annual fixed costs per flow for each asset (only used for Full cost pricing)
685685 let mut annual_fixed_costs: HashMap < _ , _ > = HashMap :: new ( ) ;
686686
687+ // Cache of annual activity limits for each asset (only used for Full cost pricing)
688+ let mut annual_activity_limits: HashMap < _ , _ > = HashMap :: new ( ) ;
689+
687690 // Accumulator map to collect costs from candidate assets. Similar to existing_accum,
688691 // but costs are weighted according to activity limits (i.e. assuming full utilisation).
689692 let mut cand_accum: IndexMap <
@@ -695,6 +698,22 @@ where
695698 for ( asset, time_slice) in activity_keys_for_candidates {
696699 let region_id = asset. region_id ( ) ;
697700
701+ // When using full cost pricing, skip assets with a zero upper limit on annual activity,
702+ // since we cannot calculate a fixed cost per flow.
703+ let annual_activity_limit =
704+ matches ! ( pricing_strategy, PricingStrategy :: FullCost ) . then ( || {
705+ * annual_activity_limits
706+ . entry ( asset. clone ( ) )
707+ . or_insert_with ( || {
708+ * asset
709+ . get_activity_limits_for_selection ( & TimeSliceSelection :: Annual )
710+ . end ( )
711+ } )
712+ } ) ;
713+ if annual_activity_limit. is_some_and ( |limit| limit < Activity :: EPSILON ) {
714+ continue ;
715+ }
716+
698717 // Get activity limits: used to weight marginal costs for seasonal/annual commodities
699718 let activity_limit = * asset
700719 . get_activity_limits_for_selection ( & TimeSliceSelection :: Single ( time_slice. clone ( ) ) )
@@ -724,13 +743,11 @@ where
724743 // Calculate total cost (marginal + fixed if applicable)
725744 let total_cost = match pricing_strategy {
726745 PricingStrategy :: FullCost => {
746+ // Get fixed costs assuming full utilisation (i.e. using the activity limit)
747+ // Input-stage validation should ensure that this limit is never zero
727748 let annual_fixed_costs_per_flow =
728749 annual_fixed_costs. entry ( asset. clone ( ) ) . or_insert_with ( || {
729- asset. get_annual_fixed_costs_per_flow (
730- * asset
731- . get_activity_limits_for_selection ( & TimeSliceSelection :: Annual )
732- . end ( ) ,
733- )
750+ asset. get_annual_fixed_costs_per_flow ( annual_activity_limit. unwrap ( ) )
734751 } ) ;
735752 marginal_cost + * annual_fixed_costs_per_flow
736753 }
0 commit comments