@@ -1167,3 +1167,92 @@ TEST(MaxFlow, Sensitivity_PartialSaturation) {
11671167 }
11681168 EXPECT_EQ (count, 2 );
11691169}
1170+
1171+ TEST (MaxFlow, Sensitivity_ShortestPathVsMaxFlow) {
1172+ // Tests that shortest_path mode produces different sensitivity results
1173+ // than full max-flow mode when there are paths of different costs.
1174+ //
1175+ // Topology: S(0) -> A(1) -> T(2) [cost 1+1=2, cap 10 each]
1176+ // S(0) -> B(3) -> T(2) [cost 2+2=4, cap 5 each]
1177+ //
1178+ // With shortest_path=false (full max-flow):
1179+ // - Uses both paths: S->A->T (10) + S->B->T (5) = 15 total
1180+ // - All 4 edges are saturated and critical
1181+ //
1182+ // With shortest_path=true (single-pass, IP/IGP mode):
1183+ // - Only uses cheapest path: S->A->T (10)
1184+ // - Edges 2,3 (S->B->T path) are NOT used, so NOT critical
1185+
1186+ std::int32_t src[4 ] = {0 , 1 , 0 , 3 };
1187+ std::int32_t dst[4 ] = {1 , 2 , 3 , 2 };
1188+ double cap[4 ] = {10.0 , 10.0 , 5.0 , 5.0 };
1189+ std::int64_t cost[4 ] = {1 , 1 , 2 , 2 }; // S->A->T costs 2, S->B->T costs 4
1190+
1191+ auto g = StrictMultiDiGraph::from_arrays (4 ,
1192+ std::span (src, 4 ), std::span (dst, 4 ),
1193+ std::span (cap, 4 ), std::span (cost, 4 ));
1194+
1195+ auto be = make_cpu_backend ();
1196+ Algorithms algs (be);
1197+ auto gh = algs.build_graph (g);
1198+
1199+ // Step 1: Verify baseline flow values with max_flow
1200+ // This validates the routing semantics before testing sensitivity
1201+ {
1202+ MaxFlowOptions opts;
1203+ opts.placement = FlowPlacement::Proportional;
1204+ opts.shortest_path = false ;
1205+ auto [flow_full, _] = algs.max_flow (gh, 0 , 2 , opts);
1206+ EXPECT_NEAR (flow_full, 15.0 , 1e-9 ) << " Full max-flow should be 15" ;
1207+ }
1208+ {
1209+ MaxFlowOptions opts;
1210+ opts.placement = FlowPlacement::Proportional;
1211+ opts.shortest_path = true ;
1212+ auto [flow_sp, _] = algs.max_flow (gh, 0 , 2 , opts);
1213+ EXPECT_NEAR (flow_sp, 10.0 , 1e-9 ) << " Shortest-path flow should be 10" ;
1214+ }
1215+
1216+ // Step 2: Test sensitivity with shortest_path=false (full max-flow)
1217+ {
1218+ MaxFlowOptions opts;
1219+ opts.placement = FlowPlacement::Proportional;
1220+ opts.shortest_path = false ;
1221+
1222+ auto results = algs.sensitivity_analysis (gh, 0 , 2 , opts);
1223+
1224+ // All 4 edges should be saturated and critical
1225+ EXPECT_EQ (results.size (), 4u ) << " Full max-flow should report all 4 edges as critical" ;
1226+
1227+ // Edges 0,1 (S->A->T path) have delta 10 when removed
1228+ // Edges 2,3 (S->B->T path) have delta 5 when removed
1229+ for (const auto & p : results) {
1230+ if (p.first == 0 || p.first == 1 ) {
1231+ EXPECT_NEAR (p.second , 10.0 , 1e-9 );
1232+ } else {
1233+ EXPECT_NEAR (p.second , 5.0 , 1e-9 );
1234+ }
1235+ }
1236+ }
1237+
1238+ // Step 3: Test sensitivity with shortest_path=true (IP/IGP mode)
1239+ {
1240+ MaxFlowOptions opts;
1241+ opts.placement = FlowPlacement::Proportional;
1242+ opts.shortest_path = true ;
1243+
1244+ auto results = algs.sensitivity_analysis (gh, 0 , 2 , opts);
1245+
1246+ // Only edges 0,1 (S->A->T path) should be critical
1247+ // Edges 2,3 (S->B->T path) are not used under shortest-path routing
1248+ EXPECT_EQ (results.size (), 2u ) << " Shortest-path mode should only report 2 edges as critical" ;
1249+
1250+ for (const auto & p : results) {
1251+ EXPECT_TRUE (p.first == 0 || p.first == 1 )
1252+ << " Edge " << p.first << " should not be critical in shortest-path mode" ;
1253+ // Baseline=10, removing either S->A or A->T forces traffic to S->B->T (cap 5)
1254+ // Delta = 10 - 5 = 5
1255+ EXPECT_NEAR (p.second , 5.0 , 1e-9 );
1256+ }
1257+ }
1258+ }
0 commit comments