diff --git a/include/hgl/hypergraph.hpp b/include/hgl/hypergraph.hpp index 68d082a..9bda8e9 100644 --- a/include/hgl/hypergraph.hpp +++ b/include/hgl/hypergraph.hpp @@ -16,6 +16,7 @@ #include "hgl/util.hpp" #include +#include #include #include #include @@ -312,6 +313,162 @@ class hypergraph final { }; } + hyperedge_type add_hyperedge(const traits::c_forward_range_of auto& vertex_id_rng) + requires std::same_as + { + auto he = this->add_hyperedge(); + this->bind(vertex_id_rng, he.id()); + return he; + } + + gl_attr_force_inline hyperedge_type add_hyperedge(std::initializer_list vertex_ids) + requires std::same_as + { + return this->add_hyperedge(std::views::all(vertex_ids)); + } + + gl_attr_force_inline hyperedge_type + add_hyperedge(const traits::c_forward_range_of auto& vertex_rng) + requires std::same_as + { + return this->add_hyperedge(vertex_rng | std::views::transform(&vertex_type::id)); + } + + gl_attr_force_inline hyperedge_type add_hyperedge(std::initializer_list vertices) + requires std::same_as + { + return this->add_hyperedge(std::views::all(vertices)); + } + + hyperedge_type add_hyperedge_with( + const traits::c_forward_range_of auto& vertex_id_rng, + hyperedge_properties_type properties + ) + requires(std::same_as and traits::c_non_empty_properties) + { + auto he = this->add_hyperedge_with(std::move(properties)); + this->bind(vertex_id_rng, he.id()); + return he; + } + + hyperedge_type add_hyperedge_with( + std::initializer_list vertex_ids, hyperedge_properties_type properties + ) + requires(std::same_as and traits::c_non_empty_properties) + { + return this->add_hyperedge_with(std::views::all(vertex_ids), std::move(properties)); + } + + gl_attr_force_inline hyperedge_type add_hyperedge_with( + const traits::c_forward_range_of auto& vertex_rng, + hyperedge_properties_type properties + ) + requires(std::same_as and traits::c_non_empty_properties) + { + return this->add_hyperedge_with( + vertex_rng | std::views::transform(&vertex_type::id), std::move(properties) + ); + } + + hyperedge_type add_hyperedge_with( + std::initializer_list vertices, hyperedge_properties_type properties + ) + requires(std::same_as and traits::c_non_empty_properties) + { + return this->add_hyperedge_with(std::views::all(vertices), std::move(properties)); + } + + hyperedge_type add_hyperedge( + const traits::c_forward_range_of auto& tail_id_rng, + const traits::c_forward_range_of auto& head_id_rng + ) + requires std::same_as + { + auto he = this->add_hyperedge(); + this->bind_tail(tail_id_rng, he.id()); + this->bind_head(head_id_rng, he.id()); + return he; + } + + gl_attr_force_inline hyperedge_type + add_hyperedge(std::initializer_list tail_ids, std::initializer_list head_ids) + requires std::same_as + { + return this->add_hyperedge(std::views::all(tail_ids), std::views::all(head_ids)); + } + + gl_attr_force_inline hyperedge_type add_hyperedge( + const traits::c_forward_range_of auto& tail_rng, + const traits::c_forward_range_of auto& head_rng + ) + requires std::same_as + { + return this->add_hyperedge( + tail_rng | std::views::transform(&vertex_type::id), + head_rng | std::views::transform(&vertex_type::id) + ); + } + + gl_attr_force_inline hyperedge_type add_hyperedge( + std::initializer_list tail_vertices, + std::initializer_list head_vertices + ) + requires std::same_as + { + return this->add_hyperedge(std::views::all(tail_vertices), std::views::all(head_vertices)); + } + + hyperedge_type add_hyperedge_with( + const traits::c_forward_range_of auto& tail_id_rng, + const traits::c_forward_range_of auto& head_id_rng, + hyperedge_properties_type properties + ) + requires(std::same_as and traits::c_non_empty_properties) + { + auto he = this->add_hyperedge_with(std::move(properties)); + this->bind_tail(tail_id_rng, he.id()); + this->bind_head(head_id_rng, he.id()); + return he; + } + + gl_attr_force_inline hyperedge_type add_hyperedge_with( + std::initializer_list tail_ids, + std::initializer_list head_ids, + hyperedge_properties_type properties + ) + requires(std::same_as and traits::c_non_empty_properties) + { + return this->add_hyperedge_with( + std::views::all(tail_ids), std::views::all(head_ids), std::move(properties) + ); + } + + gl_attr_force_inline hyperedge_type add_hyperedge_with( + const traits::c_forward_range_of auto& tail_rng, + const traits::c_forward_range_of auto& head_rng, + hyperedge_properties_type properties + ) + requires(std::same_as and traits::c_non_empty_properties) + { + return this->add_hyperedge_with( + tail_rng | std::views::transform(&vertex_type::id), + head_rng | std::views::transform(&vertex_type::id), + std::move(properties) + ); + } + + gl_attr_force_inline hyperedge_type add_hyperedge_with( + std::initializer_list tail_vertices, + std::initializer_list head_vertices, + hyperedge_properties_type properties + ) + requires(std::same_as and traits::c_non_empty_properties) + { + return this->add_hyperedge_with( + std::views::all(tail_vertices), std::views::all(head_vertices), std::move(properties) + ); + } + void add_hyperedges(const size_type n) { this->_impl.add_hyperedges(n); this->_n_hyperedges += n; @@ -406,6 +563,168 @@ class hypergraph final { this->bind(vertex.id(), hyperedge.id()); } + void bind( + const traits::c_forward_range_of auto& vertex_id_rng, const id_type hyperedge_id + ) + requires std::same_as + { + this->_verify_hyperedge_id(hyperedge_id); + for (const auto vertex_id : vertex_id_rng) { + this->_verify_vertex_id(vertex_id); + this->_impl.bind(vertex_id, hyperedge_id); + } + } + + gl_attr_force_inline void bind( + std::initializer_list vertex_ids, const id_type hyperedge_id + ) + requires std::same_as + { + this->bind(std::views::all(vertex_ids), hyperedge_id); + } + + gl_attr_force_inline void bind( + const traits::c_forward_range_of auto& vertex_rng, + const hyperedge_type& hyperedge + ) + requires std::same_as + { + this->bind(vertex_rng | std::views::transform(&vertex_type::id), hyperedge.id()); + } + + gl_attr_force_inline void bind( + std::initializer_list vertices, const hyperedge_type& hyperedge + ) + requires std::same_as + { + this->bind(std::views::all(vertices), hyperedge); + } + + void bind( + const id_type vertex_id, const traits::c_forward_range_of auto& hyperedge_id_rng + ) + requires std::same_as + { + this->_verify_vertex_id(vertex_id); + for (const auto hyperedge_id : hyperedge_id_rng) { + this->_verify_hyperedge_id(hyperedge_id); + this->_impl.bind(vertex_id, hyperedge_id); + } + } + + gl_attr_force_inline void bind( + const id_type vertex_id, std::initializer_list hyperedge_ids + ) + requires std::same_as + { + this->bind(vertex_id, std::views::all(hyperedge_ids)); + } + + gl_attr_force_inline void bind( + const vertex_type& vertex, + const traits::c_forward_range_of auto& hyperedge_rng + ) + requires std::same_as + { + this->bind(vertex.id(), hyperedge_rng | std::views::transform(&hyperedge_type::id)); + } + + gl_attr_force_inline void bind( + const vertex_type& vertex, std::initializer_list hyperedges + ) + requires std::same_as + { + this->bind(vertex, std::views::all(hyperedges)); + } + + void bind_tail(const id_type vertex_id, const id_type hyperedge_id) + requires std::same_as + { + this->_verify_vertex_id(vertex_id); + this->_verify_hyperedge_id(hyperedge_id); + this->_impl.bind_tail(vertex_id, hyperedge_id); + } + + gl_attr_force_inline void bind_tail(const vertex_type& vertex, const hyperedge_type& hyperedge) + requires std::same_as + { + this->bind_tail(vertex.id(), hyperedge.id()); + } + + void bind_tail( + const traits::c_forward_range_of auto& vertex_id_rng, const id_type hyperedge_id + ) + requires std::same_as + { + this->_verify_hyperedge_id(hyperedge_id); + for (const auto vertex_id : vertex_id_rng) { + this->_verify_vertex_id(vertex_id); + this->_impl.bind_tail(vertex_id, hyperedge_id); + } + } + + gl_attr_force_inline void bind_tail( + std::initializer_list vertex_ids, const id_type hyperedge_id + ) + requires std::same_as + { + this->bind_tail(std::views::all(vertex_ids), hyperedge_id); + } + + gl_attr_force_inline void bind_tail( + const traits::c_forward_range_of auto& vertex_rng, + const hyperedge_type& hyperedge + ) + requires std::same_as + { + this->bind_tail(vertex_rng | std::views::transform(&vertex_type::id), hyperedge.id()); + } + + gl_attr_force_inline void bind_tail( + std::initializer_list vertices, const hyperedge_type& hyperedge + ) + requires std::same_as + { + this->bind_tail(std::views::all(vertices), hyperedge); + } + + void bind_tail( + const id_type vertex_id, const traits::c_forward_range_of auto& hyperedge_id_rng + ) + requires std::same_as + { + this->_verify_vertex_id(vertex_id); + for (const auto hyperedge_id : hyperedge_id_rng) { + this->_verify_hyperedge_id(hyperedge_id); + this->_impl.bind_tail(vertex_id, hyperedge_id); + } + } + + gl_attr_force_inline void bind_tail( + const id_type vertex_id, std::initializer_list hyperedge_ids + ) + requires std::same_as + { + this->bind_tail(vertex_id, std::views::all(hyperedge_ids)); + } + + gl_attr_force_inline void bind_tail( + const vertex_type& vertex, + const traits::c_forward_range_of auto& hyperedge_rng + ) + requires std::same_as + { + this->bind_tail(vertex.id(), hyperedge_rng | std::views::transform(&hyperedge_type::id)); + } + + gl_attr_force_inline void bind_tail( + const vertex_type& vertex, std::initializer_list hyperedges + ) + requires std::same_as + { + this->bind_tail(vertex, std::views::all(hyperedges)); + } + void bind_head(const id_type vertex_id, const id_type hyperedge_id) requires std::same_as { @@ -420,18 +739,78 @@ class hypergraph final { this->bind_head(vertex.id(), hyperedge.id()); } - void bind_tail(const id_type vertex_id, const id_type hyperedge_id) + void bind_head( + const traits::c_forward_range_of auto& vertex_id_rng, const id_type hyperedge_id + ) requires std::same_as { - this->_verify_vertex_id(vertex_id); this->_verify_hyperedge_id(hyperedge_id); - this->_impl.bind_tail(vertex_id, hyperedge_id); + for (const auto vertex_id : vertex_id_rng) { + this->_verify_vertex_id(vertex_id); + this->_impl.bind_head(vertex_id, hyperedge_id); + } } - gl_attr_force_inline void bind_tail(const vertex_type& vertex, const hyperedge_type& hyperedge) + gl_attr_force_inline void bind_head( + std::initializer_list vertex_ids, const id_type hyperedge_id + ) requires std::same_as { - this->bind_tail(vertex.id(), hyperedge.id()); + this->bind_head(std::views::all(vertex_ids), hyperedge_id); + } + + gl_attr_force_inline void bind_head( + const traits::c_forward_range_of auto& vertex_rng, + const hyperedge_type& hyperedge + ) + requires std::same_as + { + this->bind_head(vertex_rng | std::views::transform(&vertex_type::id), hyperedge.id()); + } + + gl_attr_force_inline void bind_head( + std::initializer_list vertices, const hyperedge_type& hyperedge + ) + requires std::same_as + { + this->bind_head(std::views::all(vertices), hyperedge); + } + + void bind_head( + const id_type vertex_id, const traits::c_forward_range_of auto& hyperedge_id_rng + ) + requires std::same_as + { + this->_verify_vertex_id(vertex_id); + for (const auto hyperedge_id : hyperedge_id_rng) { + this->_verify_hyperedge_id(hyperedge_id); + this->_impl.bind_head(vertex_id, hyperedge_id); + } + } + + gl_attr_force_inline void bind_head( + const id_type vertex_id, std::initializer_list hyperedge_ids + ) + requires std::same_as + { + this->bind_head(vertex_id, std::views::all(hyperedge_ids)); + } + + gl_attr_force_inline void bind_head( + const vertex_type& vertex, + const traits::c_forward_range_of auto& hyperedge_rng + ) + requires std::same_as + { + this->bind_head(vertex.id(), hyperedge_rng | std::views::transform(&hyperedge_type::id)); + } + + gl_attr_force_inline void bind_head( + const vertex_type& vertex, std::initializer_list hyperedges + ) + requires std::same_as + { + this->bind_head(vertex, std::views::all(hyperedges)); } void unbind(const id_type vertex_id, const id_type hyperedge_id) { @@ -1110,18 +1489,15 @@ class hypergraph final { if (with_he_props) { hyperedge_properties_type props; is >> props; - new_he_id = this->add_hyperedge_with(std::move(props)).id(); + new_he_id = this->add_hyperedge_with(v_ids, std::move(props)).id(); } else { - new_he_id = this->add_hyperedge().id(); + new_he_id = this->add_hyperedge(v_ids).id(); } } else { - new_he_id = this->add_hyperedge().id(); + new_he_id = this->add_hyperedge(v_ids).id(); } - - for (const auto v_id : v_ids) - this->bind(v_id, new_he_id); } } @@ -1145,20 +1521,15 @@ class hypergraph final { if (with_he_props) { hyperedge_properties_type props; is >> props; - new_he_id = this->add_hyperedge_with(std::move(props)).id(); + new_he_id = this->add_hyperedge_with(tail_ids, head_ids, std::move(props)).id(); } else { - new_he_id = this->add_hyperedge().id(); + new_he_id = this->add_hyperedge(tail_ids, head_ids).id(); } } else { - new_he_id = this->add_hyperedge().id(); + new_he_id = this->add_hyperedge(tail_ids, head_ids).id(); } - - for (const auto v_id : tail_ids) - this->bind_tail(v_id, new_he_id); - for (const auto v_id : head_ids) - this->bind_head(v_id, new_he_id); } } diff --git a/tests/source/hgl/test_alg_backward_search.cpp b/tests/source/hgl/test_alg_backward_search.cpp index 51305db..87d28db 100644 --- a/tests/source/hgl/test_alg_backward_search.cpp +++ b/tests/source/hgl/test_alg_backward_search.cpp @@ -41,20 +41,9 @@ TEST_CASE_TEMPLATE_DEFINE( const auto order = 5uz; hypergraph.add_vertices(order); - - const auto e0 = hypergraph.add_hyperedge().id(); - hypergraph.bind_tail(0uz, e0); - hypergraph.bind_tail(1uz, e0); - hypergraph.bind_head(2uz, e0); - - const auto e1 = hypergraph.add_hyperedge().id(); - hypergraph.bind_tail(2uz, e1); - hypergraph.bind_head(3uz, e1); - hypergraph.bind_head(4uz, e1); - - const auto e2 = hypergraph.add_hyperedge().id(); - hypergraph.bind_tail(1uz, e2); - hypergraph.bind_head(4uz, e2); + const auto e0 = hypergraph.add_hyperedge({0u, 1u}, {2u}).id(); + const auto e1 = hypergraph.add_hyperedge({2u}, {3u, 4u}).id(); + const auto e2 = hypergraph.add_hyperedge({1u}, {4u}).id(); SUBCASE("single root v0 (immediate halt)") { root_vertices = {0u}; @@ -191,20 +180,9 @@ TEST_CASE_TEMPLATE_DEFINE( const auto order = 5uz; hypergraph.add_vertices(order); - - const auto e0 = hypergraph.add_hyperedge().id(); - hypergraph.bind_tail(0uz, e0); - hypergraph.bind_tail(1uz, e0); - hypergraph.bind_head(2uz, e0); - - const auto e1 = hypergraph.add_hyperedge().id(); - hypergraph.bind_tail(2uz, e1); - hypergraph.bind_head(3uz, e1); - hypergraph.bind_head(4uz, e1); - - const auto e2 = hypergraph.add_hyperedge().id(); - hypergraph.bind_tail(1uz, e2); - hypergraph.bind_head(4uz, e2); + const auto e0 = hypergraph.add_hyperedge({0u, 1u}, {2u}).id(); + const auto e1 = hypergraph.add_hyperedge({2u}, {3u, 4u}).id(); + const auto e2 = hypergraph.add_hyperedge({1u}, {4u}).id(); SUBCASE("single root v0 (immediate halt)") { root_vertices = {0u}; diff --git a/tests/source/hgl/test_alg_bfs.cpp b/tests/source/hgl/test_alg_bfs.cpp index c65a9e2..78527dc 100644 --- a/tests/source/hgl/test_alg_bfs.cpp +++ b/tests/source/hgl/test_alg_bfs.cpp @@ -31,9 +31,7 @@ TEST_CASE_TEMPLATE_DEFINE( SUBCASE("hub (single hyperedge)") { hypergraph.add_vertices(4uz); - const auto e = hypergraph.add_hyperedge().id(); - for (const auto v : hypergraph.vertex_ids()) - hypergraph.bind(v, e); + const auto e = hypergraph.add_hyperedge(hypergraph.vertex_ids()).id(); root_vertex_id = hgl::initial_id; @@ -46,18 +44,9 @@ TEST_CASE_TEMPLATE_DEFINE( SUBCASE("chain") { // E(H) = {{0, 1}, {1, 2, 3}, {3, 4, 5}} hypergraph.add_vertices(6uz); - - const auto e0 = hypergraph.add_hyperedge().id(); - for (const id_type v : {0u, 1u}) - hypergraph.bind(v, e0); - - const auto e1 = hypergraph.add_hyperedge().id(); - for (const id_type v : {1u, 2u, 3u}) - hypergraph.bind(v, e1); - - const auto e2 = hypergraph.add_hyperedge().id(); - for (const id_type v : {3u, 4u, 5u}) - hypergraph.bind(v, e2); + const auto e0 = hypergraph.add_hyperedge({0u, 1u}).id(); + const auto e1 = hypergraph.add_hyperedge({1u, 2u, 3u}).id(); + const auto e2 = hypergraph.add_hyperedge({3u, 4u, 5u}).id(); root_vertex_id = hgl::initial_id; @@ -70,22 +59,10 @@ TEST_CASE_TEMPLATE_DEFINE( SUBCASE("overlapping hyperedges (shortest path preference)") { // E(H) = {{0, 1}, {1, 2, 3}, {0, 3}, {3, 4}} hypergraph.add_vertices(5uz); - - const auto e0 = hypergraph.add_hyperedge().id(); - for (const id_type v : {0u, 1u}) - hypergraph.bind(v, e0); - - const auto e1 = hypergraph.add_hyperedge().id(); - for (const id_type v : {1u, 2u, 3u}) - hypergraph.bind(v, e1); - - const auto e2 = hypergraph.add_hyperedge().id(); - for (const id_type v : {0u, 3u}) - hypergraph.bind(v, e2); - - const auto e3 = hypergraph.add_hyperedge().id(); - for (const id_type v : {3u, 4u}) - hypergraph.bind(v, e3); + const auto e0 = hypergraph.add_hyperedge({0u, 1u}).id(); + const auto e1 = hypergraph.add_hyperedge({1u, 2u, 3u}).id(); + const auto e2 = hypergraph.add_hyperedge({0u, 3u}).id(); + const auto e3 = hypergraph.add_hyperedge({3u, 4u}).id(); root_vertex_id = hgl::initial_id; @@ -99,14 +76,8 @@ TEST_CASE_TEMPLATE_DEFINE( SUBCASE("disconnected components (targeted root)") { // E(H) = {{0, 1, 2}, {3, 4}} hypergraph.add_vertices(5uz); - - const auto e0 = hypergraph.add_hyperedge().id(); - for (const id_type v : {0u, 1u, 2u}) - hypergraph.bind(v, e0); - - const auto e1 = hypergraph.add_hyperedge().id(); - for (const id_type v : {3u, 4u}) - hypergraph.bind(v, e1); + const auto e0 = hypergraph.add_hyperedge({0u, 1u, 2u}).id(); + hypergraph.add_hyperedge({3u, 4u}); root_vertex_id = hgl::initial_id; @@ -119,14 +90,8 @@ TEST_CASE_TEMPLATE_DEFINE( SUBCASE("full graph traversal (no_root)") { // E(H) = {{0, 1, 2}, {3, 4}} hypergraph.add_vertices(5uz); - - const auto e0 = hypergraph.add_hyperedge().id(); - for (const id_type v : {0u, 1u, 2u}) - hypergraph.bind(v, e0); - - const auto e1 = hypergraph.add_hyperedge().id(); - for (const id_type v : {3u, 4u}) - hypergraph.bind(v, e1); + const auto e0 = hypergraph.add_hyperedge({0u, 1u, 2u}).id(); + const auto e1 = hypergraph.add_hyperedge({3u, 4u}).id(); root_vertex_id = hgl::algorithm::no_root; @@ -242,11 +207,7 @@ TEST_CASE_TEMPLATE_DEFINE( SUBCASE("forward star (single hyperedge)") { // E(H) = {0->{1, 2, 3}} hypergraph.add_vertices(4uz); - const auto e = hypergraph.add_hyperedge().id(); - - hypergraph.bind_tail(0uz, e); - for (const id_type v : {1u, 2u, 3u}) - hypergraph.bind_head(v, e); + const auto e = hypergraph.add_hyperedge({0u}, {1u, 2u, 3u}).id(); root_vertex_id = hgl::initial_id; @@ -259,20 +220,9 @@ TEST_CASE_TEMPLATE_DEFINE( SUBCASE("chain") { // E(H) = {0->1, 1->{2, 3}, 3->{4, 5}} hypergraph.add_vertices(6uz); - - const auto e0 = hypergraph.add_hyperedge().id(); - hypergraph.bind_tail(0uz, e0); - hypergraph.bind_head(1uz, e0); - - const auto e1 = hypergraph.add_hyperedge().id(); - hypergraph.bind_tail(1uz, e1); - for (const id_type v : {2u, 3u}) - hypergraph.bind_head(v, e1); - - const auto e2 = hypergraph.add_hyperedge().id(); - hypergraph.bind_tail(3uz, e2); - for (const id_type v : {4u, 5u}) - hypergraph.bind_head(v, e2); + const auto e0 = hypergraph.add_hyperedge({0u}, {1u}).id(); + const auto e1 = hypergraph.add_hyperedge({1u}, {2u, 3u}).id(); + const auto e2 = hypergraph.add_hyperedge({3u}, {4u, 5u}).id(); root_vertex_id = hgl::initial_id; @@ -285,23 +235,10 @@ TEST_CASE_TEMPLATE_DEFINE( SUBCASE("overlapping hyperedges (shortest path preference)") { // E(H) = {0->1, 1->{2, 3}, 0->3 (shortcut!), 3->4} hypergraph.add_vertices(5uz); - - const auto e0 = hypergraph.add_hyperedge().id(); - hypergraph.bind_tail(0uz, e0); - hypergraph.bind_head(1uz, e0); - - const auto e1 = hypergraph.add_hyperedge().id(); - hypergraph.bind_tail(1uz, e1); - for (const id_type v : {2u, 3u}) - hypergraph.bind_head(v, e1); - - const auto e2 = hypergraph.add_hyperedge().id(); - hypergraph.bind_tail(0uz, e2); - hypergraph.bind_head(3uz, e2); - - const auto e3 = hypergraph.add_hyperedge().id(); - hypergraph.bind_tail(3uz, e3); - hypergraph.bind_head(4uz, e3); + const auto e0 = hypergraph.add_hyperedge({0u}, {1u}).id(); + const auto e1 = hypergraph.add_hyperedge({1u}, {2u, 3u}).id(); + const auto e2 = hypergraph.add_hyperedge({0u}, {3u}).id(); + const auto e3 = hypergraph.add_hyperedge({3u}, {4u}).id(); root_vertex_id = hgl::initial_id; @@ -315,15 +252,8 @@ TEST_CASE_TEMPLATE_DEFINE( SUBCASE("disconnected components (targeted root)") { // E(H) = {0->{1, 2}, 3->4} hypergraph.add_vertices(5uz); - - const auto e0 = hypergraph.add_hyperedge().id(); - hypergraph.bind_tail(0uz, e0); - for (const id_type v : {1u, 2u}) - hypergraph.bind_head(v, e0); - - const auto e1 = hypergraph.add_hyperedge().id(); - hypergraph.bind_tail(3uz, e1); - hypergraph.bind_head(4uz, e1); + const auto e0 = hypergraph.add_hyperedge({0u}, {1u, 2u}).id(); + hypergraph.add_hyperedge({3u}, {4u}); root_vertex_id = hgl::initial_id; @@ -336,15 +266,8 @@ TEST_CASE_TEMPLATE_DEFINE( SUBCASE("full graph traversal (no_root)") { // E(H) = {0->{1, 2}, 3->4} hypergraph.add_vertices(5uz); - - const auto e0 = hypergraph.add_hyperedge().id(); - hypergraph.bind_tail(0uz, e0); - for (const id_type v : {1u, 2u}) - hypergraph.bind_head(v, e0); - - const auto e1 = hypergraph.add_hyperedge().id(); - hypergraph.bind_tail(3uz, e1); - hypergraph.bind_head(4uz, e1); + const auto e0 = hypergraph.add_hyperedge({0u}, {1u, 2u}).id(); + const auto e1 = hypergraph.add_hyperedge({3u}, {4u}).id(); root_vertex_id = hgl::algorithm::no_root; diff --git a/tests/source/hgl/test_alg_dfs.cpp b/tests/source/hgl/test_alg_dfs.cpp index 3505e8b..a207c79 100644 --- a/tests/source/hgl/test_alg_dfs.cpp +++ b/tests/source/hgl/test_alg_dfs.cpp @@ -28,9 +28,7 @@ TEST_CASE_TEMPLATE_DEFINE( SUBCASE("hub (single hyperedge)") { hypergraph.add_vertices(4uz); - const auto e = hypergraph.add_hyperedge().id(); - for (const auto v : hypergraph.vertex_ids()) - hypergraph.bind(v, e); + const auto e = hypergraph.add_hyperedge(hypergraph.vertex_ids()).id(); root_vertex_id = hgl::initial_id; @@ -43,18 +41,9 @@ TEST_CASE_TEMPLATE_DEFINE( SUBCASE("chain") { // E(H) = {{0, 1}, {1, 2, 3}, {3, 4, 5}} hypergraph.add_vertices(6uz); - - const auto e0 = hypergraph.add_hyperedge().id(); - for (const id_type v : {0u, 1u}) - hypergraph.bind(v, e0); - - const auto e1 = hypergraph.add_hyperedge().id(); - for (const id_type v : {1u, 2u, 3u}) - hypergraph.bind(v, e1); - - const auto e2 = hypergraph.add_hyperedge().id(); - for (const id_type v : {3u, 4u, 5u}) - hypergraph.bind(v, e2); + const auto e0 = hypergraph.add_hyperedge({0u, 1u}).id(); + const auto e1 = hypergraph.add_hyperedge({1u, 2u, 3u}).id(); + const auto e2 = hypergraph.add_hyperedge({3u, 4u, 5u}).id(); root_vertex_id = hgl::initial_id; @@ -68,22 +57,10 @@ TEST_CASE_TEMPLATE_DEFINE( SUBCASE("overlapping hyperedges (shortest path preference)") { // E(H) = {{0, 1}, {1, 2, 3}, {0, 3}, {3, 4}} hypergraph.add_vertices(5uz); - - const auto e0 = hypergraph.add_hyperedge().id(); - for (const id_type v : {0u, 1u}) - hypergraph.bind(v, e0); - - const auto e1 = hypergraph.add_hyperedge().id(); - for (const id_type v : {1u, 2u, 3u}) - hypergraph.bind(v, e1); - - const auto e2 = hypergraph.add_hyperedge().id(); - for (const id_type v : {0u, 3u}) - hypergraph.bind(v, e2); - - const auto e3 = hypergraph.add_hyperedge().id(); - for (const id_type v : {3u, 4u}) - hypergraph.bind(v, e3); + hypergraph.add_hyperedge({0u, 1u}); + const auto e1 = hypergraph.add_hyperedge({1u, 2u, 3u}).id(); + const auto e2 = hypergraph.add_hyperedge({0u, 3u}).id(); + const auto e3 = hypergraph.add_hyperedge({3u, 4u}).id(); root_vertex_id = hgl::initial_id; @@ -97,14 +74,8 @@ TEST_CASE_TEMPLATE_DEFINE( SUBCASE("disconnected components (targeted root)") { // E(H) = {{0, 1, 2}, {3, 4}} hypergraph.add_vertices(5uz); - - const auto e0 = hypergraph.add_hyperedge().id(); - for (const id_type v : {0u, 1u, 2u}) - hypergraph.bind(v, e0); - - const auto e1 = hypergraph.add_hyperedge().id(); - for (const id_type v : {3u, 4u}) - hypergraph.bind(v, e1); + const auto e0 = hypergraph.add_hyperedge({0u, 1u, 2u}).id(); + hypergraph.add_hyperedge({3u, 4u}); root_vertex_id = hgl::initial_id; @@ -118,14 +89,8 @@ TEST_CASE_TEMPLATE_DEFINE( SUBCASE("full graph traversal (no_root)") { // E(H) = {{0, 1, 2}, {3, 4}} hypergraph.add_vertices(5uz); - - const auto e0 = hypergraph.add_hyperedge().id(); - for (const id_type v : {0u, 1u, 2u}) - hypergraph.bind(v, e0); - - const auto e1 = hypergraph.add_hyperedge().id(); - for (const id_type v : {3u, 4u}) - hypergraph.bind(v, e1); + const auto e0 = hypergraph.add_hyperedge({0u, 1u, 2u}).id(); + const auto e1 = hypergraph.add_hyperedge({3u, 4u}).id(); root_vertex_id = hgl::algorithm::no_root; @@ -242,11 +207,7 @@ TEST_CASE_TEMPLATE_DEFINE( SUBCASE("forward star (single hyperedge)") { // E(H) = {0->{1, 2, 3}} hypergraph.add_vertices(4uz); - const auto e = hypergraph.add_hyperedge().id(); - - hypergraph.bind_tail(0uz, e); - for (const id_type v : {1u, 2u, 3u}) - hypergraph.bind_head(v, e); + const auto e = hypergraph.add_hyperedge({0u}, {1u, 2u, 3u}).id(); root_vertex_id = hgl::initial_id; @@ -259,20 +220,9 @@ TEST_CASE_TEMPLATE_DEFINE( SUBCASE("chain") { // E(H) = {0->1, 1->{2, 3}, 3->{4, 5}} hypergraph.add_vertices(6uz); - - const auto e0 = hypergraph.add_hyperedge().id(); - hypergraph.bind_tail(0uz, e0); - hypergraph.bind_head(1uz, e0); - - const auto e1 = hypergraph.add_hyperedge().id(); - hypergraph.bind_tail(1uz, e1); - for (const id_type v : {2u, 3u}) - hypergraph.bind_head(v, e1); - - const auto e2 = hypergraph.add_hyperedge().id(); - hypergraph.bind_tail(3uz, e2); - for (const id_type v : {4u, 5u}) - hypergraph.bind_head(v, e2); + const auto e0 = hypergraph.add_hyperedge({0u}, {1u}).id(); + const auto e1 = hypergraph.add_hyperedge({1u}, {2u, 3u}).id(); + const auto e2 = hypergraph.add_hyperedge({3u}, {4u, 5u}).id(); root_vertex_id = hgl::initial_id; @@ -286,23 +236,10 @@ TEST_CASE_TEMPLATE_DEFINE( SUBCASE("overlapping hyperedges (shortest path preference)") { // E(H) = {0->1, 1->{2, 3}, 0->3 (shortcut!), 3->4} hypergraph.add_vertices(5uz); - - const auto e0 = hypergraph.add_hyperedge().id(); - hypergraph.bind_tail(0uz, e0); - hypergraph.bind_head(1uz, e0); - - const auto e1 = hypergraph.add_hyperedge().id(); - hypergraph.bind_tail(1uz, e1); - for (const id_type v : {2u, 3u}) - hypergraph.bind_head(v, e1); - - const auto e2 = hypergraph.add_hyperedge().id(); - hypergraph.bind_tail(0uz, e2); - hypergraph.bind_head(3uz, e2); - - const auto e3 = hypergraph.add_hyperedge().id(); - hypergraph.bind_tail(3uz, e3); - hypergraph.bind_head(4uz, e3); + const auto e0 = hypergraph.add_hyperedge({0u}, {1u}).id(); + const auto e1 = hypergraph.add_hyperedge({1u}, {2u, 3u}).id(); + const auto e2 = hypergraph.add_hyperedge({0u}, {3u}).id(); + const auto e3 = hypergraph.add_hyperedge({3u}, {4u}).id(); root_vertex_id = hgl::initial_id; @@ -316,15 +253,8 @@ TEST_CASE_TEMPLATE_DEFINE( SUBCASE("disconnected components (targeted root)") { // E(H) = {0->{1, 2}, 3->4} hypergraph.add_vertices(5uz); - - const auto e0 = hypergraph.add_hyperedge().id(); - hypergraph.bind_tail(0uz, e0); - for (const id_type v : {1u, 2u}) - hypergraph.bind_head(v, e0); - - const auto e1 = hypergraph.add_hyperedge().id(); - hypergraph.bind_tail(3uz, e1); - hypergraph.bind_head(4uz, e1); + const auto e0 = hypergraph.add_hyperedge({0u}, {1u, 2u}).id(); + hypergraph.add_hyperedge({3u}, {4u}); root_vertex_id = hgl::initial_id; @@ -338,15 +268,8 @@ TEST_CASE_TEMPLATE_DEFINE( SUBCASE("full graph traversal (no_root)") { // E(H) = {0->{1, 2}, 3->4} hypergraph.add_vertices(5uz); - - const auto e0 = hypergraph.add_hyperedge().id(); - hypergraph.bind_tail(0uz, e0); - for (const id_type v : {1u, 2u}) - hypergraph.bind_head(v, e0); - - const auto e1 = hypergraph.add_hyperedge().id(); - hypergraph.bind_tail(3uz, e1); - hypergraph.bind_head(4uz, e1); + const auto e0 = hypergraph.add_hyperedge({0u}, {1u, 2u}).id(); + const auto e1 = hypergraph.add_hyperedge({3u}, {4u}).id(); root_vertex_id = hgl::algorithm::no_root; diff --git a/tests/source/hgl/test_alg_forward_search.cpp b/tests/source/hgl/test_alg_forward_search.cpp index 7a1cfba..97cccde 100644 --- a/tests/source/hgl/test_alg_forward_search.cpp +++ b/tests/source/hgl/test_alg_forward_search.cpp @@ -41,20 +41,9 @@ TEST_CASE_TEMPLATE_DEFINE( const auto order = 5uz; hypergraph.add_vertices(order); - - const auto e0 = hypergraph.add_hyperedge().id(); - hypergraph.bind_tail(0uz, e0); - hypergraph.bind_tail(1uz, e0); - hypergraph.bind_head(2uz, e0); - - const auto e1 = hypergraph.add_hyperedge().id(); - hypergraph.bind_tail(2uz, e1); - hypergraph.bind_head(3uz, e1); - hypergraph.bind_head(4uz, e1); - - const auto e2 = hypergraph.add_hyperedge().id(); - hypergraph.bind_tail(1uz, e2); - hypergraph.bind_head(4uz, e2); + const auto e0 = hypergraph.add_hyperedge({0u, 1u}, {2u}).id(); + const auto e1 = hypergraph.add_hyperedge({2u}, {3u, 4u}).id(); + const auto e2 = hypergraph.add_hyperedge({1u}, {4u}).id(); SUBCASE("single root v4 (partial backward traversal)") { // Rooting at v4. e1 cannot be traversed backwards because it also requires v3. @@ -224,20 +213,9 @@ TEST_CASE_TEMPLATE_DEFINE( const auto order = 5uz; hypergraph.add_vertices(order); - - const auto e0 = hypergraph.add_hyperedge().id(); - hypergraph.bind_tail(0uz, e0); - hypergraph.bind_tail(1uz, e0); - hypergraph.bind_head(2uz, e0); - - const auto e1 = hypergraph.add_hyperedge().id(); - hypergraph.bind_tail(2uz, e1); - hypergraph.bind_head(3uz, e1); - hypergraph.bind_head(4uz, e1); - - const auto e2 = hypergraph.add_hyperedge().id(); - hypergraph.bind_tail(1uz, e2); - hypergraph.bind_head(4uz, e2); + const auto e0 = hypergraph.add_hyperedge({0u, 1u}, {2u}).id(); + const auto e1 = hypergraph.add_hyperedge({2u}, {3u, 4u}).id(); + const auto e2 = hypergraph.add_hyperedge({1u}, {4u}).id(); SUBCASE("single root v4 (partial backward traversal)") { root_vertices = {4u}; diff --git a/tests/source/hgl/test_conversion.cpp b/tests/source/hgl/test_conversion.cpp index 05f9c54..c3ff425 100644 --- a/tests/source/hgl/test_conversion.cpp +++ b/tests/source/hgl/test_conversion.cpp @@ -25,16 +25,9 @@ struct test_hypergraph_conversion { template [[nodiscard]] HypergraphType create_test_hypergraph() { HypergraphType h(4uz, 3uz); - - h.bind(0uz, 0uz); - h.bind(1uz, 0uz); - h.bind(2uz, 0uz); - h.bind(1uz, 1uz); - h.bind(2uz, 1uz); - h.bind(3uz, 1uz); - h.bind(0uz, 2uz); - h.bind(3uz, 2uz); - + h.bind({0u, 1u, 2u}, 0u); + h.bind({1u, 2u, 3u}, 1u); + h.bind({0u, 3u}, 2u); this->set_properties(h); return h; } @@ -42,16 +35,10 @@ struct test_hypergraph_conversion { template [[nodiscard]] HypergraphType create_test_hypergraph() { HypergraphType h(4uz, 2uz); - - h.bind_tail(0uz, 0uz); - h.bind_tail(1uz, 0uz); - h.bind_head(2uz, 0uz); - h.bind_head(3uz, 0uz); - - h.bind_tail(2uz, 1uz); - h.bind_head(0uz, 1uz); - h.bind_head(1uz, 1uz); - + h.bind_tail({0u, 1u}, 0u); + h.bind_head({2u, 3u}, 0u); + h.bind_tail(2u, 1u); + h.bind_head({0u, 1u}, 1u); this->set_properties(h); return h; } @@ -73,18 +60,18 @@ struct test_hypergraph_conversion { void validate_hypergraph(const hgl::traits::c_undirected_hypergraph auto& h) { REQUIRE_EQ(h.order(), 4uz); REQUIRE_EQ(h.size(), 3uz); - CHECK(h.are_incident(0uz, 0uz)); - CHECK(h.are_incident(1uz, 0uz)); - CHECK(h.are_incident(2uz, 0uz)); - CHECK(h.are_incident(1uz, 1uz)); - CHECK(h.are_incident(2uz, 1uz)); - CHECK(h.are_incident(3uz, 1uz)); - CHECK(h.are_incident(0uz, 2uz)); - CHECK(h.are_incident(3uz, 2uz)); - - CHECK_FALSE(h.are_incident(3uz, 0uz)); - CHECK_FALSE(h.are_incident(0uz, 1uz)); - CHECK_FALSE(h.are_incident(1uz, 2uz)); + CHECK(h.are_incident(0u, 0u)); + CHECK(h.are_incident(1u, 0u)); + CHECK(h.are_incident(2u, 0u)); + CHECK(h.are_incident(1u, 1u)); + CHECK(h.are_incident(2u, 1u)); + CHECK(h.are_incident(3u, 1u)); + CHECK(h.are_incident(0u, 2u)); + CHECK(h.are_incident(3u, 2u)); + + CHECK_FALSE(h.are_incident(3u, 0u)); + CHECK_FALSE(h.are_incident(0u, 1u)); + CHECK_FALSE(h.are_incident(1u, 2u)); this->validate_properties(h); } @@ -93,19 +80,19 @@ struct test_hypergraph_conversion { REQUIRE_EQ(h.order(), 4uz); REQUIRE_EQ(h.size(), 2uz); - CHECK(h.is_tail(0uz, 0uz)); - CHECK(h.is_tail(1uz, 0uz)); - CHECK(h.is_head(2uz, 0uz)); - CHECK(h.is_head(3uz, 0uz)); + CHECK(h.is_tail(0u, 0u)); + CHECK(h.is_tail(1u, 0u)); + CHECK(h.is_head(2u, 0u)); + CHECK(h.is_head(3u, 0u)); - CHECK(h.is_tail(2uz, 1uz)); - CHECK(h.is_head(0uz, 1uz)); - CHECK(h.is_head(1uz, 1uz)); + CHECK(h.is_tail(2u, 1u)); + CHECK(h.is_head(0u, 1u)); + CHECK(h.is_head(1u, 1u)); - CHECK_FALSE(h.is_head(0uz, 0uz)); - CHECK_FALSE(h.is_tail(3uz, 0uz)); - CHECK_FALSE(h.is_tail(0uz, 1uz)); - CHECK_FALSE(h.is_head(2uz, 1uz)); + CHECK_FALSE(h.is_head(0u, 0u)); + CHECK_FALSE(h.is_tail(3u, 0u)); + CHECK_FALSE(h.is_tail(0u, 1u)); + CHECK_FALSE(h.is_head(2u, 1u)); this->validate_properties(h); } @@ -308,24 +295,11 @@ TEST_CASE_TEMPLATE_DEFINE( using flat_matrix_graph = gl::graph>; SUBCASE("projection should produce a clique for each hyperedge") { - sut_type sut{4ull, 4ull}; - - // e0 = {0,1,2} - sut.bind(0uz, 0uz); - sut.bind(1uz, 0uz); - sut.bind(2uz, 0uz); - - // e1 = {1,2,3} - sut.bind(1uz, 1uz); - sut.bind(2uz, 1uz); - sut.bind(3uz, 1uz); - - // e2 = {0,3} - sut.bind(0uz, 2uz); - sut.bind(3uz, 2uz); - - // e3 = {0} (should not add any edge) - sut.bind(0uz, 3uz); + sut_type sut{4uz, 4uz}; + sut.bind({0u, 1u, 2u}, 0u); // e0 = {0,1,2} + sut.bind({1u, 2u, 3u}, 1u); // e1 = {1,2,3} + sut.bind({0u, 3u}, 2u); // e2 = {0,3} + sut.bind(0u, 3u); // e3 = {0} (should not add any edge) const std::vector> expected_edges{ // e0: (0,1), (0,2), (1,2) @@ -357,40 +331,27 @@ TEST_CASE_TEMPLATE_DEFINE( } SUBCASE("incidence_graph should produce a bipartite graph connecting vertices to hyperedges") { - sut_type sut{4ull, 4ull}; - - // e0 = {0,1,2} - sut.bind(0uz, 0uz); - sut.bind(1uz, 0uz); - sut.bind(2uz, 0uz); - - // e1 = {1,2,3} - sut.bind(1uz, 1uz); - sut.bind(2uz, 1uz); - sut.bind(3uz, 1uz); - - // e2 = {0,3} - sut.bind(0uz, 2uz); - sut.bind(3uz, 2uz); - - // e3 = {0} - sut.bind(0uz, 3uz); + sut_type sut{4uz, 4uz}; + sut.bind({0u, 1u, 2u}, 0u); // e0 = {0,1,2} + sut.bind({1u, 2u, 3u}, 1u); // e1 = {1,2,3} + sut.bind({0u, 3u}, 2u); // e2 = {0,3} + sut.bind(0u, 3u); // e3 = {0} // Expected edges: vertices 0-3, hyperedges 4-7 const std::vector> expected_edges{ // e0 (4): {0,1,2} - {0uz, 4ull}, - {1uz, 4ull}, - {2uz, 4ull}, + {0u, 4u}, + {1u, 4u}, + {2u, 4u}, // e1 (5): {1,2,3} - {1uz, 5ull}, - {2uz, 5ull}, - {3uz, 5ull}, + {1u, 5u}, + {2u, 5u}, + {3u, 5u}, // e2 (6): {0,3} - {0uz, 6ull}, - {3uz, 6ull}, + {0u, 6u}, + {3u, 6u}, // e3 (7): {0} - {0uz, 7ull} + {0u, 7u} }; auto test_conversion_for = @@ -459,28 +420,25 @@ TEST_CASE_TEMPLATE_DEFINE( gl::impl::flat_matrix_t>>; SUBCASE("projection should produce directed edges from tails to heads for each hyperedge") { - sut_type sut{4ull, 2uz}; + sut_type sut{4uz, 2uz}; // e0: T={0,1} -> H={2,3} - sut.bind_tail(0uz, 0uz); - sut.bind_tail(1uz, 0uz); - sut.bind_head(2uz, 0uz); - sut.bind_head(3uz, 0uz); + sut.bind_tail({0u, 1u}, 0u); + sut.bind_head({2u, 3u}, 0u); // e1: T={2} -> H={0,1} - sut.bind_tail(2uz, 1uz); - sut.bind_head(0uz, 1uz); - sut.bind_head(1uz, 1uz); + sut.bind_tail(2u, 1u); + sut.bind_head({0u, 1u}, 1u); const std::vector> expected_edges{ // e0: 0->2, 0->3, 1->2, 1->3 - {0uz, 2uz}, - {0uz, 3uz}, - {1uz, 2uz}, - {1uz, 3uz}, + {0u, 2u}, + {0u, 3u}, + {1u, 2u}, + {1u, 3u}, // e1: 2->0, 2->1 - {2uz, 0uz}, - {2uz, 1uz} + {2u, 0u}, + {2u, 1u} }; auto test_conversion_for = @@ -502,30 +460,27 @@ TEST_CASE_TEMPLATE_DEFINE( SUBCASE("incidence_graph should produce a directed bipartite graph connecting tails to " "hyperedges and hyperedges to heads") { - sut_type sut{4ull, 2uz}; + sut_type sut{4uz, 2uz}; // e0: T={0,1} -> H={2,3} - sut.bind_tail(0uz, 0uz); - sut.bind_tail(1uz, 0uz); - sut.bind_head(2uz, 0uz); - sut.bind_head(3uz, 0uz); + sut.bind_tail({0u, 1u}, 0u); + sut.bind_head({2u, 3u}, 0u); // e1: T={2} -> H={0,1} - sut.bind_tail(2uz, 1uz); - sut.bind_head(0uz, 1uz); - sut.bind_head(1uz, 1uz); + sut.bind_tail(2u, 1u); + sut.bind_head({0u, 1u}, 1u); // Expected directed edges: vertices 0-3, hyperedges 4-5 const std::vector> expected_edges{ // Tails to hyperedges: 0->4, 1->4, 2->5 - { 0uz, 4ull}, - { 1uz, 4ull}, - { 2uz, 5ull}, + {0u, 4u}, + {1u, 4u}, + {2u, 5u}, // Hyperedges to heads: 4->2, 4->3, 5->0, 5->1 - {4ull, 2uz}, - {4ull, 3uz}, - {5ull, 0uz}, - {5ull, 1uz} + {4u, 2u}, + {4u, 3u}, + {5u, 0u}, + {5u, 1u} }; // Generic runner for the target models diff --git a/tests/source/hgl/test_hypergraph.cpp b/tests/source/hgl/test_hypergraph.cpp index 987fe15..c218369 100644 --- a/tests/source/hgl/test_hypergraph.cpp +++ b/tests/source/hgl/test_hypergraph.cpp @@ -11,7 +11,9 @@ #include #include +#include #include +#include namespace rng = std::ranges; namespace vw = std::views; @@ -257,6 +259,79 @@ TEST_CASE_TEMPLATE_DEFINE( CHECK_EQ(sut.size(), constants::n_hyperedges); } + SUBCASE("add_hyperedge() should create a hyperedge and bind the given vertices") { + if constexpr (std::same_as) { + sut_type sut{3uz}; + const std::vector v_ids{0u, 1u, 2u}; + + auto he1 = sut.add_hyperedge(v_ids); + CHECK_EQ(sut.size(), 1uz); + CHECK(rng::equal(sut.incident_vertex_ids(he1), v_ids)); + + auto vertices = + v_ids | vw::transform([&](const auto id) { return sut.get_vertex(id); }); + auto he2 = sut.add_hyperedge(vertices); + CHECK_EQ(sut.size(), 2uz); + CHECK(rng::equal(sut.incident_vertex_ids(he2), v_ids)); + } + + if constexpr (std::same_as) { + sut_type sut{4uz}; + const std::vector t_ids{0u, 3u}; + const std::vector h_ids{1u, 2u}; + + auto he1 = sut.add_hyperedge(t_ids, h_ids); + CHECK_EQ(sut.size(), 1uz); + CHECK(rng::equal(sut.tail_vertex_ids(he1), t_ids)); + CHECK(rng::equal(sut.head_vertex_ids(he1), h_ids)); + + auto h_vertices = + h_ids | vw::transform([&](const auto id) { return sut.get_vertex(id); }); + auto t_vertices = + t_ids | vw::transform([&](const auto id) { return sut.get_vertex(id); }); + auto he2 = sut.add_hyperedge(t_vertices, h_vertices); + CHECK_EQ(sut.size(), 2uz); + CHECK(rng::equal(sut.tail_vertex_ids(he2), t_ids)); + CHECK(rng::equal(sut.head_vertex_ids(he2), h_ids)); + } + } + + SUBCASE("add_hyperedge(initializer_list) variants should create a hyperedge and bind the given " + "vertices") { + if constexpr (std::same_as) { + sut_type sut{3uz}; + const std::vector v_ids{0u, 1u, 2u}; + + auto he1 = sut.add_hyperedge(v_ids); + CHECK_EQ(sut.size(), 1uz); + CHECK(rng::equal(sut.incident_vertex_ids(he1), v_ids)); + + auto he2 = + sut.add_hyperedge({sut.get_vertex(0u), sut.get_vertex(1u), sut.get_vertex(2u)}); + CHECK_EQ(sut.size(), 2uz); + CHECK(rng::equal(sut.incident_vertex_ids(he2), v_ids)); + } + + if constexpr (std::same_as) { + sut_type sut{4uz}; + + std::initializer_list t_ids = {0u, 1u}; + std::initializer_list h_ids = {2u, 3u}; + + auto he1 = sut.add_hyperedge(t_ids, h_ids); + CHECK_EQ(sut.size(), 1uz); + CHECK(rng::equal(sut.tail_vertex_ids(he1), t_ids)); + CHECK(rng::equal(sut.head_vertex_ids(he1), h_ids)); + + auto he2 = sut.add_hyperedge( + {sut.get_vertex(0u), sut.get_vertex(1u)}, {sut.get_vertex(2u), sut.get_vertex(3u)} + ); + CHECK_EQ(sut.size(), 2uz); + CHECK(rng::equal(sut.tail_vertex_ids(he2), t_ids)); + CHECK(rng::equal(sut.head_vertex_ids(he2), h_ids)); + } + } + SUBCASE("add_hyperedge_with should initialize a new hyperedge with the input properties " "structure") { using properties_traits_type = add_hyperedge_property; @@ -269,6 +344,102 @@ TEST_CASE_TEMPLATE_DEFINE( CHECK_EQ(hyperedge.properties(), constants::p_true); } + SUBCASE("add_hyperedge_with(, properties) variants should create a hyperedge, bind " + "vertices, and set properties") { + if constexpr (std::same_as) { + using properties_traits_type = + add_hyperedge_property; + + hgl::hypergraph sut{4uz}; + const std::vector v_ids{0u, 1u, 3u}; + + auto he1 = sut.add_hyperedge_with(v_ids, constants::p_true); + CHECK_EQ(sut.size(), 1uz); + CHECK(rng::equal(sut.incident_vertex_ids(he1), v_ids)); + CHECK_EQ(he1.properties(), constants::p_true); + + auto vertices = + v_ids | vw::transform([&](const auto id) { return sut.get_vertex(id); }); + auto he2 = sut.add_hyperedge_with(vertices, constants::p_false); + CHECK_EQ(sut.size(), 2uz); + CHECK(rng::equal(sut.incident_vertex_ids(he2), v_ids)); + CHECK_EQ(he2.properties(), constants::p_false); + } + + if constexpr (std::same_as) { + using properties_traits_type = + add_hyperedge_property; + + hgl::hypergraph sut{4uz}; + const std::vector t_ids{0u, 1u}; + const std::vector h_ids{2u, 3u}; + + auto he1 = sut.add_hyperedge_with(t_ids, h_ids, constants::p_true); + CHECK_EQ(sut.size(), 1uz); + CHECK(rng::equal(sut.tail_vertex_ids(he1), t_ids)); + CHECK(rng::equal(sut.head_vertex_ids(he1), h_ids)); + CHECK_EQ(he1.properties(), constants::p_true); + + auto t_vertices = + t_ids | vw::transform([&](const auto id) { return sut.get_vertex(id); }); + auto h_vertices = + h_ids | vw::transform([&](const auto id) { return sut.get_vertex(id); }); + auto he2 = sut.add_hyperedge_with(t_vertices, h_vertices, constants::p_false); + CHECK_EQ(sut.size(), 2uz); + CHECK(rng::equal(sut.tail_vertex_ids(he2), t_ids)); + CHECK(rng::equal(sut.head_vertex_ids(he2), h_ids)); + CHECK_EQ(he2.properties(), constants::p_false); + } + } + + SUBCASE("add_hyperedge_with(initializer_list, properties) variants should create a hyperedge, " + "bind vertices, and set properties") { + if constexpr (std::same_as) { + using properties_traits_type = + add_hyperedge_property; + + hgl::hypergraph sut{4uz}; + std::initializer_list v_ids{0u, 1u, 3u}; + + auto he1 = sut.add_hyperedge_with(v_ids, constants::p_true); + CHECK_EQ(sut.size(), 1uz); + CHECK(rng::equal(sut.incident_vertex_ids(he1), v_ids)); + CHECK_EQ(he1.properties(), constants::p_true); + + auto he2 = sut.add_hyperedge_with( + {sut.get_vertex(0u), sut.get_vertex(1u), sut.get_vertex(3u)}, constants::p_false + ); + CHECK_EQ(sut.size(), 2uz); + CHECK(rng::equal(sut.incident_vertex_ids(he2), v_ids)); + CHECK_EQ(he2.properties(), constants::p_false); + } + + if constexpr (std::same_as) { + using properties_traits_type = + add_hyperedge_property; + + hgl::hypergraph sut{4uz}; + std::initializer_list t_ids{0u, 1u}; + std::initializer_list h_ids{2u, 3u}; + + auto he1 = sut.add_hyperedge_with(t_ids, h_ids, constants::p_true); + CHECK_EQ(sut.size(), 1uz); + CHECK(rng::equal(sut.tail_vertex_ids(he1), t_ids)); + CHECK(rng::equal(sut.head_vertex_ids(he1), h_ids)); + CHECK_EQ(he1.properties(), constants::p_true); + + auto he2 = sut.add_hyperedge_with( + {sut.get_vertex(0u), sut.get_vertex(1u)}, + {sut.get_vertex(2u), sut.get_vertex(3u)}, + constants::p_false + ); + CHECK_EQ(sut.size(), 2uz); + CHECK(rng::equal(sut.tail_vertex_ids(he2), t_ids)); + CHECK(rng::equal(sut.head_vertex_ids(he2), h_ids)); + CHECK_EQ(he2.properties(), constants::p_false); + } + } + SUBCASE("add_hyperedges(n) should add n new hyperedges to the hypergraph") { sut_type sut{}; sut.add_hyperedges(constants::n_hyperedges); @@ -528,6 +699,141 @@ TEST_CASE_TEMPLATE_DEFINE( validate_incidence(false); } + SUBCASE("bulk bind (undirected) should properly mark given sets as incident") { + if constexpr (std::same_as) { + sut_type sut{4uz, 4uz}; + + std::vector he1_v_ids{0u, 1u}; + sut.bind(he1_v_ids, 0u); + CHECK(rng::equal(sut.incident_vertex_ids(0u), he1_v_ids)); + + auto he2_vertices = + he1_v_ids | vw::transform([&](const auto id) { return sut.get_vertex(id); }); + sut.bind(he2_vertices, sut.get_hyperedge(1u)); + CHECK(rng::equal(sut.incident_vertex_ids(1u), he1_v_ids)); + + std::vector v3_he_ids{2u, 3u}; + sut.bind(2u, v3_he_ids); + CHECK(rng::equal(sut.incident_hyperedge_ids(2u), v3_he_ids)); + + auto v4_hyperedges = + v3_he_ids | vw::transform([&](const auto id) { return sut.get_hyperedge(id); }); + sut.bind(sut.get_vertex(3u), v4_hyperedges); + CHECK(rng::equal(sut.incident_hyperedge_ids(3u), v3_he_ids)); + } + } + + SUBCASE("bulk bind (initializer list, undirected) should properly mark given sets as incident" + ) { + if constexpr (std::same_as) { + sut_type sut{4uz, 4uz}; + + std::initializer_list he1_v_ids{0u, 1u}; + sut.bind(he1_v_ids, 0u); + CHECK(rng::equal(sut.incident_vertex_ids(0u), he1_v_ids)); + + sut.bind({sut.get_vertex(0u), sut.get_vertex(1u)}, sut.get_hyperedge(1u)); + CHECK(rng::equal(sut.incident_vertex_ids(1u), he1_v_ids)); + + std::initializer_list v3_he_ids{2u, 3u}; + sut.bind(2u, v3_he_ids); + CHECK(rng::equal(sut.incident_hyperedge_ids(2u), v3_he_ids)); + + sut.bind(sut.get_vertex(3u), {sut.get_hyperedge(2u), sut.get_hyperedge(3u)}); + CHECK(rng::equal(sut.incident_hyperedge_ids(3u), v3_he_ids)); + } + } + + SUBCASE("bulk bind_tail (bf-directed) should properly mark given sets as incident") { + if constexpr (std::same_as) { + sut_type sut{4uz, 4uz}; + + std::vector he1_v_ids{0u, 1u}; + sut.bind_tail(he1_v_ids, 0u); + CHECK(rng::equal(sut.tail_vertex_ids(0u), he1_v_ids)); + + auto he2_vertices = + he1_v_ids | vw::transform([&](const auto id) { return sut.get_vertex(id); }); + sut.bind_tail(he2_vertices, sut.get_hyperedge(1u)); + CHECK(rng::equal(sut.tail_vertex_ids(1u), he1_v_ids)); + + std::vector v3_he_ids{2u, 3u}; + sut.bind_tail(2u, v3_he_ids); + CHECK(rng::equal(sut.out_hyperedge_ids(2u), v3_he_ids)); + + auto v4_hyperedges = + v3_he_ids | vw::transform([&](const auto id) { return sut.get_hyperedge(id); }); + sut.bind_tail(sut.get_vertex(3u), v4_hyperedges); + CHECK(rng::equal(sut.out_hyperedge_ids(3u), v3_he_ids)); + } + } + + SUBCASE("bulk bind_tail (initializer list, bf-directed) should properly mark given sets as " + "incident") { + if constexpr (std::same_as) { + sut_type sut{4uz, 4uz}; + + std::initializer_list he1_v_ids{0u, 1u}; + sut.bind_tail(he1_v_ids, 0u); + CHECK(rng::equal(sut.tail_vertex_ids(0u), he1_v_ids)); + + sut.bind_tail({sut.get_vertex(0u), sut.get_vertex(1u)}, sut.get_hyperedge(1u)); + CHECK(rng::equal(sut.tail_vertex_ids(1u), he1_v_ids)); + + std::initializer_list v3_he_ids{2u, 3u}; + sut.bind_tail(2u, v3_he_ids); + CHECK(rng::equal(sut.out_hyperedge_ids(2u), v3_he_ids)); + + sut.bind_tail(sut.get_vertex(3u), {sut.get_hyperedge(2u), sut.get_hyperedge(3u)}); + CHECK(rng::equal(sut.out_hyperedge_ids(3u), v3_he_ids)); + } + } + + SUBCASE("bulk bind_head (bf-directed) should properly mark given sets as incident") { + if constexpr (std::same_as) { + sut_type sut{4uz, 4uz}; + + std::vector he1_v_ids{0u, 1u}; + sut.bind_head(he1_v_ids, 0u); + CHECK(rng::equal(sut.head_vertex_ids(0u), he1_v_ids)); + + auto he2_vertices = + he1_v_ids | vw::transform([&](const auto id) { return sut.get_vertex(id); }); + sut.bind_head(he2_vertices, sut.get_hyperedge(1u)); + CHECK(rng::equal(sut.head_vertex_ids(1u), he1_v_ids)); + + std::vector v3_he_ids{2u, 3u}; + sut.bind_head(2u, v3_he_ids); + CHECK(rng::equal(sut.in_hyperedge_ids(2u), v3_he_ids)); + + auto v4_hyperedges = + v3_he_ids | vw::transform([&](const auto id) { return sut.get_hyperedge(id); }); + sut.bind_head(sut.get_vertex(3u), v4_hyperedges); + CHECK(rng::equal(sut.in_hyperedge_ids(3u), v3_he_ids)); + } + } + + SUBCASE("bulk bind_head (initializer list, bf-directed) should properly mark given sets as " + "incident") { + if constexpr (std::same_as) { + sut_type sut{4uz, 4uz}; + + std::initializer_list he1_v_ids{0u, 1u}; + sut.bind_head(he1_v_ids, 0u); + CHECK(rng::equal(sut.head_vertex_ids(0u), he1_v_ids)); + + sut.bind_head({sut.get_vertex(0u), sut.get_vertex(1u)}, sut.get_hyperedge(1u)); + CHECK(rng::equal(sut.head_vertex_ids(1u), he1_v_ids)); + + std::initializer_list v3_he_ids{2u, 3u}; + sut.bind_head(2u, v3_he_ids); + CHECK(rng::equal(sut.in_hyperedge_ids(2u), v3_he_ids)); + + sut.bind_head(sut.get_vertex(3u), {sut.get_hyperedge(2u), sut.get_hyperedge(3u)}); + CHECK(rng::equal(sut.in_hyperedge_ids(3u), v3_he_ids)); + } + } + SUBCASE("incident_hyperedges and degree should throw if the given vertex (id) is invalid") { GL_SUPPRESS_WARNING_BEGIN("-Warray-bounds");