@@ -58,7 +58,7 @@ TEST_CASE_TEMPLATE_DEFINE(
5858 hypergraph.bind_tail (1uz, e2 );
5959 hypergraph.bind_head (4uz, e2 );
6060
61- SUBCASE (" single root v1 (incomplete traversal )" ) {
61+ SUBCASE (" single root v0 (immediate halt )" ) {
6262 root_vertices = {0u };
6363 expected_previsit_order = {0u };
6464 expected_postvisit_order = {0u };
@@ -67,7 +67,7 @@ TEST_CASE_TEMPLATE_DEFINE(
6767 expected_in_hyperedges.resize (order, hgl::invalid_id);
6868 }
6969
70- SUBCASE (" roots v1 and v2 (complete traversal)" ) {
70+ SUBCASE (" roots v0 and v1 (complete traversal)" ) {
7171 root_vertices = {0u , 1u };
7272 expected_previsit_order = {0u , 1u , 2u , 4u , 3u };
7373 expected_postvisit_order = expected_previsit_order;
@@ -155,6 +155,181 @@ TEST_CASE_TEMPLATE_INSTANTIATE(
155155 hgl::bf_directed_t > // vertex-major flat incidence matrix
156156);
157157
158+ TEST_CASE_TEMPLATE_DEFINE (
159+ " forward_search should properly enforce head dependencies during traversal" ,
160+ HypergraphTraitsType,
161+ forward_search_directed_hypergraph_template
162+ ) {
163+ using hypergraph_type = hgl::hypergraph<HypergraphTraitsType>;
164+ using id_type = typename hypergraph_type::id_type;
165+ using node_type = hgl::algorithm::search_node<hypergraph_type>;
166+
167+ hypergraph_type hypergraph;
168+ std::vector<id_type> root_vertices;
169+
170+ std::vector<id_type> expected_previsit_order;
171+ std::vector<id_type> expected_postvisit_order;
172+ std::vector<id_type> expected_pred_map;
173+ std::vector<id_type> expected_in_hyperedges;
174+
175+ /* Topology:
176+ V = {v0, v1, v2, v3, v4}
177+ e0 = {v0, v1} -> {v2}
178+ e1 = {v2} -> {v3, v4}
179+ e2 = {v1} -> {v4}
180+
181+ v0 v3
182+ \ /
183+ -->-- v2 -->--
184+ / \
185+ v1 ------->------ v4
186+ */
187+
188+ const auto order = 5uz;
189+ hypergraph.add_vertices (order);
190+
191+ const auto e0 = hypergraph.add_hyperedge ().id ();
192+ hypergraph.bind_tail (0uz, e0 );
193+ hypergraph.bind_tail (1uz, e0 );
194+ hypergraph.bind_head (2uz, e0 );
195+
196+ const auto e1 = hypergraph.add_hyperedge ().id ();
197+ hypergraph.bind_tail (2uz, e1 );
198+ hypergraph.bind_head (3uz, e1 );
199+ hypergraph.bind_head (4uz, e1 );
200+
201+ const auto e2 = hypergraph.add_hyperedge ().id ();
202+ hypergraph.bind_tail (1uz, e2 );
203+ hypergraph.bind_head (4uz, e2 );
204+
205+ SUBCASE (" single root v4 (partial backward traversal)" ) {
206+ // Rooting at v4. e1 cannot be traversed backwards because it also requires v3.
207+ // e2 CAN be traversed because v4 is its only head.
208+ root_vertices = {4u };
209+
210+ expected_previsit_order = {4u , 1u };
211+ expected_postvisit_order = expected_previsit_order;
212+
213+ expected_pred_map.resize (order, hgl::invalid_id);
214+ expected_pred_map[4uz] = 4u ; // Root
215+ expected_pred_map[1uz] = 4u ; // v1 reached backward from v4 via e2
216+
217+ expected_in_hyperedges.resize (order, hgl::invalid_id);
218+ expected_in_hyperedges[1uz] = e2 ;
219+ }
220+
221+ SUBCASE (" roots v3 and v4 (complete backward traversal)" ) {
222+ // Rooting at v3 and v4 unlocks e1, which unlocks v2, which unlocks e0, which unlocks v0 and v1.
223+ root_vertices = {3u , 4u };
224+
225+ // Queue trace:
226+ // Init: [v3, v4]
227+ // Pop v3 -> sees e1 (needs v4, so wait)
228+ // Pop v4 -> sees e1 (unlocked -> enqueues v2), sees e2 (unlocked -> enqueues v1)
229+ // Queue: [v2, v1]
230+ // Pop v2 -> sees e0 (unlocked -> enqueues v0, v1) - v1 is already visited
231+ // Queue: [v1, v0]
232+ // Pop v1 -> no incoming edges
233+ // Pop v0 -> no incoming edges
234+ expected_previsit_order = {3u , 4u , 2u , 1u , 0u };
235+ expected_postvisit_order = expected_previsit_order;
236+
237+ expected_pred_map = {
238+ 2u , // v0 reached backward from v2 via e0
239+ 4u , // v1 reached backward from v4 via e2
240+ 4u , // v2 reached backward from v4 via e1
241+ 3u , // v3 is a root
242+ 4u // v4 is a root
243+ };
244+
245+ expected_in_hyperedges = {
246+ e0 , // v0 reached via e0
247+ e2 , // v1 reached via e2
248+ e1 , // v2 reached via e1
249+ hgl::invalid_id, // v3 root
250+ hgl::invalid_id // v4 root
251+ };
252+ }
253+
254+ CAPTURE (hypergraph);
255+ CAPTURE (root_vertices);
256+ CAPTURE (expected_previsit_order);
257+ CAPTURE (expected_postvisit_order);
258+ CAPTURE (expected_pred_map);
259+ CAPTURE (expected_in_hyperedges);
260+
261+ // --- noret search ---
262+
263+ std::vector<id_type> previsit_order;
264+ std::vector<id_type> postvisit_order;
265+ std::vector<id_type> noret_pred_map (hypergraph.order (), hgl::invalid_id);
266+ std::vector<id_type> noret_in_hyperedges (hypergraph.order (), hgl::invalid_id);
267+
268+ hgl::algorithm::forward_search<gl::algorithm::noret>(
269+ hypergraph,
270+ root_vertices,
271+ [&](const auto & node) {
272+ previsit_order.push_back (node.vertex_id );
273+ noret_pred_map[node.vertex_id ] = node.pred_id ;
274+ noret_in_hyperedges[node.vertex_id ] = node.hyperedge_id ;
275+ },
276+ [&](const auto & node) { postvisit_order.push_back (node.vertex_id ); }
277+ );
278+
279+ CHECK_EQ (previsit_order, expected_previsit_order);
280+ CHECK_EQ (postvisit_order, expected_postvisit_order);
281+ CHECK_EQ (noret_pred_map, expected_pred_map);
282+ CHECK_EQ (noret_in_hyperedges, expected_in_hyperedges);
283+
284+ // --- ret search ---
285+
286+ const auto search_tree =
287+ hgl::algorithm::forward_search<gl::algorithm::ret>(hypergraph, root_vertices);
288+
289+ const auto ret_pred_map =
290+ search_tree | std::views::transform (&node_type::pred_id) | std::ranges::to<std::vector>();
291+ const auto ret_in_hyperedges =
292+ search_tree | std::views::transform (&node_type::hyperedge_id)
293+ | std::ranges::to<std::vector>();
294+
295+ CHECK_EQ (ret_pred_map, expected_pred_map);
296+ CHECK_EQ (ret_in_hyperedges, expected_in_hyperedges);
297+ }
298+
299+ TEST_CASE_TEMPLATE_INSTANTIATE (
300+ forward_search_directed_hypergraph_template,
301+ hgl::list_hypergraph_traits<
302+ hgl::impl::bidirectional_t ,
303+ hgl::bf_directed_t >, // bidirectional incidence list
304+ hgl::list_hypergraph_traits<
305+ hgl::impl::hyperedge_major_t ,
306+ hgl::bf_directed_t >, // hyperedge-major incidence list
307+ hgl::list_hypergraph_traits<
308+ hgl::impl::vertex_major_t ,
309+ hgl::bf_directed_t >, // vertex-major incidence list
310+ hgl::flat_list_hypergraph_traits<
311+ hgl::impl::bidirectional_t ,
312+ hgl::bf_directed_t >, // bidirectional flat incidence list
313+ hgl::flat_list_hypergraph_traits<
314+ hgl::impl::hyperedge_major_t ,
315+ hgl::bf_directed_t >, // hyperedge-major flat incidence list
316+ hgl::flat_list_hypergraph_traits<
317+ hgl::impl::vertex_major_t ,
318+ hgl::bf_directed_t >, // vertex-major flat incidence list
319+ hgl::matrix_hypergraph_traits<
320+ hgl::impl::hyperedge_major_t ,
321+ hgl::bf_directed_t >, // hyperedge-major incidence matrix
322+ hgl::matrix_hypergraph_traits<
323+ hgl::impl::vertex_major_t ,
324+ hgl::bf_directed_t >, // vertex-major incidence matrix
325+ hgl::flat_matrix_hypergraph_traits<
326+ hgl::impl::hyperedge_major_t ,
327+ hgl::bf_directed_t >, // hyperedge-major flat incidence matrix
328+ hgl::flat_matrix_hypergraph_traits<
329+ hgl::impl::vertex_major_t ,
330+ hgl::bf_directed_t > // vertex-major flat incidence matrix
331+ );
332+
158333TEST_SUITE_END (); // test_alg_bfs
159334
160335} // namespace hgl_testing
0 commit comments