Skip to content

Commit 5c12c4a

Browse files
committed
forward search impl + tests
1 parent b7f19bf commit 5c12c4a

4 files changed

Lines changed: 198 additions & 9 deletions

File tree

include/hgl/algorithm/core.hpp

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,13 @@ using search_tree = std::vector<search_node<H>>;
5757

5858
// --- generic algorithm traits ---
5959

60-
template <hgl::traits::c_hypergraph H>
60+
enum class traversal_direction : bool { forward, backward };
61+
62+
template <hgl::traits::c_hypergraph H, traversal_direction Dir = traversal_direction::forward>
6163
struct traversal_policy;
6264

63-
template <hgl::traits::c_undirected_hypergraph H>
64-
struct traversal_policy<H> {
65+
template <hgl::traits::c_undirected_hypergraph H, traversal_direction Dir>
66+
struct traversal_policy<H, Dir> {
6567
static auto target_hyperedges(const H& h, typename H::id_type v_id) {
6668
return h.incident_hyperedge_ids(v_id);
6769
}
@@ -72,16 +74,27 @@ struct traversal_policy<H> {
7274
};
7375

7476
template <hgl::traits::c_bf_directed_hypergraph H>
75-
struct traversal_policy<H> {
77+
struct traversal_policy<H, traversal_direction::forward> {
7678
static auto target_hyperedges(const H& h, typename H::id_type v_id) {
77-
return h.out_hyperedge_ids(v_id);
79+
return h.out_hyperedge_ids(v_id); // forward star
7880
}
7981

8082
static auto target_vertices(const H& h, typename H::id_type he_id) {
8183
return h.head_vertex_ids(he_id);
8284
}
8385
};
8486

87+
template <hgl::traits::c_bf_directed_hypergraph H>
88+
struct traversal_policy<H, traversal_direction::backward> {
89+
static auto target_hyperedges(const H& h, typename H::id_type v_id) {
90+
return h.in_hyperedge_ids(v_id); // backward star
91+
}
92+
93+
static auto target_vertices(const H& h, typename H::id_type he_id) {
94+
return h.tail_vertex_ids(he_id);
95+
}
96+
};
97+
8598
} // namespace hgl::algorithm
8699

87100
// TODO! Validate `const Callback&` vs `Callback&&` in alg templates

include/hgl/algorithm/templates/bfs.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
namespace hgl::algorithm {
1212

1313
template <
14+
traversal_direction Dir = traversal_direction::forward,
1415
hgl::traits::c_hypergraph H,
1516
traits::c_forward_range_of<search_node<H>> InitQueueRangeType = std::vector<search_node<H>>,
1617
traits::c_optional_predicate<const search_node<H>&> VisitVertexPredicate = empty_callback,
@@ -30,7 +31,7 @@ bool bfs(
3031
const PreVisitCallback& pre_visit = {},
3132
const PostVisitCallback& post_visit = {}
3233
) {
33-
using policy = traversal_policy<H>;
34+
using policy = traversal_policy<H, Dir>;
3435

3536
if (std::ranges::empty(initial_queue_content))
3637
return false;

include/hgl/algorithm/traversal/bf_search.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ return_type<Result, search_tree<H>> forward_search(
8484

8585
// clang-format off
8686

87-
bfs(
87+
bfs<traversal_direction::backward>(
8888
hypergraph,
8989
root_queue,
9090
default_visit_vertex_predicate<H>(visited_vertices),

tests/source/hgl/test_alg_bfs.cpp

Lines changed: 177 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
158333
TEST_SUITE_END(); // test_alg_bfs
159334

160335
} // namespace hgl_testing

0 commit comments

Comments
 (0)