@@ -105,12 +105,19 @@ pub fn pareto_frontier(paths: &[TransformPath], cap: Option<usize>) -> Vec<Trans
105105 }
106106
107107 // Sort by total_cost ascending (cheapest first).
108+ // Paths with NaN metrics sort as equal to each other due to partial_cmp fallback.
108109 frontier. sort_by ( |a, b| {
109110 a. total_cost
110111 . partial_cmp ( & b. total_cost )
111112 . unwrap_or ( std:: cmp:: Ordering :: Equal )
112113 } ) ;
113114
115+ // Deduplicate paths with identical objectives: two paths that share both
116+ // total_cost and total_quality represent the same trade-off point and only
117+ // one needs to be kept in the frontier.
118+ let mut seen = std:: collections:: HashSet :: new ( ) ;
119+ frontier. retain ( |p| seen. insert ( ( p. total_cost . to_bits ( ) , p. total_quality . to_bits ( ) ) ) ) ;
120+
114121 if let Some ( limit) = cap {
115122 frontier. truncate ( limit) ;
116123 }
@@ -339,12 +346,11 @@ mod tests {
339346 }
340347
341348 #[ test]
342- fn test_pareto_frontier_equal_paths_not_dominated ( ) {
343- // Two identical paths: neither dominates the other (no strict improvement) .
349+ fn test_pareto_frontier_equal_paths_deduplicated ( ) {
350+ // Two paths with identical objectives: only one should be kept .
344351 let paths = vec ! [ make_path( 1.0 , 0.9 ) , make_path( 1.0 , 0.9 ) ] ;
345352 let frontier = pareto_frontier ( & paths, None ) ;
346- // Both are kept since neither strictly dominates the other.
347- assert_eq ! ( frontier. len( ) , 2 ) ;
353+ assert_eq ! ( frontier. len( ) , 1 ) ;
348354 }
349355
350356 #[ test]
0 commit comments