From 421a26e148ef1512a462e952e3614a6f81aa38f9 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Mon, 13 Apr 2026 18:07:49 +0200 Subject: [PATCH 1/5] add_hyperedge and bind in bulk --- include/hgl/hypergraph.hpp | 224 ++++++++++++++++++++++++++- tests/source/hgl/test_hypergraph.cpp | 175 +++++++++++++++++++++ 2 files changed, 394 insertions(+), 5 deletions(-) diff --git a/include/hgl/hypergraph.hpp b/include/hgl/hypergraph.hpp index 68d082a..176a855 100644 --- a/include/hgl/hypergraph.hpp +++ b/include/hgl/hypergraph.hpp @@ -312,6 +312,94 @@ 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(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)); + } + + 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; + } + + 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( + 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( + 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) + ); + } + + 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( + 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) + ); + } + void add_hyperedges(const size_type n) { this->_impl.add_hyperedges(n); this->_n_hyperedges += n; @@ -406,6 +494,104 @@ 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( + 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()); + } + + 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 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)); + } + + 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( + 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()); + } + + 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 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)); + } + void bind_head(const id_type vertex_id, const id_type hyperedge_id) requires std::same_as { @@ -420,18 +606,46 @@ 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( + const traits::c_forward_range_of auto& vertex_rng, + const hyperedge_type& hyperedge + ) requires std::same_as { - this->bind_tail(vertex.id(), hyperedge.id()); + this->bind_head(vertex_rng | std::views::transform(&vertex_type::id), hyperedge.id()); + } + + 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 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)); } void unbind(const id_type vertex_id, const id_type hyperedge_id) { diff --git a/tests/source/hgl/test_hypergraph.cpp b/tests/source/hgl/test_hypergraph.cpp index 987fe15..83f602b 100644 --- a/tests/source/hgl/test_hypergraph.cpp +++ b/tests/source/hgl/test_hypergraph.cpp @@ -269,6 +269,97 @@ TEST_CASE_TEMPLATE_DEFINE( CHECK_EQ(hyperedge.properties(), constants::p_true); } + SUBCASE("add_hyperedge() should create a hyperedge and bind the given vertices") { + const auto n_vertices = 4uz; + + if constexpr (std::same_as) { + sut_type sut{n_vertices}; + const std::vector v_ids{ + constants::id1, constants::id2, constants::id4 + }; + + auto he1 = sut.add_hyperedge(v_ids); + CHECK_EQ(sut.size(), 1uz); + CHECK(rng::is_permutation(sut.incident_vertex_ids(he1.id()), 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::is_permutation(sut.incident_vertex_ids(he2.id()), v_ids)); + } + + if constexpr (std::same_as) { + sut_type sut{n_vertices}; + const std::vector t_ids{constants::id1, constants::id4}; + const std::vector h_ids{constants::id2, constants::id3}; + + auto he1 = sut.add_hyperedge(t_ids, h_ids); + CHECK_EQ(sut.size(), 1uz); + CHECK(rng::is_permutation(sut.tail_vertex_ids(he1.id()), t_ids)); + CHECK(rng::is_permutation(sut.head_vertex_ids(he1.id()), 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::is_permutation(sut.tail_vertex_ids(he2.id()), t_ids)); + CHECK(rng::is_permutation(sut.head_vertex_ids(he2.id()), h_ids)); + } + } + + SUBCASE("add_hyperedge_with(, properties) and variants (undirected) should create a " + "hyperedge, bind vertices, and set properties") { + const auto n_vertices = 4uz; + + if constexpr (std::same_as) { + using properties_traits_type = + add_hyperedge_property; + hgl::hypergraph sut{n_vertices}; + const std::vector v_ids{ + constants::id1, constants::id2, constants::id4 + }; + + auto he1 = sut.add_hyperedge_with(v_ids, constants::p_true); + CHECK_EQ(sut.size(), 1uz); + CHECK(rng::is_permutation(sut.incident_vertex_ids(he1.id()), 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::is_permutation(sut.incident_vertex_ids(he2.id()), v_ids)); + CHECK_EQ(he2.properties(), constants::p_false); + } + + if constexpr (std::same_as) { + using properties_traits_type = + add_hyperedge_property; + hgl::hypergraph sut{n_vertices}; + const std::vector t_ids{constants::id1, constants::id4}; + const std::vector h_ids{constants::id2, constants::id3}; + + auto he1 = sut.add_hyperedge_with(t_ids, h_ids, constants::p_true); + CHECK_EQ(sut.size(), 1uz); + CHECK(rng::is_permutation(sut.tail_vertex_ids(he1.id()), t_ids)); + CHECK(rng::is_permutation(sut.head_vertex_ids(he1.id()), 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::is_permutation(sut.tail_vertex_ids(he2.id()), t_ids)); + CHECK(rng::is_permutation(sut.head_vertex_ids(he2.id()), 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 +619,90 @@ TEST_CASE_TEMPLATE_DEFINE( validate_incidence(false); } + SUBCASE("bulk bind (undirected) should properly mark given sets as incident") { + const auto n_vertices = 4uz; + const auto n_hyperedges = 4uz; + + if constexpr (std::same_as) { + sut_type sut{n_vertices, n_hyperedges}; + const std::vector v_ids{constants::id1, constants::id2}; + const std::vector he_ids{constants::id3, constants::id4}; + + sut.bind(v_ids, constants::id1); + CHECK(rng::is_permutation(sut.incident_vertex_ids(constants::id1), v_ids)); + + sut.bind(constants::id3, he_ids); + CHECK(rng::is_permutation(sut.incident_hyperedge_ids(constants::id3), he_ids)); + + auto vertices = + v_ids | vw::transform([&](const auto id) { return sut.get_vertex(id); }); + auto hyperedges = + he_ids | vw::transform([&](const auto id) { return sut.get_hyperedge(id); }); + + sut.bind(vertices, sut.get_hyperedge(constants::id2)); + CHECK(rng::is_permutation(sut.incident_vertex_ids(constants::id2), v_ids)); + + sut.bind(sut.get_vertex(constants::id4), hyperedges); + CHECK(rng::is_permutation(sut.incident_hyperedge_ids(constants::id4), he_ids)); + } + } + + SUBCASE("bulk bind_tail (bf-directed) should properly mark given sets as incident") { + const auto n_vertices = 4uz; + const auto n_hyperedges = 4uz; + + if constexpr (std::same_as) { + sut_type sut{n_vertices, n_hyperedges}; + const std::vector v_ids{constants::id1, constants::id2}; + const std::vector he_ids{constants::id3, constants::id4}; + + sut.bind_tail(v_ids, constants::id1); + CHECK(rng::is_permutation(sut.tail_vertex_ids(constants::id1), v_ids)); + + sut.bind_tail(constants::id3, he_ids); + CHECK(rng::is_permutation(sut.out_hyperedge_ids(constants::id3), he_ids)); + + auto vertices = + v_ids | vw::transform([&](const auto id) { return sut.get_vertex(id); }); + auto hyperedges = + he_ids | vw::transform([&](const auto id) { return sut.get_hyperedge(id); }); + + sut.bind_tail(vertices, sut.get_hyperedge(constants::id2)); + CHECK(rng::is_permutation(sut.tail_vertex_ids(constants::id2), v_ids)); + + sut.bind_tail(sut.get_vertex(constants::id4), hyperedges); + CHECK(rng::is_permutation(sut.out_hyperedge_ids(constants::id4), he_ids)); + } + } + + SUBCASE("bulk bind_head ranges (bf-directed) should properly mark given sets as incident") { + const auto n_vertices = 4uz; + const auto n_hyperedges = 4uz; + + if constexpr (std::same_as) { + sut_type sut{n_vertices, n_hyperedges}; + const std::vector v_ids{constants::id1, constants::id2}; + const std::vector he_ids{constants::id3, constants::id4}; + + sut.bind_head(v_ids, constants::id1); + CHECK(rng::is_permutation(sut.head_vertex_ids(constants::id1), v_ids)); + + sut.bind_head(constants::id3, he_ids); + CHECK(rng::is_permutation(sut.in_hyperedge_ids(constants::id3), he_ids)); + + auto vertices = + v_ids | vw::transform([&](const auto id) { return sut.get_vertex(id); }); + auto hyperedges = + he_ids | vw::transform([&](const auto id) { return sut.get_hyperedge(id); }); + + sut.bind_head(vertices, sut.get_hyperedge(constants::id2)); + CHECK(rng::is_permutation(sut.head_vertex_ids(constants::id2), v_ids)); + + sut.bind_head(sut.get_vertex(constants::id4), hyperedges); + CHECK(rng::is_permutation(sut.in_hyperedge_ids(constants::id4), he_ids)); + } + } + SUBCASE("incident_hyperedges and degree should throw if the given vertex (id) is invalid") { GL_SUPPRESS_WARNING_BEGIN("-Warray-bounds"); From 1db81647f63e645ecd1a55360d58968bd988bd7b Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Mon, 13 Apr 2026 18:33:42 +0200 Subject: [PATCH 2/5] initializer list overloads --- include/hgl/hypergraph.hpp | 185 ++++++++++++++++++++++++++++++++++--- 1 file changed, 171 insertions(+), 14 deletions(-) diff --git a/include/hgl/hypergraph.hpp b/include/hgl/hypergraph.hpp index 176a855..9021c08 100644 --- a/include/hgl/hypergraph.hpp +++ b/include/hgl/hypergraph.hpp @@ -16,6 +16,7 @@ #include "hgl/util.hpp" #include +#include #include #include #include @@ -320,6 +321,12 @@ class hypergraph final { 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 @@ -327,6 +334,12 @@ class hypergraph final { 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 @@ -338,6 +351,14 @@ class hypergraph final { 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 @@ -349,6 +370,14 @@ class hypergraph final { ); } + 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 @@ -361,6 +390,13 @@ class hypergraph final { 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 @@ -373,6 +409,15 @@ class hypergraph final { ); } + 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, @@ -386,6 +431,18 @@ class hypergraph final { 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, @@ -400,6 +457,18 @@ class hypergraph final { ); } + gl_attr_force_inline hyperedge_type add_hyperedge_with( + std::initializer_list tail_verticess, + std::initializer_list head_verticess, + hyperedge_properties_type properties + ) + requires(std::same_as and traits::c_non_empty_properties) + { + return this->add_hyperedge_with( + std::views::all(tail_verticess), std::views::all(head_verticess), std::move(properties) + ); + } + void add_hyperedges(const size_type n) { this->_impl.add_hyperedges(n); this->_n_hyperedges += n; @@ -506,6 +575,14 @@ class hypergraph final { } } + 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 @@ -515,6 +592,14 @@ class hypergraph final { this->bind(vertex_rng | std::views::transform(&vertex_type::id), hyperedge.id()); } + gl_attr_force_inline void bind( + std::initializer_list vertices, const id_type hyperedge_id + ) + requires std::same_as + { + this->bind(std::views::all(vertices), hyperedge_id); + } + void bind( const id_type vertex_id, const traits::c_forward_range_of auto& hyperedge_id_rng ) @@ -527,6 +612,14 @@ class hypergraph final { } } + 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 @@ -536,6 +629,14 @@ class hypergraph final { this->bind(vertex.id(), hyperedge_rng | std::views::transform(&hyperedge_type::id)); } + gl_attr_force_inline void bind( + const id_type vertex_id, std::initializer_list hyperedges + ) + requires std::same_as + { + this->bind(vertex_id, std::views::all(hyperedges)); + } + void bind_tail(const id_type vertex_id, const id_type hyperedge_id) requires std::same_as { @@ -562,6 +663,14 @@ class hypergraph final { } } + 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 @@ -571,6 +680,14 @@ class hypergraph final { 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 id_type hyperedge_id + ) + requires std::same_as + { + this->bind_tail(std::views::all(vertices), hyperedge_id); + } + void bind_tail( const id_type vertex_id, const traits::c_forward_range_of auto& hyperedge_id_rng ) @@ -583,6 +700,14 @@ class hypergraph final { } } + 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 @@ -592,6 +717,14 @@ class hypergraph final { this->bind_tail(vertex.id(), hyperedge_rng | std::views::transform(&hyperedge_type::id)); } + gl_attr_force_inline void bind_tail( + const id_type vertex_id, std::initializer_list hyperedges + ) + requires std::same_as + { + this->bind_tail(vertex_id, std::views::all(hyperedges)); + } + void bind_head(const id_type vertex_id, const id_type hyperedge_id) requires std::same_as { @@ -618,6 +751,14 @@ class hypergraph final { } } + gl_attr_force_inline void bind_head( + std::initializer_list vertex_ids, const id_type hyperedge_id + ) + requires std::same_as + { + 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 @@ -627,6 +768,14 @@ class hypergraph final { 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 id_type hyperedge_id + ) + requires std::same_as + { + this->bind_head(std::views::all(vertices), hyperedge_id); + } + void bind_head( const id_type vertex_id, const traits::c_forward_range_of auto& hyperedge_id_rng ) @@ -639,6 +788,14 @@ class hypergraph final { } } + 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 @@ -648,6 +805,14 @@ class hypergraph final { this->bind_head(vertex.id(), hyperedge_rng | std::views::transform(&hyperedge_type::id)); } + gl_attr_force_inline void bind_head( + const id_type vertex_id, std::initializer_list hyperedges + ) + requires std::same_as + { + this->bind_head(vertex_id, std::views::all(hyperedges)); + } + void unbind(const id_type vertex_id, const id_type hyperedge_id) { this->_verify_vertex_id(vertex_id); this->_verify_hyperedge_id(hyperedge_id); @@ -1324,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); } } @@ -1359,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); } } From 630d5fb1ac571a844efb09f63432601d6268d23b Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Mon, 13 Apr 2026 19:32:21 +0200 Subject: [PATCH 3/5] tests --- include/hgl/hypergraph.hpp | 32 +-- tests/source/hgl/test_hypergraph.cpp | 327 +++++++++++++++++++-------- 2 files changed, 245 insertions(+), 114 deletions(-) diff --git a/include/hgl/hypergraph.hpp b/include/hgl/hypergraph.hpp index 9021c08..fba31fa 100644 --- a/include/hgl/hypergraph.hpp +++ b/include/hgl/hypergraph.hpp @@ -593,11 +593,11 @@ class hypergraph final { } gl_attr_force_inline void bind( - std::initializer_list vertices, const id_type hyperedge_id + std::initializer_list vertices, const hyperedge_type& hyperedge ) requires std::same_as { - this->bind(std::views::all(vertices), hyperedge_id); + this->bind(std::views::all(vertices), hyperedge); } void bind( @@ -630,11 +630,11 @@ class hypergraph final { } gl_attr_force_inline void bind( - const id_type vertex_id, std::initializer_list hyperedges + const vertex_type& vertex, std::initializer_list hyperedges ) requires std::same_as { - this->bind(vertex_id, std::views::all(hyperedges)); + this->bind(vertex, std::views::all(hyperedges)); } void bind_tail(const id_type vertex_id, const id_type hyperedge_id) @@ -681,11 +681,11 @@ class hypergraph final { } gl_attr_force_inline void bind_tail( - std::initializer_list vertices, const id_type hyperedge_id + std::initializer_list vertices, const hyperedge_type& hyperedge ) requires std::same_as { - this->bind_tail(std::views::all(vertices), hyperedge_id); + this->bind_tail(std::views::all(vertices), hyperedge); } void bind_tail( @@ -703,7 +703,7 @@ class hypergraph final { gl_attr_force_inline void bind_tail( const id_type vertex_id, std::initializer_list hyperedge_ids ) - requires std::same_as + requires std::same_as { this->bind_tail(vertex_id, std::views::all(hyperedge_ids)); } @@ -718,11 +718,11 @@ class hypergraph final { } gl_attr_force_inline void bind_tail( - const id_type vertex_id, std::initializer_list hyperedges + const vertex_type& vertex, std::initializer_list hyperedges ) - requires std::same_as + requires std::same_as { - this->bind_tail(vertex_id, std::views::all(hyperedges)); + this->bind_tail(vertex, std::views::all(hyperedges)); } void bind_head(const id_type vertex_id, const id_type hyperedge_id) @@ -769,11 +769,11 @@ class hypergraph final { } gl_attr_force_inline void bind_head( - std::initializer_list vertices, const id_type hyperedge_id + std::initializer_list vertices, const hyperedge_type& hyperedge ) requires std::same_as { - this->bind_head(std::views::all(vertices), hyperedge_id); + this->bind_head(std::views::all(vertices), hyperedge); } void bind_head( @@ -791,7 +791,7 @@ class hypergraph final { gl_attr_force_inline void bind_head( const id_type vertex_id, std::initializer_list hyperedge_ids ) - requires std::same_as + requires std::same_as { this->bind_head(vertex_id, std::views::all(hyperedge_ids)); } @@ -806,11 +806,11 @@ class hypergraph final { } gl_attr_force_inline void bind_head( - const id_type vertex_id, std::initializer_list hyperedges + const vertex_type& vertex, std::initializer_list hyperedges ) - requires std::same_as + requires std::same_as { - this->bind_head(vertex_id, std::views::all(hyperedges)); + this->bind_head(vertex, std::views::all(hyperedges)); } void unbind(const id_type vertex_id, const id_type hyperedge_id) { diff --git a/tests/source/hgl/test_hypergraph.cpp b/tests/source/hgl/test_hypergraph.cpp index 83f602b..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,47 +259,31 @@ TEST_CASE_TEMPLATE_DEFINE( CHECK_EQ(sut.size(), constants::n_hyperedges); } - SUBCASE("add_hyperedge_with should initialize a new hyperedge with the input properties " - "structure") { - using properties_traits_type = add_hyperedge_property; - hgl::hypergraph sut; - - const auto hyperedge = sut.add_hyperedge_with(constants::p_true); - REQUIRE_EQ(sut.size(), 1uz); - - CHECK_EQ(hyperedge.id(), constants::id1); - CHECK_EQ(hyperedge.properties(), constants::p_true); - } - SUBCASE("add_hyperedge() should create a hyperedge and bind the given vertices") { - const auto n_vertices = 4uz; - if constexpr (std::same_as) { - sut_type sut{n_vertices}; - const std::vector v_ids{ - constants::id1, constants::id2, constants::id4 - }; + 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::is_permutation(sut.incident_vertex_ids(he1.id()), v_ids)); + 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::is_permutation(sut.incident_vertex_ids(he2.id()), v_ids)); + CHECK(rng::equal(sut.incident_vertex_ids(he2), v_ids)); } if constexpr (std::same_as) { - sut_type sut{n_vertices}; - const std::vector t_ids{constants::id1, constants::id4}; - const std::vector h_ids{constants::id2, constants::id3}; + 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::is_permutation(sut.tail_vertex_ids(he1.id()), t_ids)); - CHECK(rng::is_permutation(sut.head_vertex_ids(he1.id()), h_ids)); + 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); }); @@ -305,47 +291,93 @@ TEST_CASE_TEMPLATE_DEFINE( 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::is_permutation(sut.tail_vertex_ids(he2.id()), t_ids)); - CHECK(rng::is_permutation(sut.head_vertex_ids(he2.id()), h_ids)); + CHECK(rng::equal(sut.tail_vertex_ids(he2), t_ids)); + CHECK(rng::equal(sut.head_vertex_ids(he2), h_ids)); } } - SUBCASE("add_hyperedge_with(, properties) and variants (undirected) should create a " - "hyperedge, bind vertices, and set properties") { - const auto n_vertices = 4uz; + 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; + hgl::hypergraph sut; + + const auto hyperedge = sut.add_hyperedge_with(constants::p_true); + REQUIRE_EQ(sut.size(), 1uz); + + CHECK_EQ(hyperedge.id(), constants::id1); + 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{n_vertices}; - const std::vector v_ids{ - constants::id1, constants::id2, constants::id4 - }; + + 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::is_permutation(sut.incident_vertex_ids(he1.id()), v_ids)); + 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::is_permutation(sut.incident_vertex_ids(he2.id()), v_ids)); + 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{n_vertices}; - const std::vector t_ids{constants::id1, constants::id4}; - const std::vector h_ids{constants::id2, constants::id3}; + + 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::is_permutation(sut.tail_vertex_ids(he1.id()), t_ids)); - CHECK(rng::is_permutation(sut.head_vertex_ids(he1.id()), h_ids)); + 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 = @@ -354,8 +386,56 @@ TEST_CASE_TEMPLATE_DEFINE( 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::is_permutation(sut.tail_vertex_ids(he2.id()), t_ids)); - CHECK(rng::is_permutation(sut.head_vertex_ids(he2.id()), h_ids)); + 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); } } @@ -620,86 +700,137 @@ TEST_CASE_TEMPLATE_DEFINE( } SUBCASE("bulk bind (undirected) should properly mark given sets as incident") { - const auto n_vertices = 4uz; - const auto n_hyperedges = 4uz; - if constexpr (std::same_as) { - sut_type sut{n_vertices, n_hyperedges}; - const std::vector v_ids{constants::id1, constants::id2}; - const std::vector he_ids{constants::id3, constants::id4}; + sut_type sut{4uz, 4uz}; - sut.bind(v_ids, constants::id1); - CHECK(rng::is_permutation(sut.incident_vertex_ids(constants::id1), v_ids)); + std::vector he1_v_ids{0u, 1u}; + sut.bind(he1_v_ids, 0u); + CHECK(rng::equal(sut.incident_vertex_ids(0u), he1_v_ids)); - sut.bind(constants::id3, he_ids); - CHECK(rng::is_permutation(sut.incident_hyperedge_ids(constants::id3), he_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)); - auto vertices = - v_ids | vw::transform([&](const auto id) { return sut.get_vertex(id); }); - auto hyperedges = - he_ids | vw::transform([&](const auto id) { return sut.get_hyperedge(id); }); + std::vector v3_he_ids{2u, 3u}; + sut.bind(2u, v3_he_ids); + CHECK(rng::equal(sut.incident_hyperedge_ids(2u), v3_he_ids)); - sut.bind(vertices, sut.get_hyperedge(constants::id2)); - CHECK(rng::is_permutation(sut.incident_vertex_ids(constants::id2), v_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)); + } + } - sut.bind(sut.get_vertex(constants::id4), hyperedges); - CHECK(rng::is_permutation(sut.incident_hyperedge_ids(constants::id4), 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") { - const auto n_vertices = 4uz; - const auto n_hyperedges = 4uz; - if constexpr (std::same_as) { - sut_type sut{n_vertices, n_hyperedges}; - const std::vector v_ids{constants::id1, constants::id2}; - const std::vector he_ids{constants::id3, constants::id4}; + sut_type sut{4uz, 4uz}; - sut.bind_tail(v_ids, constants::id1); - CHECK(rng::is_permutation(sut.tail_vertex_ids(constants::id1), v_ids)); + 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)); - sut.bind_tail(constants::id3, he_ids); - CHECK(rng::is_permutation(sut.out_hyperedge_ids(constants::id3), he_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)); - auto vertices = - v_ids | vw::transform([&](const auto id) { return sut.get_vertex(id); }); - auto hyperedges = - he_ids | vw::transform([&](const auto id) { return sut.get_hyperedge(id); }); - - sut.bind_tail(vertices, sut.get_hyperedge(constants::id2)); - CHECK(rng::is_permutation(sut.tail_vertex_ids(constants::id2), 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)); - sut.bind_tail(sut.get_vertex(constants::id4), hyperedges); - CHECK(rng::is_permutation(sut.out_hyperedge_ids(constants::id4), 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_head ranges (bf-directed) should properly mark given sets as incident") { - const auto n_vertices = 4uz; - const auto n_hyperedges = 4uz; + 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{n_vertices, n_hyperedges}; - const std::vector v_ids{constants::id1, constants::id2}; - const std::vector he_ids{constants::id3, constants::id4}; + sut_type sut{4uz, 4uz}; - sut.bind_head(v_ids, constants::id1); - CHECK(rng::is_permutation(sut.head_vertex_ids(constants::id1), v_ids)); + 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)); - sut.bind_head(constants::id3, he_ids); - CHECK(rng::is_permutation(sut.in_hyperedge_ids(constants::id3), he_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)); - auto vertices = - v_ids | vw::transform([&](const auto id) { return sut.get_vertex(id); }); - auto hyperedges = - he_ids | vw::transform([&](const auto id) { return sut.get_hyperedge(id); }); + 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)); - sut.bind_head(vertices, sut.get_hyperedge(constants::id2)); - CHECK(rng::is_permutation(sut.head_vertex_ids(constants::id2), 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(constants::id4), hyperedges); - CHECK(rng::is_permutation(sut.in_hyperedge_ids(constants::id4), 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)); } } From 048a8477dd5be00d9ba8405e4209cd0b62bcfaf8 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Mon, 13 Apr 2026 20:14:29 +0200 Subject: [PATCH 4/5] resolved comments --- include/hgl/hypergraph.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/hgl/hypergraph.hpp b/include/hgl/hypergraph.hpp index fba31fa..9bda8e9 100644 --- a/include/hgl/hypergraph.hpp +++ b/include/hgl/hypergraph.hpp @@ -458,14 +458,14 @@ class hypergraph final { } gl_attr_force_inline hyperedge_type add_hyperedge_with( - std::initializer_list tail_verticess, - std::initializer_list head_verticess, + 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_verticess), std::views::all(head_verticess), std::move(properties) + std::views::all(tail_vertices), std::views::all(head_vertices), std::move(properties) ); } From eff914f0bb0692b455378f6d724b315249d6aa3c Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Mon, 13 Apr 2026 20:25:25 +0200 Subject: [PATCH 5/5] other tests --- tests/source/hgl/test_alg_backward_search.cpp | 34 +--- tests/source/hgl/test_alg_bfs.cpp | 125 +++--------- tests/source/hgl/test_alg_dfs.cpp | 125 +++--------- tests/source/hgl/test_alg_forward_search.cpp | 34 +--- tests/source/hgl/test_conversion.cpp | 189 +++++++----------- 5 files changed, 132 insertions(+), 375 deletions(-) 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