diff --git a/include/hgl/directional_tags.hpp b/include/hgl/directional_tags.hpp index d4fa6253..386f6fa9 100644 --- a/include/hgl/directional_tags.hpp +++ b/include/hgl/directional_tags.hpp @@ -4,7 +4,7 @@ #pragma once -#include "types/type_traits.hpp" +#include "hgl/types/type_traits.hpp" namespace hgl { diff --git a/include/hgl/hypergraph.hpp b/include/hgl/hypergraph.hpp index c86fc704..fde678fc 100644 --- a/include/hgl/hypergraph.hpp +++ b/include/hgl/hypergraph.hpp @@ -4,9 +4,9 @@ #pragma once -#include "constants.hpp" -#include "hypergraph_traits.hpp" -#include "util.hpp" +#include "hgl/constants.hpp" +#include "hgl/hypergraph_traits.hpp" +#include "hgl/util.hpp" #include #include @@ -21,6 +21,8 @@ class hypergraph final { using traits_type = HypergraphTraits; using directional_tag = typename traits_type::directional_tag; using implementation_tag = typename traits_type::implementation_tag; + using implementation_type = + typename implementation_tag::template implementation_type; using vertex_type = typename traits_type::vertex_type; using vertex_properties_type = typename traits_type::vertex_properties_type; @@ -40,7 +42,7 @@ class hypergraph final { hypergraph& operator=(const hypergraph&) = delete; hypergraph(const types::size_type n_vertices = 0uz, const types::size_type n_hyperedges = 0uz) - : _n_vertices(n_vertices), _n_hyperedges(n_hyperedges) { + : _n_vertices(n_vertices), _n_hyperedges(n_hyperedges), _impl(n_vertices, n_hyperedges) { if constexpr (type_traits::c_non_empty_properties) { this->_vertex_properties.reserve(n_vertices); for (const auto _ : this->vertex_ids()) @@ -67,21 +69,8 @@ class hypergraph final { // --- vertex methods --- - [[nodiscard]] gl_attr_force_inline auto vertices() const - requires(type_traits::c_empty_properties) - { - return this->vertex_ids() - | std::views::transform([](const types::id_type id) { return vertex_type{id}; }); - } - - [[nodiscard]] gl_attr_force_inline auto vertices() const - requires(type_traits::c_non_empty_properties) - { - return this->_vertex_properties | std::views::enumerate - | std::views::transform([](const auto& property_item) { - const auto& [id, ptr] = property_item; - return vertex_type{static_cast(id), *ptr}; - }); + [[nodiscard]] gl_attr_force_inline auto vertices() noexcept { + return this->vertex_ids() | std::views::transform(this->_create_vertex_descriptor()); } [[nodiscard]] gl_attr_force_inline auto vertex_ids() const noexcept { @@ -105,7 +94,7 @@ class hypergraph final { } vertex_type add_vertex() { - // this->_impl.add_vertex(); + this->_impl.add_vertices(1uz); const auto new_vertex_id = this->_n_vertices++; if constexpr (type_traits::c_non_empty_properties) @@ -120,7 +109,7 @@ class hypergraph final { vertex_type add_vertex_with(vertex_properties_type properties) requires(type_traits::c_non_empty_properties) { - // this->_impl.add_vertex(); + this->_impl.add_vertices(1uz); return vertex_type{ this->_n_vertices++, *this->_vertex_properties.emplace_back( @@ -130,7 +119,7 @@ class hypergraph final { } void add_vertices(const types::size_type n) { - // this->_impl.add_vertices(n); + this->_impl.add_vertices(n); this->_n_vertices += n; if constexpr (type_traits::c_non_empty_properties) { @@ -148,7 +137,7 @@ class hypergraph final { { const auto n = std::ranges::size(properties_range); - // this->_impl.add_vertices(n); + this->_impl.add_vertices(n); this->_n_vertices += n; if constexpr (type_traits::c_non_empty_properties) { @@ -160,8 +149,7 @@ class hypergraph final { } } - void remove_vertex(const types::id_type vertex_id) { - this->_verify_vertex_id(vertex_id); + gl_attr_force_inline void remove_vertex(const types::id_type vertex_id) { this->_remove_vertex_impl(vertex_id); } @@ -210,21 +198,8 @@ class hypergraph final { // --- hyperedge methods --- - [[nodiscard]] gl_attr_force_inline auto hyperedges() const - requires(type_traits::c_empty_properties) - { - return this->hyperedge_ids() - | std::views::transform([](const types::id_type id) { return hyperedge_type{id}; }); - } - - [[nodiscard]] gl_attr_force_inline auto hyperedges() const - requires(type_traits::c_non_empty_properties) - { - return this->_hyperedge_properties | std::views::enumerate - | std::views::transform([](const auto& property_item) { - const auto& [id, ptr] = property_item; - return hyperedge_type{static_cast(id), *ptr}; - }); + [[nodiscard]] gl_attr_force_inline auto hyperedges() noexcept { + return this->hyperedge_ids() | std::views::transform(this->_create_hyperedge_descriptor()); } [[nodiscard]] gl_attr_force_inline auto hyperedge_ids() const noexcept { @@ -248,7 +223,7 @@ class hypergraph final { } hyperedge_type add_hyperedge() { - // this->_impl.add_hyperedge(); + this->_impl.add_hyperedges(1uz); const auto new_hyperedge_id = this->_n_hyperedges++; if constexpr (type_traits::c_non_empty_properties) @@ -265,7 +240,7 @@ class hypergraph final { hyperedge_type add_hyperedge_with(hyperedge_properties_type properties) requires(type_traits::c_non_empty_properties) { - // this->_impl.add_hyperedge(); + this->_impl.add_hyperedges(1uz); return hyperedge_type{ this->_n_hyperedges++, *this->_hyperedge_properties.emplace_back( @@ -275,7 +250,7 @@ class hypergraph final { } void add_hyperedges(const types::size_type n) { - // this->_impl.add_hyperedges(n); + this->_impl.add_hyperedges(n); this->_n_hyperedges += n; if constexpr (type_traits::c_non_empty_properties) { @@ -294,7 +269,7 @@ class hypergraph final { { const auto n = std::ranges::size(properties_range); - // this->_impl.add_hyperedges(n); + this->_impl.add_hyperedges(n); this->_n_hyperedges += n; if constexpr (type_traits::c_non_empty_properties) { @@ -306,8 +281,7 @@ class hypergraph final { } } - void remove_hyperedge(const types::id_type hyperedge_id) { - this->_verify_hyperedge_id(hyperedge_id); + gl_attr_force_inline void remove_hyperedge(const types::id_type hyperedge_id) { this->_remove_hyperedge_impl(hyperedge_id); } @@ -356,6 +330,82 @@ class hypergraph final { return *this->_hyperedge_properties[id]; } + // --- incidence methods --- + + void bind(const types::id_type vertex_id, const types::id_type hyperedge_id) { + this->_verify_vertex_id(vertex_id); + this->_verify_hyperedge_id(hyperedge_id); + this->_impl.bind(vertex_id, hyperedge_id); + } + + gl_attr_force_inline void bind(const vertex_type& vertex, const hyperedge_type& hyperedge) { + this->bind(vertex.id(), hyperedge.id()); + } + + void unbind(const types::id_type vertex_id, const types::id_type hyperedge_id) { + this->_verify_vertex_id(vertex_id); + this->_verify_hyperedge_id(hyperedge_id); + this->_impl.unbind(vertex_id, hyperedge_id); + } + + gl_attr_force_inline void unbind(const vertex_type& vertex, const hyperedge_type& hyperedge) { + this->unbind(vertex.id(), hyperedge.id()); + } + + [[nodiscard]] bool are_incident( + const types::id_type vertex_id, const types::id_type hyperedge_id + ) const { + this->_verify_vertex_id(vertex_id); + this->_verify_hyperedge_id(hyperedge_id); + return this->_impl.are_bound(vertex_id, hyperedge_id); + } + + [[nodiscard]] gl_attr_force_inline bool are_incident( + const vertex_type& vertex, const hyperedge_type& hyperedge + ) const { + return this->are_incident(vertex.id(), hyperedge.id()); + } + + [[nodiscard]] auto incident_hyperedges(const types::id_type vertex_id) { + this->_verify_vertex_id(vertex_id); + return this->_impl.incident_hyperedges(vertex_id) + | std::views::transform(this->_create_hyperedge_descriptor()); + } + + [[nodiscard]] gl_attr_force_inline auto incident_hyperedges(const vertex_type& vertex) { + return this->incident_hyperedges(vertex.id()); + } + + [[nodiscard]] types::size_type degree(const types::id_type vertex_id) const { + this->_verify_vertex_id(vertex_id); + return this->_impl.degree(vertex_id); + } + + [[nodiscard]] gl_attr_force_inline types::size_type degree(const vertex_type& vertex) const { + return this->degree(vertex.id()); + } + + [[nodiscard]] auto incident_vertices(const types::id_type hyperedge_id) { + this->_verify_hyperedge_id(hyperedge_id); + return this->_impl.incident_vertices(hyperedge_id) + | std::views::transform(this->_create_vertex_descriptor()); + } + + [[nodiscard]] gl_attr_force_inline auto incident_vertices(const hyperedge_type& hyperedge) { + return this->incident_vertices(hyperedge.id()); + } + + [[nodiscard]] types::size_type hyperedge_size(const types::id_type hyperedge_id) const { + this->_verify_hyperedge_id(hyperedge_id); + return this->_impl.hyperedge_size(hyperedge_id); + } + + [[nodiscard]] gl_attr_force_inline types::size_type hyperedge_size( + const hyperedge_type& hyperedge + ) const { + return this->hyperedge_size(hyperedge.id()); + } + private: // --- vertex methods --- @@ -365,10 +415,13 @@ class hypergraph final { } void _remove_vertex_impl(const types::id_type vertex_id) { + if (not this->has_vertex(vertex_id)) + return; + + this->_impl.remove_vertex(vertex_id); this->_n_vertices--; if constexpr (type_traits::c_non_empty_properties) this->_vertex_properties.erase(this->_vertex_properties.begin() + vertex_id); - // TODO: impl::remove_vertex(vertex_id) } // --- hyperedge methods --- @@ -379,14 +432,52 @@ class hypergraph final { } void _remove_hyperedge_impl(const types::id_type hyperedge_id) { + if (not this->has_hyperedge(hyperedge_id)) + return; + + this->_impl.remove_hyperedge(hyperedge_id); this->_n_hyperedges--; if constexpr (type_traits::c_non_empty_properties) this->_hyperedge_properties.erase(this->_hyperedge_properties.begin() + hyperedge_id); } + // --- transformations --- + + gl_attr_force_inline auto _create_vertex_descriptor() noexcept + requires(type_traits::c_empty_properties) + { + return [](const types::id_type id) { return vertex_type{id}; }; + } + + gl_attr_force_inline auto _create_vertex_descriptor() noexcept + requires(type_traits::c_non_empty_properties) + { + return [&pmap = this->_vertex_properties](const types::id_type id) { + return vertex_type{id, *pmap[id]}; + }; + } + + gl_attr_force_inline auto _create_hyperedge_descriptor() noexcept + requires(type_traits::c_empty_properties) + { + return [](const types::id_type id) { return hyperedge_type{id}; }; + } + + gl_attr_force_inline auto _create_hyperedge_descriptor() noexcept + requires(type_traits::c_non_empty_properties) + { + return [&pmap = this->_hyperedge_properties](const types::id_type id) { + return hyperedge_type{id, *pmap[id]}; + }; + } + + // --- data members --- + types::size_type _n_vertices = 0uz; types::size_type _n_hyperedges = 0uz; + implementation_type _impl{}; + [[no_unique_address]] vertex_properties_map_type _vertex_properties{}; [[no_unique_address]] hyperedge_properties_map_type _hyperedge_properties{}; }; diff --git a/include/hgl/hypergraph_elements.hpp b/include/hgl/hypergraph_elements.hpp index 2766b541..1a75e2f6 100644 --- a/include/hgl/hypergraph_elements.hpp +++ b/include/hgl/hypergraph_elements.hpp @@ -4,10 +4,10 @@ #pragma once -#include "constants.hpp" #include "gl/vertex_descriptor.hpp" -#include "types/type_traits.hpp" -#include "types/types.hpp" +#include "hgl/constants.hpp" +#include "hgl/types/type_traits.hpp" +#include "hgl/types/types.hpp" namespace hgl { diff --git a/include/hgl/hypergraph_traits.hpp b/include/hgl/hypergraph_traits.hpp index e8b31e31..474b652b 100644 --- a/include/hgl/hypergraph_traits.hpp +++ b/include/hgl/hypergraph_traits.hpp @@ -4,10 +4,10 @@ #pragma once -#include "directional_tags.hpp" -#include "hypergraph_elements.hpp" -#include "impl/impl_tags.hpp" -#include "impl/layout_tags.hpp" +#include "hgl/directional_tags.hpp" +#include "hgl/hypergraph_elements.hpp" +#include "hgl/impl/impl_tags.hpp" +#include "hgl/impl/layout_tags.hpp" namespace hgl { diff --git a/include/hgl/impl/impl_tags.hpp b/include/hgl/impl/impl_tags.hpp index 0227b1b3..f038695f 100644 --- a/include/hgl/impl/impl_tags.hpp +++ b/include/hgl/impl/impl_tags.hpp @@ -4,9 +4,12 @@ #pragma once +#include "hgl/directional_tags.hpp" +#include "hgl/impl/incidence_list.hpp" +#include "hgl/impl/incidence_matrix.hpp" +#include "hgl/impl/layout_tags.hpp" #include "hgl/types/type_traits.hpp" #include "hgl/types/types.hpp" -#include "layout_tags.hpp" namespace hgl { @@ -15,11 +18,17 @@ namespace impl { template struct list_t { using layout_tag = LayoutTag; + + template + using implementation_type = incidence_list; }; template struct matrix_t { using layout_tag = LayoutTag; + + template + using implementation_type = incidence_matrix; }; } // namespace impl diff --git a/include/hgl/impl/incidence_list.hpp b/include/hgl/impl/incidence_list.hpp index 044df439..83f2cee0 100644 --- a/include/hgl/impl/incidence_list.hpp +++ b/include/hgl/impl/incidence_list.hpp @@ -4,8 +4,9 @@ #pragma once +#include "hgl/directional_tags.hpp" +#include "hgl/impl/layout_tags.hpp" #include "hgl/types/types.hpp" -#include "layout_tags.hpp" #include #include @@ -19,23 +20,28 @@ struct test_incidence_list; namespace hgl::impl { +template < + type_traits::c_hypergraph_directional_tag DirectionalTag, + type_traits::c_hypergraph_layout_tag LayoutTag> +class incidence_list; + template -class undirected_incidence_list final { +class incidence_list final { public: using layout_tag = LayoutTag; - undirected_incidence_list(const undirected_incidence_list&) = delete; - undirected_incidence_list& operator=(const undirected_incidence_list&) = delete; + incidence_list(const incidence_list&) = delete; + incidence_list& operator=(const incidence_list&) = delete; - undirected_incidence_list() = default; + incidence_list() = default; - undirected_incidence_list(const types::size_type n_vertices, const types::size_type n_hyperedges) + incidence_list(const types::size_type n_vertices, const types::size_type n_hyperedges) : _major_storage{layout_tag::major(n_vertices, n_hyperedges)} {} - undirected_incidence_list(undirected_incidence_list&&) = default; - undirected_incidence_list& operator=(undirected_incidence_list&&) = default; + incidence_list(incidence_list&&) = default; + incidence_list& operator=(incidence_list&&) = default; - ~undirected_incidence_list() = default; + ~incidence_list() = default; // --- vertex methods --- diff --git a/include/hgl/impl/incidence_matrix.hpp b/include/hgl/impl/incidence_matrix.hpp index ba13c38c..c2bcee15 100644 --- a/include/hgl/impl/incidence_matrix.hpp +++ b/include/hgl/impl/incidence_matrix.hpp @@ -4,8 +4,9 @@ #pragma once +#include "hgl/directional_tags.hpp" +#include "hgl/impl/layout_tags.hpp" #include "hgl/types/types.hpp" -#include "layout_tags.hpp" #include #include @@ -19,28 +20,31 @@ struct test_incidence_matrix; namespace hgl::impl { +template < + type_traits::c_hypergraph_directional_tag DirectionalTag, + type_traits::c_hypergraph_layout_tag LayoutTag> +class incidence_matrix; + template -class undirected_incidence_matrix final { +class incidence_matrix final { public: using layout_tag = LayoutTag; - undirected_incidence_matrix(const undirected_incidence_matrix&) = delete; - undirected_incidence_matrix& operator=(const undirected_incidence_matrix&) = delete; + incidence_matrix(const incidence_matrix&) = delete; + incidence_matrix& operator=(const incidence_matrix&) = delete; - undirected_incidence_matrix() = default; + incidence_matrix() = default; - undirected_incidence_matrix( - const types::size_type n_vertices, const types::size_type n_hyperedges - ) + incidence_matrix(const types::size_type n_vertices, const types::size_type n_hyperedges) : _matrix_row_size{layout_tag::minor(n_vertices, n_hyperedges)}, _matrix( layout_tag::major(n_vertices, n_hyperedges), matrix_row_type(_matrix_row_size, false) ) {} - undirected_incidence_matrix(undirected_incidence_matrix&&) = default; - undirected_incidence_matrix& operator=(undirected_incidence_matrix&&) = default; + incidence_matrix(incidence_matrix&&) = default; + incidence_matrix& operator=(incidence_matrix&&) = default; - ~undirected_incidence_matrix() = default; + ~incidence_matrix() = default; // --- vertex methods --- diff --git a/tests/source/hgl/test_hypergraph.cpp b/tests/source/hgl/test_hypergraph.cpp index 072d1bc8..32b47aca 100644 --- a/tests/source/hgl/test_hypergraph.cpp +++ b/tests/source/hgl/test_hypergraph.cpp @@ -93,7 +93,7 @@ TEST_CASE_TEMPLATE_DEFINE( const auto vertex = sut.add_vertex(); CHECK_EQ(vertex.id(), v_id); CHECK_EQ(sut.n_vertices(), v_id + 1uz); - // TODO: check no hyperedges + CHECK_EQ(sut.degree(v_id), 0uz); } CHECK_EQ(sut.n_vertices(), constants::n_vertices); @@ -109,7 +109,7 @@ TEST_CASE_TEMPLATE_DEFINE( CHECK_EQ(vertex.id(), constants::id1); CHECK_EQ(vertex.properties(), constants::p_true); - // TODO: check no hyperedges + CHECK_EQ(sut.degree(vertex), 0uz); } SUBCASE("add_vertices(n) should add n new vertices to the hypergraph") { @@ -164,11 +164,12 @@ TEST_CASE_TEMPLATE_DEFINE( CHECK_EQ(sut.get_vertex(added_vertex.id()), added_vertex); } - SUBCASE("remove_vertex(vertex) should throw if the given vertex is invalid") { + SUBCASE("remove_vertex(vertex) should do nothing if the given vertex is invalid") { sut_type sut{constants::n_vertices}; - CHECK_THROWS_AS( - sut.remove_vertex(vertex_type{constants::out_of_rng_vid}), std::out_of_range - ); + REQUIRE_EQ(sut.n_vertices(), constants::n_vertices); + + CHECK_NOTHROW(sut.remove_vertex(vertex_type{constants::out_of_rng_vid})); + CHECK_EQ(sut.n_vertices(), constants::n_vertices); } SUBCASE("remove_vertex(vertex) should remove the given vertex and align ids of remaining " @@ -182,9 +183,12 @@ TEST_CASE_TEMPLATE_DEFINE( ); } - SUBCASE("remove_vertex(id) should throw if the given id is invalid") { + SUBCASE("remove_vertex(id) should do nothing if the given id is invalid") { sut_type sut{constants::n_vertices}; - CHECK_THROWS_AS(sut.remove_vertex(constants::out_of_rng_vid), std::out_of_range); + REQUIRE_EQ(sut.n_vertices(), constants::n_vertices); + + CHECK_NOTHROW(sut.remove_vertex(constants::out_of_rng_vid)); + CHECK_EQ(sut.n_vertices(), constants::n_vertices); } SUBCASE("remove_vertex(id) should remove the given vertex and align ids of remaining vertices" @@ -306,11 +310,12 @@ TEST_CASE_TEMPLATE_DEFINE( CHECK_EQ(sut.get_hyperedge(added_hyperedge.id()), added_hyperedge); } - SUBCASE("remove_hyperedge(hyperedge) should throw if the given hyperedge is invalid") { + SUBCASE("remove_hyperedge(hyperedge) should do nothing if the given hyperedge is invalid") { sut_type sut{0uz, constants::n_hyperedges}; - CHECK_THROWS_AS( - sut.remove_hyperedge(hyperedge_type{constants::out_of_rng_eid}), std::out_of_range - ); + REQUIRE_EQ(sut.n_hyperedges(), constants::n_hyperedges); + + CHECK_NOTHROW(sut.remove_hyperedge(hyperedge_type{constants::out_of_rng_eid})); + CHECK_EQ(sut.n_hyperedges(), constants::n_hyperedges); } SUBCASE("remove_hyperedge(hyperedge) should remove the given hyperedge and align ids of " @@ -324,9 +329,12 @@ TEST_CASE_TEMPLATE_DEFINE( ); } - SUBCASE("remove_hyperedge(id) should throw if the given id is invalid") { + SUBCASE("remove_hyperedge(id) should do nothing if the given id is invalid") { sut_type sut{0uz, constants::n_hyperedges}; - CHECK_THROWS_AS(sut.remove_hyperedge(constants::out_of_rng_eid), std::out_of_range); + REQUIRE_EQ(sut.n_hyperedges(), constants::n_hyperedges); + + CHECK_NOTHROW(sut.remove_hyperedge(constants::out_of_rng_eid)); + CHECK_EQ(sut.n_hyperedges(), constants::n_hyperedges); } SUBCASE("remove_hyperedge(id) should remove the given hyperedge and align ids of remaining " @@ -365,6 +373,178 @@ TEST_CASE_TEMPLATE_DEFINE( constexpr auto expected_n_hyperedges = n_hyperedges - 2uz; REQUIRE_EQ(sut.n_hyperedges(), expected_n_hyperedges); } + + // --- incidence method tests --- + + SUBCASE("bind, unbind and are_incident should throw if either of the fiven elements is invalid" + ) { + sut_type sut{constants::n_vertices, constants::n_hyperedges}; + + CHECK_THROWS_AS( + sut.bind(constants::out_of_rng_vid, constants::out_of_rng_eid), std::out_of_range + ); + CHECK_THROWS_AS(sut.bind(constants::id1, constants::out_of_rng_eid), std::out_of_range); + CHECK_THROWS_AS(sut.bind(constants::out_of_rng_vid, constants::id1), std::out_of_range); + + CHECK_THROWS_AS( + sut.unbind(constants::out_of_rng_vid, constants::out_of_rng_eid), std::out_of_range + ); + CHECK_THROWS_AS(sut.unbind(constants::id1, constants::out_of_rng_eid), std::out_of_range); + CHECK_THROWS_AS(sut.unbind(constants::out_of_rng_vid, constants::id1), std::out_of_range); + + CHECK_THROWS_AS( + static_cast(sut.are_incident(constants::out_of_rng_vid, constants::out_of_rng_eid) + ), + std::out_of_range + ); + CHECK_THROWS_AS( + static_cast(sut.are_incident(constants::id1, constants::out_of_rng_eid)), + std::out_of_range + ); + CHECK_THROWS_AS( + static_cast(sut.are_incident(constants::out_of_rng_vid, constants::id1)), + std::out_of_range + ); + } + + SUBCASE("are_incident should return false by default") { + sut_type sut{constants::n_vertices, constants::n_hyperedges}; + for (const auto vertex : sut.vertices()) + for (const auto hyperedge : sut.hyperedges()) + CHECK_FALSE(sut.are_incident(vertex, hyperedge)); + } + + SUBCASE("bind should properly mark the given vertex and hyperedge as incident and unbind " + "should mark them as not incident") { + constexpr auto vertex_id = constants::id1; + constexpr auto hyperedge_id = constants::id2; + constexpr auto unbound_id = constants::id3; + + sut_type sut{constants::n_vertices, constants::n_hyperedges}; + REQUIRE_FALSE(sut.are_incident(vertex_id, hyperedge_id)); + REQUIRE_FALSE(sut.are_incident(vertex_id, unbound_id)); + REQUIRE_FALSE(sut.are_incident(unbound_id, hyperedge_id)); + + sut.bind(vertex_id, hyperedge_id); + CHECK(sut.are_incident(vertex_id, hyperedge_id)); + CHECK_FALSE(sut.are_incident(vertex_id, unbound_id)); + CHECK_FALSE(sut.are_incident(unbound_id, hyperedge_id)); + + sut.unbind(vertex_id, hyperedge_id); + CHECK_FALSE(sut.are_incident(vertex_id, hyperedge_id)); + CHECK_FALSE(sut.are_incident(vertex_id, unbound_id)); + CHECK_FALSE(sut.are_incident(unbound_id, hyperedge_id)); + } + + SUBCASE("incident_hyperedges and degree should throw if the given vertex (id) is invalid") { + sut_type sut{constants::n_vertices, constants::n_hyperedges}; + CHECK_THROWS_AS( + static_cast(sut.incident_hyperedges(vertex_type{constants::out_of_rng_vid})), + std::out_of_range + ); + CHECK_THROWS_AS( + static_cast(sut.degree(vertex_type{constants::out_of_rng_vid})), std::out_of_range + ); + } + + SUBCASE("incident_hyperedges should return an empty view by default and degree should return 0 " + "by default") { + sut_type sut{constants::n_vertices, constants::n_hyperedges}; + CHECK(std::ranges::all_of(sut.vertices(), [&sut](const auto& vertex) { + return std::ranges::empty(sut.incident_hyperedges(vertex)); + })); + CHECK(std::ranges::all_of(sut.vertices(), [&sut](const auto& vertex) { + return sut.degree(vertex) == 0uz; + })); + } + + SUBCASE("incident_hyperedges should return a view containing ids of hyperedges incident with " + "the given vertex, and degree should return their count") { + sut_type sut{constants::n_vertices, constants::n_hyperedges}; + constexpr auto vertex_id = constants::id1; + + SUBCASE("sequential hyperedges") { + std::vector expected_hyperedges{}; + for (const auto eid : sut.hyperedge_ids()) { + sut.bind(vertex_id, eid); + expected_hyperedges.push_back(eid); + } + + CHECK(std::ranges::equal( + sut.incident_hyperedges(vertex_id), expected_hyperedges, std::equal_to{}, get_id + )); + CHECK_EQ(sut.degree(vertex_id), expected_hyperedges.size()); + } + + SUBCASE("specific hyperedges") { + sut.bind(vertex_id, constants::id2); + sut.bind(vertex_id, constants::id4); + + CHECK(std::ranges::equal( + sut.incident_hyperedges(vertex_id), + std::vector{constants::id2, constants::id4}, + std::equal_to{}, + get_id + )); + CHECK_EQ(sut.degree(vertex_id), 2uz); + } + } + + SUBCASE("incident_vertices and hyperedge_size should throw if the given hyperedge (id) is " + "invalid") { + sut_type sut{constants::n_vertices, constants::n_hyperedges}; + CHECK_THROWS_AS( + static_cast(sut.incident_vertices(hyperedge_type{constants::out_of_rng_eid})), + std::out_of_range + ); + CHECK_THROWS_AS( + static_cast(sut.hyperedge_size(hyperedge_type{constants::out_of_rng_eid})), + std::out_of_range + ); + } + + SUBCASE("incident_vertices should return an empty view by default and hyperedge_size should " + "return 0 by default") { + sut_type sut{constants::n_vertices, constants::n_hyperedges}; + CHECK(std::ranges::all_of(sut.hyperedges(), [&sut](const auto& hyperedge) { + return std::ranges::empty(sut.incident_vertices(hyperedge)); + })); + CHECK(std::ranges::all_of(sut.vertices(), [&sut](const auto& hyperedge) { + return sut.hyperedge_size(hyperedge) == 0uz; + })); + } + + SUBCASE("incident_vertices should return a view containing ids of vertices incident with the " + "given hyperedge, and hyperedge_size should return their count") { + sut_type sut{constants::n_vertices, constants::n_hyperedges}; + constexpr auto hyperedge_id = constants::id1; + + SUBCASE("sequential vertices") { + std::vector expected_vertices{}; + for (const auto vid : sut.vertex_ids()) { + sut.bind(vid, hyperedge_id); + expected_vertices.push_back(vid); + } + + CHECK(std::ranges::equal( + sut.incident_vertices(hyperedge_id), expected_vertices, std::equal_to{}, get_id + )); + CHECK_EQ(sut.hyperedge_size(hyperedge_id), expected_vertices.size()); + } + + SUBCASE("specific vertices") { + sut.bind(constants::id1, hyperedge_id); + sut.bind(constants::id3, hyperedge_id); + + CHECK(std::ranges::equal( + sut.incident_vertices(hyperedge_id), + std::vector{constants::id1, constants::id3}, + std::equal_to{}, + get_id + )); + CHECK_EQ(sut.hyperedge_size(hyperedge_id), 2uz); + } + } } TEST_CASE_TEMPLATE_INSTANTIATE( @@ -372,33 +552,36 @@ TEST_CASE_TEMPLATE_INSTANTIATE( hgl::list_hypergraph_traits< hgl::impl::hyperedge_major_t, hgl::undirected_t>, // undirected hyperedge-major incidence list - hgl::list_hypergraph_traits< - hgl::impl::hyperedge_major_t, - hgl::bf_directed_t>, // bf-directed hyperedge-major incidence list hgl::list_hypergraph_traits< hgl::impl::vertex_major_t, hgl::undirected_t>, // undirected vertex-major incidence list - hgl::list_hypergraph_traits< - hgl::impl::vertex_major_t, - hgl::bf_directed_t>, // bf-directed vertex-major incidence list hgl::matrix_hypergraph_traits< hgl::impl::hyperedge_major_t, hgl::undirected_t>, // undirected hyperedge-major incidence matrix - hgl::matrix_hypergraph_traits< - hgl::impl::hyperedge_major_t, - hgl::bf_directed_t>, // bf-directed hyperedge-major incidence matrix hgl::matrix_hypergraph_traits< hgl::impl::vertex_major_t, - hgl::undirected_t>, // undirected vertex-major incidence matrix - hgl::matrix_hypergraph_traits< - hgl::impl::vertex_major_t, - hgl::bf_directed_t> // bf-directed vertex-major incidence matrix + hgl::undirected_t> // undirected vertex-major incidence matrix + // TODO: uncomment after the bf-directed models are implemented + // hgl::list_hypergraph_traits< + // hgl::impl::hyperedge_major_t, + // hgl::bf_directed_t>, // bf-directed hyperedge-major incidence list + // hgl::list_hypergraph_traits< + // hgl::impl::vertex_major_t, + // hgl::bf_directed_t>, // bf-directed vertex-major incidence list + // hgl::matrix_hypergraph_traits< + // hgl::impl::hyperedge_major_t, + // hgl::bf_directed_t>, // bf-directed hyperedge-major incidence matrix + // hgl::matrix_hypergraph_traits< + // hgl::impl::vertex_major_t, + // hgl::bf_directed_t> // bf-directed vertex-major incidence matrix ); TEST_CASE_TEMPLATE_DEFINE( "properties getter tests", HypergraphTraits, property_hypergraph_traits_template ) { using sut_type = hgl::hypergraph; + using vertex_properties_type = typename sut_type::vertex_properties_type; + using hyperedge_properties_type = typename sut_type::hyperedge_properties_type; sut_type sut{constants::n_vertices, constants::n_hyperedges}; for (auto vertex : sut.vertices()) @@ -406,28 +589,107 @@ TEST_CASE_TEMPLATE_DEFINE( for (auto hyperedge : sut.hyperedges()) hyperedge.properties() = std::format("hyperedge_{}", hyperedge.id()); - auto vmap = sut.vertex_properties_map(); - CHECK(vmap.size() == constants::n_vertices); - for (auto [id, property] : vw::zip(sut.vertex_ids(), vmap)) { - CHECK_EQ(property, std::format("vertex_{}", id)); - CHECK_EQ(vmap[id], std::format("vertex_{}", id)); - CHECK_EQ(sut.get_vertex_properties(id), std::format("vertex_{}", id)); - } - CHECK_THROWS_AS( - static_cast(sut.get_vertex_properties(constants::out_of_rng_vid)), std::out_of_range - ); - - auto emap = sut.hyperedge_properties_map(); - CHECK(emap.size() == constants::n_hyperedges); - for (auto [id, property] : vw::zip(sut.hyperedge_ids(), emap)) { - CHECK_EQ(property, std::format("hyperedge_{}", id)); - CHECK_EQ(emap[id], std::format("hyperedge_{}", id)); - CHECK_EQ(sut.get_hyperedge_properties(id), std::format("hyperedge_{}", id)); - } - CHECK_THROWS_AS( - static_cast(sut.get_hyperedge_properties(constants::out_of_rng_eid)), - std::out_of_range - ); + SUBCASE("vertex property getters should return proper property objects/view") { + auto vmap = sut.vertex_properties_map(); + CHECK(vmap.size() == constants::n_vertices); + for (auto [id, property] : vw::zip(sut.vertex_ids(), vmap)) { + CHECK_EQ(property, std::format("vertex_{}", id)); + CHECK_EQ(vmap[id], std::format("vertex_{}", id)); + CHECK_EQ(sut.get_vertex_properties(id), std::format("vertex_{}", id)); + } + CHECK_THROWS_AS( + static_cast(sut.get_vertex_properties(constants::out_of_rng_vid)), + std::out_of_range + ); + } + + SUBCASE("hyperedge property getters should return proper property objects/view") { + auto emap = sut.hyperedge_properties_map(); + CHECK(emap.size() == constants::n_hyperedges); + for (auto [id, property] : vw::zip(sut.hyperedge_ids(), emap)) { + CHECK_EQ(property, std::format("hyperedge_{}", id)); + CHECK_EQ(emap[id], std::format("hyperedge_{}", id)); + CHECK_EQ(sut.get_hyperedge_properties(id), std::format("hyperedge_{}", id)); + } + CHECK_THROWS_AS( + static_cast(sut.get_hyperedge_properties(constants::out_of_rng_eid)), + std::out_of_range + ); + } + + constexpr auto get_property_addr = [](const auto& descriptor) { + return &descriptor.properties(); + }; + + SUBCASE("incident_hyperedges should return a view of hyperedge_descriptor objects containing " + "correct property references") { + constexpr auto vertex_id = constants::id1; + + SUBCASE("sequential hyperedges") { + std::vector expected_properties{}; + for (const auto hyperedge_id : sut.hyperedge_ids()) { + sut.bind(vertex_id, hyperedge_id); + expected_properties.push_back(&sut.get_hyperedge_properties(hyperedge_id)); + + CHECK(std::ranges::equal( + sut.incident_hyperedges(vertex_id), + expected_properties, + std::equal_to{}, + get_property_addr + )); + } + } + + SUBCASE("specific hyperedges") { + sut.bind(vertex_id, constants::id2); + sut.bind(vertex_id, constants::id4); + + CHECK(std::ranges::equal( + sut.incident_hyperedges(vertex_id), + std::vector{ + &sut.get_hyperedge_properties(constants::id2), + &sut.get_hyperedge_properties(constants::id4) + }, + std::equal_to{}, + get_property_addr + )); + } + } + + SUBCASE("incident_vertices should return a view of vertex_descriptor objects containing " + "correct property references") { + constexpr auto hyperedge_id = constants::id1; + + SUBCASE("sequential vertices") { + std::vector expected_properties{}; + for (const auto vertex_id : sut.vertex_ids()) { + sut.bind(vertex_id, hyperedge_id); + expected_properties.push_back(&sut.get_vertex_properties(vertex_id)); + + CHECK(std::ranges::equal( + sut.incident_vertices(hyperedge_id), + expected_properties, + std::equal_to{}, + get_property_addr + )); + } + } + + SUBCASE("specific vertices") { + sut.bind(constants::id1, hyperedge_id); + sut.bind(constants::id3, hyperedge_id); + + CHECK(std::ranges::equal( + sut.incident_vertices(hyperedge_id), + std::vector{ + &sut.get_vertex_properties(constants::id1), + &sut.get_vertex_properties(constants::id3) + }, + std::equal_to{}, + get_property_addr + )); + } + } } TEST_CASE_TEMPLATE_INSTANTIATE( @@ -437,41 +699,42 @@ TEST_CASE_TEMPLATE_INSTANTIATE( hgl::undirected_t, hgl::types::name_property, hgl::types::name_property>, // undirected hyperedge-major incidence list - hgl::list_hypergraph_traits< - hgl::impl::hyperedge_major_t, - hgl::bf_directed_t, - hgl::types::name_property, - hgl::types::name_property>, // bf-directed hyperedge-major incidence list hgl::list_hypergraph_traits< hgl::impl::vertex_major_t, hgl::undirected_t, hgl::types::name_property, hgl::types::name_property>, // undirected vertex-major incidence list - hgl::list_hypergraph_traits< - hgl::impl::vertex_major_t, - hgl::bf_directed_t, - hgl::types::name_property, - hgl::types::name_property>, // bf-directed vertex-major incidence list hgl::matrix_hypergraph_traits< hgl::impl::hyperedge_major_t, hgl::undirected_t, hgl::types::name_property, hgl::types::name_property>, // undirected hyperedge-major incidence matrix - hgl::matrix_hypergraph_traits< - hgl::impl::hyperedge_major_t, - hgl::bf_directed_t, - hgl::types::name_property, - hgl::types::name_property>, // bf-directed hyperedge-major incidence matrix hgl::matrix_hypergraph_traits< hgl::impl::vertex_major_t, hgl::undirected_t, hgl::types::name_property, - hgl::types::name_property>, // undirected vertex-major incidence matrix - hgl::matrix_hypergraph_traits< - hgl::impl::vertex_major_t, - hgl::bf_directed_t, - hgl::types::name_property, - hgl::types::name_property> // bf-directed vertex-major incidence matrix + hgl::types::name_property> // undirected vertex-major incidence matrix + // TODO: uncomment after the bf-directed models are implemented + // hgl::list_hypergraph_traits< + // hgl::impl::hyperedge_major_t, + // hgl::bf_directed_t, + // hgl::types::name_property, + // hgl::types::name_property>, // bf-directed hyperedge-major incidence list + // hgl::list_hypergraph_traits< + // hgl::impl::vertex_major_t, + // hgl::bf_directed_t, + // hgl::types::name_property, + // hgl::types::name_property>, // bf-directed vertex-major incidence list + // hgl::matrix_hypergraph_traits< + // hgl::impl::hyperedge_major_t, + // hgl::bf_directed_t, + // hgl::types::name_property, + // hgl::types::name_property>, // bf-directed hyperedge-major incidence matrix + // hgl::matrix_hypergraph_traits< + // hgl::impl::vertex_major_t, + // hgl::bf_directed_t, + // hgl::types::name_property, + // hgl::types::name_property> // bf-directed vertex-major incidence matrix ); TEST_SUITE_END(); // test_hypergraph diff --git a/tests/source/hgl/test_incidence_list.cpp b/tests/source/hgl/test_incidence_list.cpp index f5eaca31..9c72dda3 100644 --- a/tests/source/hgl/test_incidence_list.cpp +++ b/tests/source/hgl/test_incidence_list.cpp @@ -18,7 +18,7 @@ struct test_incidence_list { }; struct test_undirected_vertex_major_incidence_list : public test_incidence_list { - using sut_type = hgl::impl::undirected_incidence_list; + using sut_type = hgl::impl::incidence_list; }; TEST_CASE_FIXTURE( @@ -250,7 +250,7 @@ TEST_CASE_FIXTURE(test_undirected_vertex_major_incidence_list, "add_hyperedges s } struct test_undirected_hyperedge_major_incidence_list : public test_incidence_list { - using sut_type = hgl::impl::undirected_incidence_list; + using sut_type = hgl::impl::incidence_list; }; TEST_CASE_FIXTURE( diff --git a/tests/source/hgl/test_incidence_matrix.cpp b/tests/source/hgl/test_incidence_matrix.cpp index 2d357108..8adedc91 100644 --- a/tests/source/hgl/test_incidence_matrix.cpp +++ b/tests/source/hgl/test_incidence_matrix.cpp @@ -19,7 +19,7 @@ struct test_incidence_matrix { }; struct test_undirected_vertex_major_incidence_matrix : public test_incidence_matrix { - using sut_type = hgl::impl::undirected_incidence_matrix; + using sut_type = hgl::impl::incidence_matrix; }; TEST_CASE_FIXTURE( @@ -296,7 +296,7 @@ TEST_CASE_FIXTURE( } struct test_undirected_hyperedge_major_incidence_matrix : public test_incidence_matrix { - using sut_type = hgl::impl::undirected_incidence_matrix; + using sut_type = hgl::impl::incidence_matrix; }; TEST_CASE_FIXTURE(