@@ -14,6 +14,7 @@ use anyhow::{Context, Result, bail, ensure};
1414use indexmap:: IndexMap ;
1515use itertools:: { Itertools , chain} ;
1616use log:: debug;
17+ use std:: cmp:: Ordering ;
1718use std:: collections:: HashMap ;
1819use std:: fmt:: Display ;
1920
@@ -721,7 +722,11 @@ fn select_best_assets(
721722 let assets_sorted_by_metric = outputs_for_opts
722723 . into_iter ( )
723724 . filter ( |output| output. capacity > Capacity ( 0.0 ) )
724- . sorted_by ( AppraisalOutput :: compare_metric)
725+ . sorted_by ( |output1, output2| match output1. compare_metric ( output2) {
726+ // If equal, we fall back on comparing asset properties
727+ Ordering :: Equal => compare_asset_fallback ( & output1. asset , & output2. asset ) ,
728+ cmp => cmp,
729+ } )
725730 . collect_vec ( ) ;
726731
727732 // Check if all options have zero capacity. If so, we cannot meet demand, so have to bail
@@ -787,6 +792,14 @@ fn is_any_remaining_demand(demand: &DemandMap) -> bool {
787792 demand. values ( ) . any ( |flow| * flow > Flow ( 0.0 ) )
788793}
789794
795+ /// Compare assets, sorting commissioned before uncommissioned and newer before older.
796+ ///
797+ /// Used as a fallback to sort assets when they have equal appraisal tool outputs.
798+ fn compare_asset_fallback ( asset1 : & Asset , asset2 : & Asset ) -> Ordering {
799+ ( asset2. is_commissioned ( ) , asset2. commission_year ( ) )
800+ . cmp ( & ( asset1. is_commissioned ( ) , asset1. commission_year ( ) ) )
801+ }
802+
790803/// Update capacity of chosen asset, if needed, and update both asset options and chosen assets
791804fn update_assets (
792805 mut best_asset : AssetRef ,
@@ -833,14 +846,17 @@ fn update_assets(
833846#[ cfg( test) ]
834847mod tests {
835848 use super :: * ;
849+ use crate :: agent:: AgentID ;
850+ use crate :: asset:: Asset ;
836851 use crate :: commodity:: Commodity ;
837852 use crate :: fixture:: {
838- asset, process, process_activity_limits_map, process_flows_map, svd_commodity , time_slice ,
839- time_slice_info, time_slice_info2,
853+ agent_id , asset, process, process_activity_limits_map, process_flows_map, region_id ,
854+ svd_commodity , time_slice , time_slice_info, time_slice_info2,
840855 } ;
841856 use crate :: process:: { ActivityLimits , FlowType , Process , ProcessFlow } ;
857+ use crate :: region:: RegionID ;
842858 use crate :: time_slice:: { TimeSliceID , TimeSliceInfo } ;
843- use crate :: units:: { Flow , FlowPerActivity , MoneyPerFlow } ;
859+ use crate :: units:: { Capacity , Flow , FlowPerActivity , MoneyPerFlow } ;
844860 use indexmap:: indexmap;
845861 use rstest:: rstest;
846862 use std:: rc:: Rc ;
@@ -926,4 +942,32 @@ mod tests {
926942 // Maximum = 20.0
927943 assert_eq ! ( result, Capacity ( 20.0 ) ) ;
928944 }
945+
946+ #[ rstest]
947+ fn test_compare_assets_fallback ( process : Process , region_id : RegionID , agent_id : AgentID ) {
948+ let process = Rc :: new ( process) ;
949+ let capacity = Capacity ( 2.0 ) ;
950+ let asset1 = Asset :: new_commissioned (
951+ agent_id. clone ( ) ,
952+ process. clone ( ) ,
953+ region_id. clone ( ) ,
954+ capacity,
955+ 2015 ,
956+ )
957+ . unwrap ( ) ;
958+ let asset2 =
959+ Asset :: new_candidate ( process. clone ( ) , region_id. clone ( ) , capacity, 2015 ) . unwrap ( ) ;
960+ let asset3 =
961+ Asset :: new_commissioned ( agent_id, process, region_id. clone ( ) , capacity, 2010 ) . unwrap ( ) ;
962+
963+ assert ! ( compare_asset_fallback( & asset1, & asset1) . is_eq( ) ) ;
964+ assert ! ( compare_asset_fallback( & asset2, & asset2) . is_eq( ) ) ;
965+ assert ! ( compare_asset_fallback( & asset3, & asset3) . is_eq( ) ) ;
966+ assert ! ( compare_asset_fallback( & asset1, & asset2) . is_lt( ) ) ;
967+ assert ! ( compare_asset_fallback( & asset2, & asset1) . is_gt( ) ) ;
968+ assert ! ( compare_asset_fallback( & asset1, & asset3) . is_lt( ) ) ;
969+ assert ! ( compare_asset_fallback( & asset3, & asset1) . is_gt( ) ) ;
970+ assert ! ( compare_asset_fallback( & asset3, & asset2) . is_lt( ) ) ;
971+ assert ! ( compare_asset_fallback( & asset2, & asset3) . is_gt( ) ) ;
972+ }
929973}
0 commit comments