@@ -285,17 +285,23 @@ inline void expect_csr_valid(const StrictMultiDiGraph& g) {
285285 auto row = g.row_offsets_view ();
286286 auto col = g.col_indices_view ();
287287 auto aei = g.adj_edge_index_view ();
288+ auto edge_src = g.edge_src_view ();
289+ auto edge_dst = g.edge_dst_view ();
290+ const auto M = static_cast <std::size_t >(g.num_edges ());
291+ const auto N = static_cast <std::size_t >(g.num_nodes ());
292+
293+ // --- Forward CSR structural checks ---
288294
289295 // Offsets should be monotonic
290- EXPECT_EQ (row.size (), static_cast <std:: size_t >(g. num_nodes () + 1 ) );
296+ EXPECT_EQ (row.size (), N + 1 );
291297 for (std::size_t i = 0 ; i < row.size () - 1 ; ++i) {
292298 EXPECT_LE (row[i], row[i + 1 ]) << " Row offsets not monotonic at " << i;
293299 }
294300
295301 // Total edges match
296- EXPECT_EQ (row[row.size () - 1 ], g. num_edges () );
297- EXPECT_EQ (col.size (), static_cast <std:: size_t >(g. num_edges ()) );
298- EXPECT_EQ (aei.size (), static_cast <std:: size_t >(g. num_edges ()) );
302+ EXPECT_EQ (static_cast <std:: size_t >( row[row.size () - 1 ]), M );
303+ EXPECT_EQ (col.size (), M );
304+ EXPECT_EQ (aei.size (), M );
299305
300306 // All column indices and edge indices in range
301307 for (std::size_t i = 0 ; i < col.size (); ++i) {
@@ -304,6 +310,75 @@ inline void expect_csr_valid(const StrictMultiDiGraph& g) {
304310 EXPECT_GE (aei[i], 0 ) << " Invalid edge index at " << i;
305311 EXPECT_LT (aei[i], g.num_edges ()) << " Edge index out of range at " << i;
306312 }
313+
314+ // --- Forward CSR semantic checks ---
315+
316+ // Bijection: every edge appears exactly once in the CSR
317+ std::vector<int > edge_count (M, 0 );
318+ for (std::size_t u = 0 ; u < N; ++u) {
319+ auto s = static_cast <std::size_t >(row[u]);
320+ auto e = static_cast <std::size_t >(row[u + 1 ]);
321+ for (std::size_t j = s; j < e; ++j) {
322+ auto eid = static_cast <std::size_t >(aei[j]);
323+ ASSERT_LT (eid, M) << " Edge index out of range at CSR pos " << j;
324+ edge_count[eid]++;
325+
326+ // Source consistency: edge must originate from node u
327+ EXPECT_EQ (static_cast <std::size_t >(edge_src[eid]), u)
328+ << " Forward CSR: edge " << eid << " in node " << u
329+ << " 's adjacency has src=" << edge_src[eid];
330+
331+ // Destination consistency: col_indices must match edge destination
332+ EXPECT_EQ (col[j], edge_dst[eid])
333+ << " Forward CSR: col_indices[" << j << " ]=" << col[j]
334+ << " doesn't match edge_dst[" << eid << " ]=" << edge_dst[eid];
335+ }
336+ }
337+ for (std::size_t eid = 0 ; eid < M; ++eid) {
338+ EXPECT_EQ (edge_count[eid], 1 )
339+ << " Edge " << eid << " appears " << edge_count[eid]
340+ << " times in forward CSR (expected exactly 1)" ;
341+ }
342+
343+ // --- Reverse CSR checks ---
344+ auto in_row = g.in_row_offsets_view ();
345+ auto in_col = g.in_col_indices_view ();
346+ auto in_aei = g.in_adj_edge_index_view ();
347+
348+ EXPECT_EQ (in_row.size (), N + 1 );
349+ for (std::size_t i = 0 ; i < in_row.size () - 1 ; ++i) {
350+ EXPECT_LE (in_row[i], in_row[i + 1 ]) << " Reverse row offsets not monotonic at " << i;
351+ }
352+ EXPECT_EQ (static_cast <std::size_t >(in_row[in_row.size () - 1 ]), M);
353+ EXPECT_EQ (in_col.size (), M);
354+ EXPECT_EQ (in_aei.size (), M);
355+
356+ // Bijection and consistency for reverse CSR
357+ std::vector<int > rev_edge_count (M, 0 );
358+ for (std::size_t v = 0 ; v < N; ++v) {
359+ auto s = static_cast <std::size_t >(in_row[v]);
360+ auto e = static_cast <std::size_t >(in_row[v + 1 ]);
361+ for (std::size_t j = s; j < e; ++j) {
362+ auto eid = static_cast <std::size_t >(in_aei[j]);
363+ ASSERT_LT (eid, M) << " Reverse edge index out of range at CSR pos " << j;
364+ rev_edge_count[eid]++;
365+
366+ // Destination consistency: edge must point to node v
367+ EXPECT_EQ (static_cast <std::size_t >(edge_dst[eid]), v)
368+ << " Reverse CSR: edge " << eid << " in node " << v
369+ << " 's incoming list has dst=" << edge_dst[eid];
370+
371+ // Source consistency: in_col_indices must match edge source
372+ EXPECT_EQ (in_col[j], edge_src[eid])
373+ << " Reverse CSR: in_col_indices[" << j << " ]=" << in_col[j]
374+ << " doesn't match edge_src[" << eid << " ]=" << edge_src[eid];
375+ }
376+ }
377+ for (std::size_t eid = 0 ; eid < M; ++eid) {
378+ EXPECT_EQ (rev_edge_count[eid], 1 )
379+ << " Edge " << eid << " appears " << rev_edge_count[eid]
380+ << " times in reverse CSR (expected exactly 1)" ;
381+ }
307382}
308383
309384inline void expect_pred_dag_valid (const PredDAG& dag, int num_nodes) {
@@ -325,6 +400,42 @@ inline void expect_pred_dag_valid(const PredDAG& dag, int num_nodes) {
325400 }
326401}
327402
403+ // Extended PredDAG validation that also checks semantic correctness against a graph.
404+ // Verifies that each entry refers to an actual edge from parent to node, and that
405+ // distances are consistent (dist[v] == dist[parent] + edge_cost).
406+ inline void expect_pred_dag_semantically_valid (const StrictMultiDiGraph& g,
407+ const PredDAG& dag,
408+ const std::vector<Cost>& dist) {
409+ const auto N = g.num_nodes ();
410+ const auto edge_src = g.edge_src_view ();
411+ const auto edge_dst = g.edge_dst_view ();
412+ const auto edge_cost = g.cost_view ();
413+
414+ expect_pred_dag_valid (dag, N);
415+
416+ for (std::int32_t v = 0 ; v < N; ++v) {
417+ auto s = static_cast <std::size_t >(dag.parent_offsets [static_cast <std::size_t >(v)]);
418+ auto e = static_cast <std::size_t >(dag.parent_offsets [static_cast <std::size_t >(v) + 1 ]);
419+ for (std::size_t i = s; i < e; ++i) {
420+ auto parent = dag.parents [i];
421+ auto eid = static_cast <std::size_t >(dag.via_edges [i]);
422+ ASSERT_LT (eid, static_cast <std::size_t >(g.num_edges ()));
423+ // Edge must go from parent -> v
424+ EXPECT_EQ (edge_src[eid], parent)
425+ << " Node " << v << " : via_edge " << eid << " src mismatch" ;
426+ EXPECT_EQ (edge_dst[eid], v)
427+ << " Node " << v << " : via_edge " << eid << " dst mismatch" ;
428+ // Distance consistency
429+ auto d_v = dist[static_cast <std::size_t >(v)];
430+ auto d_p = dist[static_cast <std::size_t >(parent)];
431+ if (d_v < std::numeric_limits<Cost>::max () && d_p < std::numeric_limits<Cost>::max ()) {
432+ EXPECT_EQ (d_v, d_p + edge_cost[eid])
433+ << " Distance inconsistency at node " << v;
434+ }
435+ }
436+ }
437+ }
438+
328439inline void expect_flow_conservation (const FlowGraph& fg, NodeId src, NodeId dst) {
329440 const auto & g = fg.graph ();
330441 auto row = g.row_offsets_view ();
0 commit comments