diff --git a/include/gl/util/ranges.hpp b/include/gl/util/ranges.hpp index fa915e0..5286d71 100644 --- a/include/gl/util/ranges.hpp +++ b/include/gl/util/ranges.hpp @@ -4,6 +4,7 @@ #pragma once +#include #include namespace gl::util { @@ -20,4 +21,22 @@ constexpr auto range_size(R&& r) { return std::ranges::distance(std::begin(r), std::end(r)); } +template +[[nodiscard]] constexpr bool is_constant(R&& range) noexcept { + if (std::ranges::empty(range)) + return true; + + return std::ranges::all_of(range, [target = *std::ranges::begin(range)](const auto& val) { + return val == target; + }); +} + +template +[[nodiscard]] constexpr bool all_equal(R&& range, const std::ranges::range_value_t& k) noexcept { + if (std::ranges::empty(range)) + return true; + + return std::ranges::all_of(range, [&k](const auto& val) { return val == k; }); +} + } // namespace gl::util diff --git a/include/hgl/hypergraph.hpp b/include/hgl/hypergraph.hpp index e08c6d8..11afe9a 100644 --- a/include/hgl/hypergraph.hpp +++ b/include/hgl/hypergraph.hpp @@ -4,11 +4,13 @@ #pragma once +#include "gl/types/types.hpp" #include "hgl/constants.hpp" #include "hgl/directional_tags.hpp" #include "hgl/hypergraph_traits.hpp" #include "hgl/util.hpp" +#include #include #include #include @@ -452,6 +454,10 @@ class hypergraph final { return this->degree(vertex.id()); } + [[nodiscard]] std::vector degree_map() const { + return this->_impl.degree_map(this->_n_vertices); + } + [[nodiscard]] auto outgoing_hyperedges(const types::id_type vertex_id) requires std::same_as { @@ -479,6 +485,12 @@ class hypergraph final { return this->out_degree(vertex.id()); } + [[nodiscard]] std::vector out_degree_map() const + requires std::same_as + { + return this->_impl.out_degree_map(this->_n_vertices); + } + [[nodiscard]] auto incoming_hyperedges(const types::id_type vertex_id) requires std::same_as { @@ -506,6 +518,12 @@ class hypergraph final { return this->in_degree(vertex.id()); } + [[nodiscard]] std::vector in_degree_map() const + requires std::same_as + { + return this->_impl.in_degree_map(this->_n_vertices); + } + [[nodiscard]] auto incident_vertices(const types::id_type hyperedge_id) { this->_verify_hyperedge_id(hyperedge_id); return this->_impl.incident_vertices(hyperedge_id) @@ -527,6 +545,10 @@ class hypergraph final { return this->hyperedge_size(hyperedge.id()); } + [[nodiscard]] std::vector hyperedge_size_map() const { + return this->_impl.hyperedge_size_map(this->_n_hyperedges); + } + [[nodiscard]] auto tail_vertices(const types::id_type hyperedge_id) requires std::same_as { @@ -555,6 +577,12 @@ class hypergraph final { return this->tail_size(hyperedge.id()); } + [[nodiscard]] std::vector tail_size_map() const + requires std::same_as + { + return this->_impl.tail_size_map(this->_n_hyperedges); + } + [[nodiscard]] auto head_vertices(const types::id_type hyperedge_id) requires std::same_as { @@ -583,6 +611,12 @@ class hypergraph final { return this->head_size(hyperedge.id()); } + [[nodiscard]] std::vector head_size_map() const + requires std::same_as + { + return this->_impl.head_size_map(this->_n_hyperedges); + } + private: // --- vertex methods --- @@ -659,4 +693,171 @@ class hypergraph final { [[no_unique_address]] hyperedge_properties_map_type _hyperedge_properties{}; }; +// --- general hypergraph utility --- + +namespace type_traits { + +template +concept c_hypergraph = c_instantiation_of; + +template +concept c_undirected_hypergraph = + c_hypergraph and std::same_as; + +template +concept c_bf_directed_hypergraph = + c_hypergraph and std::same_as; + +} // namespace type_traits + +// --- degree bounds --- + +[[nodiscard]] types::size_type max_degree(const type_traits::c_hypergraph auto& hypergraph +) noexcept { + const auto degrees = hypergraph.degree_map(); + return degrees.empty() ? 0uz : *std::ranges::max_element(degrees); +} + +[[nodiscard]] types::size_type min_degree(const type_traits::c_hypergraph auto& hypergraph +) noexcept { + const auto degrees = hypergraph.degree_map(); + return degrees.empty() ? 0uz : *std::ranges::min_element(degrees); +} + +[[nodiscard]] types::size_type max_out_degree( + const type_traits::c_bf_directed_hypergraph auto& hypergraph +) noexcept { + const auto degrees = hypergraph.out_degree_map(); + return degrees.empty() ? 0uz : *std::ranges::max_element(degrees); +} + +[[nodiscard]] types::size_type min_out_degree( + const type_traits::c_bf_directed_hypergraph auto& hypergraph +) noexcept { + const auto degrees = hypergraph.out_degree_map(); + return degrees.empty() ? 0uz : *std::ranges::min_element(degrees); +} + +[[nodiscard]] types::size_type max_in_degree( + const type_traits::c_bf_directed_hypergraph auto& hypergraph +) noexcept { + const auto degrees = hypergraph.in_degree_map(); + return degrees.empty() ? 0uz : *std::ranges::max_element(degrees); +} + +[[nodiscard]] types::size_type min_in_degree( + const type_traits::c_bf_directed_hypergraph auto& hypergraph +) noexcept { + const auto degrees = hypergraph.in_degree_map(); + return degrees.empty() ? 0uz : *std::ranges::min_element(degrees); +} + +// --- hyperedge size bounds --- + +[[nodiscard]] types::size_type rank(const type_traits::c_hypergraph auto& hypergraph) noexcept { + const auto sizes = hypergraph.hyperedge_size_map(); + return sizes.empty() ? 0uz : *std::ranges::max_element(sizes); +} + +[[nodiscard]] types::size_type corank(const type_traits::c_hypergraph auto& hypergraph) noexcept { + const auto sizes = hypergraph.hyperedge_size_map(); + return sizes.empty() ? 0uz : *std::ranges::min_element(sizes); +} + +[[nodiscard]] types::size_type max_tail_size( + const type_traits::c_bf_directed_hypergraph auto& hypergraph +) noexcept { + const auto sizes = hypergraph.tail_size_map(); + return sizes.empty() ? 0uz : *std::ranges::max_element(sizes); +} + +[[nodiscard]] types::size_type min_tail_size( + const type_traits::c_bf_directed_hypergraph auto& hypergraph +) noexcept { + const auto sizes = hypergraph.tail_size_map(); + return sizes.empty() ? 0uz : *std::ranges::min_element(sizes); +} + +[[nodiscard]] types::size_type max_head_size( + const type_traits::c_bf_directed_hypergraph auto& hypergraph +) noexcept { + const auto sizes = hypergraph.head_size_map(); + return sizes.empty() ? 0uz : *std::ranges::max_element(sizes); +} + +[[nodiscard]] types::size_type min_head_size( + const type_traits::c_bf_directed_hypergraph auto& hypergraph +) noexcept { + const auto sizes = hypergraph.head_size_map(); + return sizes.empty() ? 0uz : *std::ranges::min_element(sizes); +} + +// --- regularity --- + +[[nodiscard]] bool is_regular( + const type_traits::c_hypergraph auto& hypergraph, const types::size_type k +) noexcept { + return util::all_equal(hypergraph.degree_map(), k); +} + +[[nodiscard]] bool is_regular(const type_traits::c_hypergraph auto& hypergraph) noexcept { + return util::is_constant(hypergraph.degree_map()); +} + +[[nodiscard]] bool is_out_regular( + const type_traits::c_bf_directed_hypergraph auto& hypergraph, const types::size_type k +) noexcept { + return util::all_equal(hypergraph.out_degree_map(), k); +} + +[[nodiscard]] bool is_out_regular(const type_traits::c_bf_directed_hypergraph auto& hypergraph +) noexcept { + return util::is_constant(hypergraph.out_degree_map()); +} + +[[nodiscard]] bool is_in_regular( + const type_traits::c_bf_directed_hypergraph auto& hypergraph, const types::size_type k +) noexcept { + return util::all_equal(hypergraph.in_degree_map(), k); +} + +[[nodiscard]] bool is_in_regular(const type_traits::c_bf_directed_hypergraph auto& hypergraph +) noexcept { + return util::is_constant(hypergraph.in_degree_map()); +} + +// --- uniformity --- + +[[nodiscard]] bool is_uniform( + const type_traits::c_hypergraph auto& hypergraph, const types::size_type k +) noexcept { + return util::all_equal(hypergraph.hyperedge_size_map(), k); +} + +[[nodiscard]] bool is_uniform(const type_traits::c_hypergraph auto& hypergraph) noexcept { + return util::is_constant(hypergraph.hyperedge_size_map()); +} + +[[nodiscard]] bool is_tail_uniform( + const type_traits::c_bf_directed_hypergraph auto& hypergraph, const types::size_type k +) noexcept { + return util::all_equal(hypergraph.tail_size_map(), k); +} + +[[nodiscard]] bool is_tail_uniform(const type_traits::c_bf_directed_hypergraph auto& hypergraph +) noexcept { + return util::is_constant(hypergraph.tail_size_map()); +} + +[[nodiscard]] bool is_head_uniform( + const type_traits::c_bf_directed_hypergraph auto& hypergraph, const types::size_type k +) noexcept { + return util::all_equal(hypergraph.head_size_map(), k); +} + +[[nodiscard]] bool is_head_uniform(const type_traits::c_bf_directed_hypergraph auto& hypergraph +) noexcept { + return util::is_constant(hypergraph.head_size_map()); +} + } // namespace hgl diff --git a/include/hgl/impl/incidence_list.hpp b/include/hgl/impl/incidence_list.hpp index b9a5171..025b2fd 100644 --- a/include/hgl/impl/incidence_list.hpp +++ b/include/hgl/impl/incidence_list.hpp @@ -4,12 +4,15 @@ #pragma once +#include "gl/types/types.hpp" #include "hgl/directional_tags.hpp" #include "hgl/impl/layout_tags.hpp" #include "hgl/types/types.hpp" #include +#include #include +#include #include #include @@ -48,8 +51,7 @@ class incidence_list final { // --- vertex methods --- gl_attr_force_inline void add_vertices(const types::size_type n) noexcept { - if constexpr (std::same_as) - this->_major_storage.resize(this->_major_storage.size() + n); + this->_add(n); } gl_attr_force_inline void remove_vertex(const types::id_type vertex_id) noexcept { @@ -66,11 +68,16 @@ class incidence_list final { return this->_size(vertex_id); } + [[nodiscard]] gl_attr_force_inline std::vector degree_map( + const types::size_type n_vertices + ) const noexcept { + return this->_size_map(n_vertices); + } + // --- hyperedge methods --- gl_attr_force_inline void add_hyperedges(const types::size_type n) noexcept { - if constexpr (std::same_as) - this->_major_storage.resize(this->_major_storage.size() + n); + this->_add(n); } gl_attr_force_inline void remove_hyperedge(const types::id_type hyperedge_id) noexcept { @@ -88,6 +95,12 @@ class incidence_list final { return this->_size(hyperedge_id); } + [[nodiscard]] gl_attr_force_inline std::vector hyperedge_size_map( + const types::size_type n_hyperedges + ) const noexcept { + return this->_size_map(n_hyperedges); + } + // --- binding methods --- gl_attr_force_inline void bind( @@ -131,6 +144,12 @@ class incidence_list final { using major_element_type = minor_storage_type; using major_storage_type = std::vector; + template + void _add(const types::size_type n) noexcept { + if constexpr (Element == layout_tag::major_element) // add major + this->_major_storage.resize(this->_major_storage.size() + n); + } + template void _remove(const types::id_type id) noexcept { if constexpr (Element == layout_tag::major_element) { // remove major @@ -176,6 +195,24 @@ class incidence_list final { } } + template + [[nodiscard]] std::vector _size_map(const types::size_type n_elements + ) const noexcept { + if constexpr (Element == layout_tag::major_element) { // size major + auto size_map = this->_major_storage | std::views::transform(&major_element_type::size) + | std::ranges::to>(); + size_map.resize(n_elements, 0ull); + return size_map; + } + else { // size minor + std::vector size_map(n_elements, 0ull); + for (const auto& major_entry : this->_major_storage) + for (const auto& minor_id : major_entry) + ++size_map[static_cast(minor_id)]; + return size_map; + } + } + [[nodiscard]] bool _contains( const minor_storage_type& minor_storage, const types::id_type minor_id ) const noexcept { @@ -226,22 +263,37 @@ class incidence_list final { return this->_size(vertex_id); } + [[nodiscard]] std::vector degree_map(const types::size_type n_vertices + ) const noexcept { + return this->_size_map(n_vertices); + } + [[nodiscard]] gl_attr_force_inline auto outgoing_hyperedges(const types::id_type vertex_id ) const noexcept { - return this->_get_tail(vertex_id); + return this->_get(vertex_id, &major_element_type::tail); } [[nodiscard]] types::size_type out_degree(const types::id_type vertex_id) const noexcept { - return this->_tail_size(vertex_id); + return this->_size(vertex_id, &major_element_type::tail); + } + + [[nodiscard]] std::vector out_degree_map(const types::size_type n_vertices + ) const noexcept { + return this->_size_map(n_vertices, &major_element_type::tail); } [[nodiscard]] gl_attr_force_inline auto incoming_hyperedges(const types::id_type vertex_id ) const noexcept { - return this->_get_head(vertex_id); + return this->_get(vertex_id, &major_element_type::head); } [[nodiscard]] types::size_type in_degree(const types::id_type vertex_id) const noexcept { - return this->_head_size(vertex_id); + return this->_size(vertex_id, &major_element_type::head); + } + + [[nodiscard]] std::vector in_degree_map(const types::size_type n_vertices + ) const noexcept { + return this->_size_map(n_vertices, &major_element_type::head); } // --- hyperedge methods : general --- @@ -266,22 +318,42 @@ class incidence_list final { return this->_size(hyperedge_id); } + [[nodiscard]] std::vector hyperedge_size_map( + const types::size_type n_hyperedges + ) const noexcept { + return this->_size_map(n_hyperedges); + } + [[nodiscard]] gl_attr_force_inline auto tail_vertices(const types::id_type hyperedge_id ) const noexcept { - return this->_get_tail(hyperedge_id); + return this->_get(hyperedge_id, &major_element_type::tail); } [[nodiscard]] types::size_type tail_size(const types::id_type hyperedge_id) const noexcept { - return this->_tail_size(hyperedge_id); + return this->_size(hyperedge_id, &major_element_type::tail); + } + + [[nodiscard]] std::vector tail_size_map(const types::size_type n_hyperedges + ) const noexcept { + return this->_size_map( + n_hyperedges, &major_element_type::tail + ); } [[nodiscard]] gl_attr_force_inline auto head_vertices(const types::id_type hyperedge_id ) const noexcept { - return this->_get_head(hyperedge_id); + return this->_get(hyperedge_id, &major_element_type::head); } [[nodiscard]] types::size_type head_size(const types::id_type hyperedge_id) const noexcept { - return this->_head_size(hyperedge_id); + return this->_size(hyperedge_id, &major_element_type::head); + } + + [[nodiscard]] std::vector head_size_map(const types::size_type n_hyperedges + ) const noexcept { + return this->_size_map( + n_hyperedges, &major_element_type::head + ); } // --- binding methods --- @@ -350,10 +422,26 @@ class incidence_list final { struct major_element_type { minor_storage_type tail; minor_storage_type head; + + [[nodiscard]] types::size_type size() const noexcept { + return tail.size() + head.size(); + } }; using major_storage_type = std::vector; + static constexpr auto _view_of = [](const auto& ctr) { // A view over all container elements + if constexpr (std::same_as, major_element_type>) { + // TODO: use std::views::concat (C++26) + // NOTE: This is safe because the range operator | creates an owning view over the array + return std::array, 2>{ctr.tail, ctr.head} + | std::views::join; + } + else { + return std::views::all(ctr); + } + }; + template void _add(const types::size_type n) noexcept { if constexpr (Element == layout_tag::major_element) // add major @@ -388,92 +476,55 @@ class incidence_list final { return minor_it; } - template - [[nodiscard]] gl_attr_force_inline auto _get(const types::id_type id) const noexcept { - if constexpr (Element == layout_tag::major_element) { // get major - const auto& entry = this->_major_storage[id]; - // TODO: use std::views::concat (C++26) - // NOTE: This is safe because the range operator | creates an owning view over the array - return std::array, 2>{entry.tail, entry.head} - | std::views::join; - } - else { // get minor - return std::views::iota(0uz, this->_major_storage.size()) - | std::views::filter([this, minor_id = id](types::id_type major_id) { - return this->_contains(this->_major_storage[major_id], minor_id); - }); - } - } - - template - [[nodiscard]] gl_attr_force_inline auto _get_tail(const types::id_type id) const noexcept { - if constexpr (Element == layout_tag::major_element) { // get major - return std::views::all(this->_major_storage[id].tail); - } - else { // get minor - return std::views::iota(0uz, this->_major_storage.size()) - | std::views::filter([this, minor_id = id](types::id_type major_id) { - return this->_contains(this->_major_storage[major_id].tail, minor_id); - }); - } - } - - template - [[nodiscard]] gl_attr_force_inline auto _get_head(const types::id_type id) const noexcept { - if constexpr (Element == layout_tag::major_element) { // get major - return std::views::all(this->_major_storage[id].head); - } - else { // get minor + template + [[nodiscard]] gl_attr_force_inline auto _get( + const types::id_type id, const Projection subset_proj = {} + ) const noexcept { + if constexpr (Element == layout_tag::major_element) // get major + return _view_of(std::invoke(subset_proj, this->_major_storage[id])); + else // get minor return std::views::iota(0uz, this->_major_storage.size()) - | std::views::filter([this, minor_id = id](types::id_type major_id) { - return this->_contains(this->_major_storage[major_id].head, minor_id); + | std::views::filter([this, subset_proj, minor_id = id](types::id_type major_id) { + return this->_contains( + std::invoke(subset_proj, this->_major_storage[major_id]), minor_id + ); }); - } - } - - template - [[nodiscard]] gl_attr_force_inline types::size_type _size(const types::id_type id - ) const noexcept { - if constexpr (Element == layout_tag::major_element) { // size major - const auto& entry = this->_major_storage[id]; - return entry.tail.size() + entry.head.size(); - } - else { // size minor - types::size_type size = 0uz; - for (const auto& major_el : this->_major_storage) - if (this->_contains(major_el, id)) - ++size; - return size; - } } - template - [[nodiscard]] gl_attr_force_inline types::size_type _tail_size(const types::id_type id + template + [[nodiscard]] gl_attr_force_inline types::size_type _size( + const types::id_type id, const Projection subset_proj = {} ) const noexcept { if constexpr (Element == layout_tag::major_element) { // size major - return this->_major_storage[id].tail.size(); + return std::invoke(subset_proj, this->_major_storage[id]).size(); } else { // size minor types::size_type size = 0uz; for (const auto& major_el : this->_major_storage) - if (this->_contains(major_el.tail, id)) + if (this->_contains(std::invoke(subset_proj, major_el), id)) ++size; return size; } } - template - [[nodiscard]] gl_attr_force_inline types::size_type _head_size(const types::id_type id + template + [[nodiscard]] std::vector _size_map( + const types::size_type n_elements, const Projection subset_proj = {} ) const noexcept { if constexpr (Element == layout_tag::major_element) { // size major - return this->_major_storage[id].head.size(); + auto size_map = + this->_major_storage | std::views::transform(subset_proj) + | std::views::transform([](const auto& ctr) { return ctr.size(); }) + | std::ranges::to>(); + size_map.resize(n_elements, 0ull); + return size_map; } else { // size minor - types::size_type size = 0uz; - for (const auto& major_el : this->_major_storage) - if (this->_contains(major_el.head, id)) - ++size; - return size; + std::vector size_map(n_elements, 0ull); + for (const auto& major_entry : this->_major_storage) + for (const auto& minor_id : _view_of(std::invoke(subset_proj, major_entry))) + ++size_map[static_cast(minor_id)]; + return size_map; } } diff --git a/include/hgl/impl/incidence_matrix.hpp b/include/hgl/impl/incidence_matrix.hpp index 295f007..0c87f0a 100644 --- a/include/hgl/impl/incidence_matrix.hpp +++ b/include/hgl/impl/incidence_matrix.hpp @@ -68,6 +68,11 @@ class incidence_matrix final { return this->_count(vertex_id); } + [[nodiscard]] std::vector degree_map(const types::size_type n_vertices + ) const noexcept { + return this->_count_map(n_vertices); + } + // --- hyperedge methods --- gl_attr_force_inline void add_hyperedges(const types::size_type n) noexcept { @@ -88,6 +93,12 @@ class incidence_matrix final { return this->_count(hyperedge_id); } + [[nodiscard]] std::vector hyperedge_size_map( + const types::size_type n_hyperedges + ) const noexcept { + return this->_count_map(n_hyperedges); + } + // --- binding methods --- gl_attr_force_inline void bind( @@ -179,6 +190,32 @@ class incidence_matrix final { return count; } + template + [[nodiscard]] std::vector _count_map(const types::size_type n_elements + ) const noexcept { + std::vector size_map(n_elements, 0uz); + + if constexpr (Element == layout_tag::major_element) { // count map major + const types::size_type limit = + std::min(n_elements, static_cast(this->_matrix.size())); + for (types::size_type i = 0uz; i < limit; ++i) { + size_map[i] = + static_cast(std::ranges::count(this->_matrix[i], true)); + } + } + else { // count map minor + const types::size_type limit = std::min(n_elements, this->_matrix_row_size); + for (const auto& row : this->_matrix) { + for (types::size_type j = 0uz; j < limit; ++j) { + if (row[j]) { + ++size_map[j]; + } + } + } + } + return size_map; + } + types::size_type _matrix_row_size = 0uz; hypergraph_storage_type _matrix; }; @@ -227,6 +264,11 @@ class incidence_matrix final { return this->_count(vertex_id, _is_incident); } + [[nodiscard]] std::vector degree_map(const types::size_type n_vertices + ) const noexcept { + return this->_count_map(n_vertices, _is_incident); + } + [[nodiscard]] gl_attr_force_inline auto outgoing_hyperedges(const types::id_type vertex_id ) const noexcept { return this->_query(vertex_id, _is_tail); @@ -236,6 +278,11 @@ class incidence_matrix final { return this->_count(vertex_id, _is_tail); } + [[nodiscard]] std::vector out_degree_map(const types::size_type n_vertices + ) const noexcept { + return this->_count_map(n_vertices, _is_tail); + } + [[nodiscard]] gl_attr_force_inline auto incoming_hyperedges(const types::id_type vertex_id ) const noexcept { return this->_query(vertex_id, _is_head); @@ -245,6 +292,11 @@ class incidence_matrix final { return this->_count(vertex_id, _is_head); } + [[nodiscard]] std::vector in_degree_map(const types::size_type n_vertices + ) const noexcept { + return this->_count_map(n_vertices, _is_head); + } + // --- hyperedge methods : general --- gl_attr_force_inline void add_hyperedges(const types::size_type n) noexcept { @@ -267,6 +319,12 @@ class incidence_matrix final { return this->_count(hyperedge_id, _is_incident); } + [[nodiscard]] std::vector hyperedge_size_map( + const types::size_type n_hyperedges + ) const noexcept { + return this->_count_map(n_hyperedges, _is_incident); + } + [[nodiscard]] gl_attr_force_inline auto tail_vertices(const types::id_type hyperedge_id ) const noexcept { return this->_query(hyperedge_id, _is_tail); @@ -276,6 +334,11 @@ class incidence_matrix final { return this->_count(hyperedge_id, _is_tail); } + [[nodiscard]] std::vector tail_size_map(const types::size_type n_hyperedges + ) const noexcept { + return this->_count_map(n_hyperedges, _is_tail); + } + [[nodiscard]] gl_attr_force_inline auto head_vertices(const types::id_type hyperedge_id ) const noexcept { return this->_query(hyperedge_id, _is_head); @@ -285,6 +348,11 @@ class incidence_matrix final { return this->_count(hyperedge_id, _is_head); } + [[nodiscard]] std::vector head_size_map(const types::size_type n_hyperedges + ) const noexcept { + return this->_count_map(n_hyperedges, _is_head); + } + // --- binding methods --- gl_attr_force_inline void bind_tail( @@ -421,6 +489,37 @@ class incidence_matrix final { return count; } + template + [[nodiscard]] std::vector _count_map( + const types::size_type n_elements, std::predicate auto&& pred + ) const noexcept { + std::vector size_map(n_elements, 0uz); + + if constexpr (Element == layout_tag::major_element) { // count map major + const types::size_type limit = + std::min(n_elements, static_cast(this->_matrix.size())); + for (types::size_type i = 0uz; i < limit; ++i) { + for (const auto val : this->_matrix[i]) { + if (pred(val)) { + ++size_map[i]; + } + } + } + } + else { // count map minor + for (const auto& row : this->_matrix) { + const types::size_type limit = std::min(n_elements, this->_matrix_row_size); + for (types::size_type j = 0uz; j < limit; ++j) { + if (pred(row[j])) { + ++size_map[j]; + } + } + } + } + + return size_map; + } + types::size_type _matrix_row_size = 0uz; hypergraph_storage_type _matrix; }; diff --git a/include/hgl/util.hpp b/include/hgl/util.hpp index 4bf2bff..d9b3b21 100644 --- a/include/hgl/util.hpp +++ b/include/hgl/util.hpp @@ -8,7 +8,9 @@ namespace hgl::util { +using gl::util::all_equal; using gl::util::deref_view; +using gl::util::is_constant; using gl::util::range_size; } // namespace hgl::util diff --git a/tests/source/hgl/test_hypergraph.cpp b/tests/source/hgl/test_hypergraph.cpp index a51f1b3..5a85610 100644 --- a/tests/source/hgl/test_hypergraph.cpp +++ b/tests/source/hgl/test_hypergraph.cpp @@ -775,6 +775,62 @@ TEST_CASE_TEMPLATE_DEFINE( } } } + + SUBCASE("element size map getters should return maps of properly calculated element sizes") { + constexpr auto n_elements = 5ull; + sut_type sut{n_elements, n_elements}; + + constexpr auto is_zero = [](const auto& size) { return size == 0ull; }; + REQUIRE(std::ranges::all_of(sut.degree_map(), is_zero)); + REQUIRE(std::ranges::all_of(sut.hyperedge_size_map(), is_zero)); + + if constexpr (std::same_as) { + for (std::size_t i = 0uz; i < n_elements; i++) + for (std::size_t j = 0uz; j <= i; j++) + sut.bind(i, j); + + const auto deg_map = sut.degree_map(); + const auto esize_map = sut.hyperedge_size_map(); + + for (std::size_t i = 0uz; i < n_elements; i++) { + CHECK_EQ(deg_map[i], i + 1uz); + CHECK_EQ(esize_map[i], n_elements - i); + } + } + + if constexpr (std::same_as) { + REQUIRE(std::ranges::all_of(sut.out_degree_map(), is_zero)); + REQUIRE(std::ranges::all_of(sut.in_degree_map(), is_zero)); + REQUIRE(std::ranges::all_of(sut.tail_size_map(), is_zero)); + REQUIRE(std::ranges::all_of(sut.head_size_map(), is_zero)); + + for (std::size_t i = 0uz; i < n_elements; i++) { + for (std::size_t j = 0uz; j <= i; j++) { + if (i == j) + sut.bind_tail(i, j); + else + sut.bind_head(i, j); + } + } + + const auto deg_map = sut.degree_map(); + const auto out_deg_map = sut.out_degree_map(); + const auto in_deg_map = sut.in_degree_map(); + const auto esize_map = sut.hyperedge_size_map(); + const auto tsize_map = sut.tail_size_map(); + const auto hsize_map = sut.head_size_map(); + + for (std::size_t k = 0uz; k < n_elements; k++) { + CHECK_EQ(deg_map[k], k + 1uz); + CHECK_EQ(out_deg_map[k], 1uz); + CHECK_EQ(in_deg_map[k], k); + + CHECK_EQ(esize_map[k], n_elements - k); + CHECK_EQ(tsize_map[k], 1uz); + CHECK_EQ(hsize_map[k], n_elements - k - 1uz); + } + } + } } TEST_CASE_TEMPLATE_INSTANTIATE( @@ -1165,4 +1221,204 @@ TEST_CASE_TEMPLATE_INSTANTIATE( TEST_SUITE_END(); // test_hypergraph +TEST_CASE_TEMPLATE_DEFINE( + "hypergraph size utility tests", HypergraphTraits, hypergraph_traits_util_template +) { + using sut_type = hgl::hypergraph; + using directional_tag = typename sut_type::directional_tag; + + SUBCASE("utilities on empty hypergraph should return zero or true") { + sut_type sut; + + // --- Degree Bounds --- + CHECK_EQ(hgl::min_degree(sut), 0uz); + CHECK_EQ(hgl::max_degree(sut), 0uz); + + // --- Size Bounds --- + CHECK_EQ(hgl::rank(sut), 0uz); + CHECK_EQ(hgl::corank(sut), 0uz); + + // --- Regularity/Uniformity --- + CHECK(hgl::is_regular(sut)); + CHECK(hgl::is_regular(sut, 0uz)); + CHECK(hgl::is_uniform(sut)); + CHECK(hgl::is_uniform(sut, 0uz)); + + if constexpr (std::same_as) { + // --- Degree Bounds --- + CHECK_EQ(hgl::min_out_degree(sut), 0uz); + CHECK_EQ(hgl::max_out_degree(sut), 0uz); + CHECK_EQ(hgl::min_in_degree(sut), 0uz); + CHECK_EQ(hgl::max_in_degree(sut), 0uz); + + // --- Size Bounds --- + CHECK_EQ(hgl::min_tail_size(sut), 0uz); + CHECK_EQ(hgl::max_tail_size(sut), 0uz); + CHECK_EQ(hgl::min_head_size(sut), 0uz); + CHECK_EQ(hgl::max_head_size(sut), 0uz); + + // --- Regularity/Uniformity --- + CHECK(hgl::is_out_regular(sut)); + CHECK(hgl::is_in_regular(sut)); + CHECK(hgl::is_tail_uniform(sut)); + CHECK(hgl::is_head_uniform(sut)); + } + } + + SUBCASE("utilities on a symmetric topology (Cycle C3) should report constant properties") { + // Setup: 3 Vertices, 3 Hyperedges forming a cycle. + // Undirected: Edges are {0,1}, {1,2}, {2,0}. + // Directed: Edges are 0->1, 1->2, 2->0. + constexpr auto n_elements = 3uz; + sut_type sut{n_elements, n_elements}; + + if constexpr (std::same_as) { + sut.bind(0uz, 0uz); + sut.bind(1uz, 0uz); // e0: {0,1} + sut.bind(1uz, 1uz); + sut.bind(2uz, 1uz); // e1: {1,2} + sut.bind(2uz, 2uz); + sut.bind(0uz, 2uz); // e2: {2,0} + + // Expected: 2-regular, 2-uniform + CHECK_EQ(hgl::max_degree(sut), 2uz); + CHECK_EQ(hgl::min_degree(sut), 2uz); + CHECK(hgl::is_regular(sut)); + CHECK(hgl::is_regular(sut, 2uz)); + CHECK_FALSE(hgl::is_regular(sut, 1uz)); + + CHECK_EQ(hgl::rank(sut), 2uz); + CHECK_EQ(hgl::corank(sut), 2uz); + CHECK(hgl::is_uniform(sut)); + CHECK(hgl::is_uniform(sut, 2uz)); + CHECK_FALSE(hgl::is_uniform(sut, 1uz)); + } + + if constexpr (std::same_as) { + sut.bind_tail(0, 0); + sut.bind_head(1, 0); // e0: 0 -> 1 + sut.bind_tail(1, 1); + sut.bind_head(2, 1); // e1: 1 -> 2 + sut.bind_tail(2, 2); + sut.bind_head(0, 2); // e2: 2 -> 0 + + // Expected General: Degree 2 (1 in + 1 out), Size 2 (1 tail + 1 head) + CHECK_EQ(hgl::min_degree(sut), 2uz); + CHECK_EQ(hgl::max_degree(sut), 2uz); + CHECK(hgl::is_regular(sut, 2uz)); + CHECK_FALSE(hgl::is_regular(sut, 1uz)); + CHECK(hgl::is_uniform(sut, 2uz)); + CHECK_FALSE(hgl::is_uniform(sut, 1uz)); + + // Expected Directed: 1-out-regular, 1-in-regular + CHECK_EQ(hgl::min_out_degree(sut), 1uz); + CHECK_EQ(hgl::max_out_degree(sut), 1uz); + CHECK(hgl::is_out_regular(sut, 1uz)); + CHECK_FALSE(hgl::is_out_regular(sut, 2uz)); + + CHECK_EQ(hgl::min_in_degree(sut), 1uz); + CHECK_EQ(hgl::max_in_degree(sut), 1uz); + CHECK(hgl::is_in_regular(sut, 1uz)); + CHECK_FALSE(hgl::is_in_regular(sut, 2uz)); + + // Expected Directed Sizes: 1-tail, 1-head + CHECK_EQ(hgl::min_tail_size(sut), 1uz); + CHECK_EQ(hgl::max_tail_size(sut), 1uz); + CHECK(hgl::is_tail_uniform(sut, 1uz)); + CHECK_FALSE(hgl::is_tail_uniform(sut, 2uz)); + + CHECK_EQ(hgl::min_head_size(sut), 1uz); + CHECK_EQ(hgl::max_head_size(sut), 1uz); + CHECK(hgl::is_head_uniform(sut, 1uz)); + CHECK_FALSE(hgl::is_head_uniform(sut, 2uz)); + } + } + + SUBCASE("utilities on asymmetric topology should report divergent bounds and false checks") { + // Setup: 3 Vertices, 2 Hyperedges. + // Undirected: e0={0,1,2} (size 3), e1={0} (size 1) + // Directed: e0: 0 -> {1,2} (1 tail, 2 heads), e1: {0,1} -> 2 (2 tails, 1 head) + sut_type sut{3, 2}; + + if constexpr (std::same_as) { + sut.bind(0, 0); + sut.bind(1, 0); + sut.bind(2, 0); // e0 size 3 + sut.bind(0, 1); // e1 size 1 + + // Sizes: 3, 1 -> Non-uniform + CHECK_EQ(hgl::rank(sut), 3uz); + CHECK_EQ(hgl::corank(sut), 1uz); + CHECK_FALSE(hgl::is_uniform(sut)); + + // Degrees: v0=2, v1=1, v2=1 -> Irregular + CHECK_EQ(hgl::max_degree(sut), 2uz); + CHECK_EQ(hgl::min_degree(sut), 1uz); + CHECK_FALSE(hgl::is_regular(sut)); + } + + if constexpr (std::same_as) { + // e0: 0 -> {1, 2} + sut.bind_tail(0, 0); + sut.bind_head(1, 0); + sut.bind_head(2, 0); + + // e1: {0, 1} -> 2 + sut.bind_tail(0, 1); + sut.bind_tail(1, 1); + sut.bind_head(2, 1); + + // --- Size Checks --- + // Tail sizes: e0=1, e1=2 + CHECK_EQ(hgl::max_tail_size(sut), 2uz); + CHECK_EQ(hgl::min_tail_size(sut), 1uz); + CHECK_FALSE(hgl::is_tail_uniform(sut)); + + // Head sizes: e0=2, e1=1 + CHECK_EQ(hgl::max_head_size(sut), 2uz); + CHECK_EQ(hgl::min_head_size(sut), 1uz); + CHECK_FALSE(hgl::is_head_uniform(sut)); + + // --- Degree Checks --- + // Out degrees: v0(2), v1(1), v2(0) + CHECK_EQ(hgl::max_out_degree(sut), 2uz); + CHECK_EQ(hgl::min_out_degree(sut), 0uz); + CHECK_FALSE(hgl::is_out_regular(sut)); + + // In degrees: v0(0), v1(1), v2(2) + CHECK_EQ(hgl::max_in_degree(sut), 2uz); + CHECK_EQ(hgl::min_in_degree(sut), 0uz); + CHECK_FALSE(hgl::is_in_regular(sut)); + } + } +} + +TEST_CASE_TEMPLATE_INSTANTIATE( + hypergraph_traits_util_template, + hgl::list_hypergraph_traits< + hgl::impl::hyperedge_major_t, + hgl::undirected_t>, // undirected hyperedge-major incidence list + hgl::list_hypergraph_traits< + hgl::impl::vertex_major_t, + hgl::undirected_t>, // undirected 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::vertex_major_t, + hgl::undirected_t>, // undirected vertex-major incidence matrix + 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 +); + } // namespace hgl_testing diff --git a/tests/source/hgl/test_incidence_list.cpp b/tests/source/hgl/test_incidence_list.cpp index 8598803..16ab389 100644 --- a/tests/source/hgl/test_incidence_list.cpp +++ b/tests/source/hgl/test_incidence_list.cpp @@ -249,6 +249,29 @@ TEST_CASE_FIXTURE(test_undirected_vertex_major_incidence_list, "add_hyperedges s CHECK_EQ(storage(sut).size(), constants::n_vertices); } +TEST_CASE_FIXTURE( + test_undirected_vertex_major_incidence_list, + "element size map getters should return maps of properly calculated element sizes" +) { + constexpr auto n_elements = 5ull; + sut_type sut{n_elements, n_elements}; + + constexpr auto is_zero = [](const auto& size) { return size == 0ull; }; + REQUIRE(std::ranges::all_of(sut.degree_map(n_elements), is_zero)); + REQUIRE(std::ranges::all_of(sut.hyperedge_size_map(n_elements), is_zero)); + + for (std::size_t i = 0uz; i < n_elements; i++) + for (std::size_t j = 0uz; j <= i; j++) + sut.bind(i, j); + + const auto deg_map = sut.degree_map(n_elements); + const auto esize_map = sut.hyperedge_size_map(n_elements); + for (std::size_t i = 0uz; i < n_elements; i++) { + CHECK_EQ(deg_map[i], i + 1uz); + CHECK_EQ(esize_map[i], n_elements - i); + } +} + struct test_undirected_hyperedge_major_incidence_list : public test_incidence_list { using sut_type = hgl::impl::incidence_list; }; @@ -473,6 +496,29 @@ TEST_CASE_FIXTURE( CHECK_EQ(sut.degree(vertex_id), constants::n_hyperedges); } +TEST_CASE_FIXTURE( + test_undirected_hyperedge_major_incidence_list, + "element size map getters should return maps of properly calculated element sizes" +) { + constexpr auto n_elements = 5ull; + sut_type sut{n_elements, n_elements}; + + constexpr auto is_zero = [](const auto& size) { return size == 0ull; }; + REQUIRE(std::ranges::all_of(sut.degree_map(n_elements), is_zero)); + REQUIRE(std::ranges::all_of(sut.hyperedge_size_map(n_elements), is_zero)); + + for (std::size_t i = 0uz; i < n_elements; i++) + for (std::size_t j = 0uz; j <= i; j++) + sut.bind(i, j); + + const auto deg_map = sut.degree_map(n_elements); + const auto esize_map = sut.hyperedge_size_map(n_elements); + for (std::size_t i = 0uz; i < n_elements; i++) { + CHECK_EQ(deg_map[i], i + 1uz); + CHECK_EQ(esize_map[i], n_elements - i); + } +} + struct test_bf_directed_incidence_list : public test_incidence_list { auto altbind_to_vertex( auto& sut, const hgl::types::id_type vertex_id, const hgl::types::size_type n_hyperedges @@ -865,6 +911,88 @@ TEST_CASE_FIXTURE( CHECK_FALSE(sut.is_head(constants::id3, constants::id1)); } +TEST_CASE_FIXTURE( + test_bf_directed_vertex_major_incidence_list, + "element size map getters should return maps of properly calculated element sizes" +) { + constexpr auto n_elements = 5ull; + sut_type sut{n_elements, n_elements}; + + constexpr auto is_zero = [](const auto& size) { return size == 0ull; }; + REQUIRE(std::ranges::all_of(sut.degree_map(n_elements), is_zero)); + REQUIRE(std::ranges::all_of(sut.out_degree_map(n_elements), is_zero)); + REQUIRE(std::ranges::all_of(sut.in_degree_map(n_elements), is_zero)); + REQUIRE(std::ranges::all_of(sut.hyperedge_size_map(n_elements), is_zero)); + REQUIRE(std::ranges::all_of(sut.tail_size_map(n_elements), is_zero)); + REQUIRE(std::ranges::all_of(sut.head_size_map(n_elements), is_zero)); + + SUBCASE("tail bind") { + for (std::size_t i = 0uz; i < n_elements; i++) + for (std::size_t j = 0uz; j <= i; j++) + sut.bind_tail(i, j); + + const auto deg_map = sut.degree_map(n_elements); + const auto out_deg_map = sut.out_degree_map(n_elements); + const auto esize_map = sut.hyperedge_size_map(n_elements); + const auto tsize_map = sut.tail_size_map(n_elements); + for (std::size_t i = 0uz; i < n_elements; i++) { + CHECK_EQ(deg_map[i], i + 1uz); + CHECK_EQ(out_deg_map[i], i + 1uz); + CHECK_EQ(esize_map[i], n_elements - i); + CHECK_EQ(tsize_map[i], n_elements - i); + } + CHECK(std::ranges::all_of(sut.in_degree_map(n_elements), is_zero)); + CHECK(std::ranges::all_of(sut.head_size_map(n_elements), is_zero)); + } + + SUBCASE("head bind") { + for (std::size_t i = 0uz; i < n_elements; i++) + for (std::size_t j = 0uz; j <= i; j++) + sut.bind_head(i, j); + + const auto deg_map = sut.degree_map(n_elements); + const auto in_deg_map = sut.in_degree_map(n_elements); + const auto esize_map = sut.hyperedge_size_map(n_elements); + const auto hsize_map = sut.head_size_map(n_elements); + for (std::size_t i = 0uz; i < n_elements; i++) { + CHECK_EQ(deg_map[i], i + 1uz); + CHECK_EQ(in_deg_map[i], i + 1uz); + CHECK_EQ(esize_map[i], n_elements - i); + CHECK_EQ(hsize_map[i], n_elements - i); + } + CHECK(std::ranges::all_of(sut.out_degree_map(n_elements), is_zero)); + CHECK(std::ranges::all_of(sut.tail_size_map(n_elements), is_zero)); + } + + // diagonal = tail, everything else is head + for (std::size_t i = 0uz; i < n_elements; i++) { + for (std::size_t j = 0uz; j <= i; j++) { + if (i == j) + sut.bind_tail(i, j); + else + sut.bind_head(i, j); + } + } + + const auto deg_map = sut.degree_map(n_elements); + const auto out_deg_map = sut.out_degree_map(n_elements); + const auto in_deg_map = sut.in_degree_map(n_elements); + + const auto esize_map = sut.hyperedge_size_map(n_elements); + const auto tsize_map = sut.tail_size_map(n_elements); + const auto hsize_map = sut.head_size_map(n_elements); + + for (std::size_t i = 0uz; i < n_elements; i++) { + CHECK_EQ(deg_map[i], i + 1uz); + CHECK_EQ(out_deg_map[i], 1uz); + CHECK_EQ(in_deg_map[i], i); + + CHECK_EQ(esize_map[i], n_elements - i); + CHECK_EQ(tsize_map[i], 1uz); + CHECK_EQ(hsize_map[i], n_elements - i - 1uz); + } +} + struct test_bf_directed_hyperedge_major_incidence_list : public test_bf_directed_incidence_list { using sut_type = hgl::impl::incidence_list; }; @@ -951,7 +1079,7 @@ TEST_CASE_FIXTURE( } TEST_CASE_FIXTURE( - test_bf_directed_vertex_major_incidence_list, + test_bf_directed_hyperedge_major_incidence_list, "incident_hyperedges should return a view of the vertex's incident hyperedge ids," "outgoing_hyperedges should return a view of the vertex's outgoing hyperedge ids (v in T(e))," "incoming_hyperedges should return a view of the vertex's incoming hyperedge ids (v in H(e))" @@ -972,7 +1100,7 @@ TEST_CASE_FIXTURE( } TEST_CASE_FIXTURE( - test_bf_directed_vertex_major_incidence_list, + test_bf_directed_hyperedge_major_incidence_list, "degree should return the number of the vertex's incident hyperedges," "out_degree should return the number of the vertex's outgoing hyperedges (v in T(e))," "in_degree should return the number of the vertex's incoming hyperedges (v in H(e))" @@ -1059,7 +1187,7 @@ TEST_CASE_FIXTURE( } TEST_CASE_FIXTURE( - test_bf_directed_vertex_major_incidence_list, + test_bf_directed_hyperedge_major_incidence_list, "incident_vertices should return a view of the hyperedge's incident vertex ids, " "tail_vertices should return a view of the hyperedge's tail vertex ids: T(e), " "head_vertices should return a view of the hyperedge's head vertex ids: H(e)" @@ -1072,13 +1200,15 @@ TEST_CASE_FIXTURE( const auto [tail_bound_vertices, head_bound_vertices] = altbind_to_hyperedge(sut, hyperedge_id, constants::n_vertices); - CHECK(std::ranges::equal(sut.incident_vertices(hyperedge_id), constants::vertex_ids_view)); + CHECK( + std::ranges::is_permutation(sut.incident_vertices(hyperedge_id), constants::vertex_ids_view) + ); CHECK(std::ranges::equal(sut.tail_vertices(hyperedge_id), tail_bound_vertices)); CHECK(std::ranges::equal(sut.head_vertices(hyperedge_id), head_bound_vertices)); } TEST_CASE_FIXTURE( - test_bf_directed_vertex_major_incidence_list, + test_bf_directed_hyperedge_major_incidence_list, "hyperedge_size should return the number of the hyperedge's incident vertices, " "tail_size should return the number of the hyperedge's tail vertices: |T(e)|, " "head_size should return the number of the hyperedge's head vertices: |H(e)|" @@ -1097,7 +1227,7 @@ TEST_CASE_FIXTURE( } TEST_CASE_FIXTURE( - test_bf_directed_vertex_major_incidence_list, + test_bf_directed_hyperedge_major_incidence_list, "bind_tail should insert the hyperedge id to the tail list of an appropriate vertex entry" ) { sut_type sut{constants::n_vertices, constants::n_hyperedges}; @@ -1116,7 +1246,7 @@ TEST_CASE_FIXTURE( } TEST_CASE_FIXTURE( - test_bf_directed_vertex_major_incidence_list, + test_bf_directed_hyperedge_major_incidence_list, "bind_head should insert the hyperedge id to the head list of an appropriate vertex entry" ) { sut_type sut{constants::n_vertices, constants::n_hyperedges}; @@ -1135,7 +1265,7 @@ TEST_CASE_FIXTURE( } TEST_CASE_FIXTURE( - test_bf_directed_vertex_major_incidence_list, + test_bf_directed_hyperedge_major_incidence_list, "binding methods should rebind the elements if they are already bound" ) { sut_type sut{constants::n_vertices, constants::n_hyperedges}; @@ -1165,7 +1295,7 @@ TEST_CASE_FIXTURE( } TEST_CASE_FIXTURE( - test_bf_directed_vertex_major_incidence_list, "unbind should clear the corresponding bit" + test_bf_directed_hyperedge_major_incidence_list, "unbind should clear the corresponding bit" ) { sut_type sut{constants::n_vertices, constants::n_hyperedges}; std::vector expected_storage; @@ -1193,7 +1323,7 @@ TEST_CASE_FIXTURE( } TEST_CASE_FIXTURE( - test_bf_directed_vertex_major_incidence_list, + test_bf_directed_hyperedge_major_incidence_list, "are_bound, is_tail, is_head should return true only when the corresponding major list entries " "contain the given minor ids" ) { @@ -1215,6 +1345,88 @@ TEST_CASE_FIXTURE( CHECK_FALSE(sut.is_head(constants::id3, constants::id1)); } +TEST_CASE_FIXTURE( + test_bf_directed_hyperedge_major_incidence_list, + "element size map getters should return maps of properly calculated element sizes" +) { + constexpr auto n_elements = 5ull; + sut_type sut{n_elements, n_elements}; + + constexpr auto is_zero = [](const auto& size) { return size == 0ull; }; + REQUIRE(std::ranges::all_of(sut.degree_map(n_elements), is_zero)); + REQUIRE(std::ranges::all_of(sut.out_degree_map(n_elements), is_zero)); + REQUIRE(std::ranges::all_of(sut.in_degree_map(n_elements), is_zero)); + REQUIRE(std::ranges::all_of(sut.hyperedge_size_map(n_elements), is_zero)); + REQUIRE(std::ranges::all_of(sut.tail_size_map(n_elements), is_zero)); + REQUIRE(std::ranges::all_of(sut.head_size_map(n_elements), is_zero)); + + SUBCASE("tail bind") { + for (std::size_t i = 0uz; i < n_elements; i++) + for (std::size_t j = 0uz; j <= i; j++) + sut.bind_tail(i, j); + + const auto deg_map = sut.degree_map(n_elements); + const auto out_deg_map = sut.out_degree_map(n_elements); + const auto esize_map = sut.hyperedge_size_map(n_elements); + const auto tsize_map = sut.tail_size_map(n_elements); + for (std::size_t i = 0uz; i < n_elements; i++) { + CHECK_EQ(deg_map[i], i + 1uz); + CHECK_EQ(out_deg_map[i], i + 1uz); + CHECK_EQ(esize_map[i], n_elements - i); + CHECK_EQ(tsize_map[i], n_elements - i); + } + CHECK(std::ranges::all_of(sut.in_degree_map(n_elements), is_zero)); + CHECK(std::ranges::all_of(sut.head_size_map(n_elements), is_zero)); + } + + SUBCASE("head bind") { + for (std::size_t i = 0uz; i < n_elements; i++) + for (std::size_t j = 0uz; j <= i; j++) + sut.bind_head(i, j); + + const auto deg_map = sut.degree_map(n_elements); + const auto in_deg_map = sut.in_degree_map(n_elements); + const auto esize_map = sut.hyperedge_size_map(n_elements); + const auto hsize_map = sut.head_size_map(n_elements); + for (std::size_t i = 0uz; i < n_elements; i++) { + CHECK_EQ(deg_map[i], i + 1uz); + CHECK_EQ(in_deg_map[i], i + 1uz); + CHECK_EQ(esize_map[i], n_elements - i); + CHECK_EQ(hsize_map[i], n_elements - i); + } + CHECK(std::ranges::all_of(sut.out_degree_map(n_elements), is_zero)); + CHECK(std::ranges::all_of(sut.tail_size_map(n_elements), is_zero)); + } + + // diagonal = tail, everything else is head + for (std::size_t i = 0uz; i < n_elements; i++) { + for (std::size_t j = 0uz; j <= i; j++) { + if (i == j) + sut.bind_tail(i, j); + else + sut.bind_head(i, j); + } + } + + const auto deg_map = sut.degree_map(n_elements); + const auto out_deg_map = sut.out_degree_map(n_elements); + const auto in_deg_map = sut.in_degree_map(n_elements); + + const auto esize_map = sut.hyperedge_size_map(n_elements); + const auto tsize_map = sut.tail_size_map(n_elements); + const auto hsize_map = sut.head_size_map(n_elements); + + for (std::size_t i = 0uz; i < n_elements; i++) { + CHECK_EQ(deg_map[i], i + 1uz); + CHECK_EQ(out_deg_map[i], 1uz); + CHECK_EQ(in_deg_map[i], i); + + CHECK_EQ(esize_map[i], n_elements - i); + CHECK_EQ(tsize_map[i], 1uz); + CHECK_EQ(hsize_map[i], n_elements - i - 1uz); + } +} + TEST_SUITE_END(); // test_incidence_list } // namespace hgl_testing diff --git a/tests/source/hgl/test_incidence_matrix.cpp b/tests/source/hgl/test_incidence_matrix.cpp index 101125e..c60a47f 100644 --- a/tests/source/hgl/test_incidence_matrix.cpp +++ b/tests/source/hgl/test_incidence_matrix.cpp @@ -306,6 +306,29 @@ TEST_CASE_FIXTURE( CHECK_FALSE(sut.are_bound(constants::id2, constants::id1)); } +TEST_CASE_FIXTURE( + test_undirected_vertex_major_incidence_matrix, + "element size map getters should return maps of properly calculated element sizes" +) { + constexpr auto n_elements = 5ull; + sut_type sut{n_elements, n_elements}; + + constexpr auto is_zero = [](const auto& size) { return size == 0ull; }; + REQUIRE(std::ranges::all_of(sut.degree_map(n_elements), is_zero)); + REQUIRE(std::ranges::all_of(sut.hyperedge_size_map(n_elements), is_zero)); + + for (std::size_t i = 0uz; i < n_elements; i++) + for (std::size_t j = 0uz; j <= i; j++) + sut.bind(i, j); + + const auto deg_map = sut.degree_map(n_elements); + const auto esize_map = sut.hyperedge_size_map(n_elements); + for (std::size_t i = 0uz; i < n_elements; i++) { + CHECK_EQ(deg_map[i], i + 1uz); + CHECK_EQ(esize_map[i], n_elements - i); + } +} + struct test_undirected_hyperedge_major_incidence_matrix : public test_incidence_matrix { using sut_type = hgl::impl::incidence_matrix; }; @@ -583,6 +606,29 @@ TEST_CASE_FIXTURE( CHECK_FALSE(sut.are_bound(constants::id2, constants::id1)); } +TEST_CASE_FIXTURE( + test_undirected_hyperedge_major_incidence_matrix, + "element size map getters should return maps of properly calculated element sizes" +) { + constexpr auto n_elements = 5ull; + sut_type sut{n_elements, n_elements}; + + constexpr auto is_zero = [](const auto& size) { return size == 0ull; }; + REQUIRE(std::ranges::all_of(sut.degree_map(n_elements), is_zero)); + REQUIRE(std::ranges::all_of(sut.hyperedge_size_map(n_elements), is_zero)); + + for (std::size_t i = 0uz; i < n_elements; i++) + for (std::size_t j = 0uz; j <= i; j++) + sut.bind(i, j); + + const auto deg_map = sut.degree_map(n_elements); + const auto esize_map = sut.hyperedge_size_map(n_elements); + for (std::size_t i = 0uz; i < n_elements; i++) { + CHECK_EQ(deg_map[i], i + 1uz); + CHECK_EQ(esize_map[i], n_elements - i); + } +} + struct test_bf_directed_incidence_matrix : public test_incidence_matrix { template auto is_incident_pred() { @@ -964,6 +1010,88 @@ TEST_CASE_FIXTURE( CHECK_FALSE(sut.is_head(constants::id3, constants::id1)); } +TEST_CASE_FIXTURE( + test_bf_directed_vertex_major_incidence_matrix, + "element size map getters should return maps of properly calculated element sizes" +) { + constexpr auto n_elements = 5ull; + sut_type sut{n_elements, n_elements}; + + constexpr auto is_zero = [](const auto& size) { return size == 0ull; }; + REQUIRE(std::ranges::all_of(sut.degree_map(n_elements), is_zero)); + REQUIRE(std::ranges::all_of(sut.out_degree_map(n_elements), is_zero)); + REQUIRE(std::ranges::all_of(sut.in_degree_map(n_elements), is_zero)); + REQUIRE(std::ranges::all_of(sut.hyperedge_size_map(n_elements), is_zero)); + REQUIRE(std::ranges::all_of(sut.tail_size_map(n_elements), is_zero)); + REQUIRE(std::ranges::all_of(sut.head_size_map(n_elements), is_zero)); + + SUBCASE("tail bind") { + for (std::size_t i = 0uz; i < n_elements; i++) + for (std::size_t j = 0uz; j <= i; j++) + sut.bind_tail(i, j); + + const auto deg_map = sut.degree_map(n_elements); + const auto out_deg_map = sut.out_degree_map(n_elements); + const auto esize_map = sut.hyperedge_size_map(n_elements); + const auto tsize_map = sut.tail_size_map(n_elements); + for (std::size_t i = 0uz; i < n_elements; i++) { + CHECK_EQ(deg_map[i], i + 1uz); + CHECK_EQ(out_deg_map[i], i + 1uz); + CHECK_EQ(esize_map[i], n_elements - i); + CHECK_EQ(tsize_map[i], n_elements - i); + } + CHECK(std::ranges::all_of(sut.in_degree_map(n_elements), is_zero)); + CHECK(std::ranges::all_of(sut.head_size_map(n_elements), is_zero)); + } + + SUBCASE("head bind") { + for (std::size_t i = 0uz; i < n_elements; i++) + for (std::size_t j = 0uz; j <= i; j++) + sut.bind_head(i, j); + + const auto deg_map = sut.degree_map(n_elements); + const auto in_deg_map = sut.in_degree_map(n_elements); + const auto esize_map = sut.hyperedge_size_map(n_elements); + const auto hsize_map = sut.head_size_map(n_elements); + for (std::size_t i = 0uz; i < n_elements; i++) { + CHECK_EQ(deg_map[i], i + 1uz); + CHECK_EQ(in_deg_map[i], i + 1uz); + CHECK_EQ(esize_map[i], n_elements - i); + CHECK_EQ(hsize_map[i], n_elements - i); + } + CHECK(std::ranges::all_of(sut.out_degree_map(n_elements), is_zero)); + CHECK(std::ranges::all_of(sut.tail_size_map(n_elements), is_zero)); + } + + // diagonal = tail, everything else is head + for (std::size_t i = 0uz; i < n_elements; i++) { + for (std::size_t j = 0uz; j <= i; j++) { + if (i == j) + sut.bind_tail(i, j); + else + sut.bind_head(i, j); + } + } + + const auto deg_map = sut.degree_map(n_elements); + const auto out_deg_map = sut.out_degree_map(n_elements); + const auto in_deg_map = sut.in_degree_map(n_elements); + + const auto esize_map = sut.hyperedge_size_map(n_elements); + const auto tsize_map = sut.tail_size_map(n_elements); + const auto hsize_map = sut.head_size_map(n_elements); + + for (std::size_t i = 0uz; i < n_elements; i++) { + CHECK_EQ(deg_map[i], i + 1uz); + CHECK_EQ(out_deg_map[i], 1uz); + CHECK_EQ(in_deg_map[i], i); + + CHECK_EQ(esize_map[i], n_elements - i); + CHECK_EQ(tsize_map[i], 1uz); + CHECK_EQ(hsize_map[i], n_elements - i - 1uz); + } +} + struct test_bf_directed_hyperedge_major_incidence_matrix : public test_bf_directed_incidence_matrix { using sut_type = hgl::impl::incidence_matrix; @@ -1075,7 +1203,7 @@ TEST_CASE_FIXTURE( } TEST_CASE_FIXTURE( - test_bf_directed_vertex_major_incidence_matrix, + test_bf_directed_hyperedge_major_incidence_matrix, "incident_hyperedges should return a view of the vertex's incident hyperedge ids," "outgoing_hyperedges should return a view of the vertex's outgoing hyperedge ids (v in T(e))," "incoming_hyperedges should return a view of the vertex's incoming hyperedge ids (v in H(e))" @@ -1094,7 +1222,7 @@ TEST_CASE_FIXTURE( } TEST_CASE_FIXTURE( - test_bf_directed_vertex_major_incidence_matrix, + test_bf_directed_hyperedge_major_incidence_matrix, "degree should return the number of the vertex's incident hyperedges," "out_degree should return the number of the vertex's outgoing hyperedges (v in T(e))," "in_degree should return the number of the vertex's incoming hyperedges (v in H(e))" @@ -1183,7 +1311,7 @@ TEST_CASE_FIXTURE( } TEST_CASE_FIXTURE( - test_bf_directed_vertex_major_incidence_matrix, + test_bf_directed_hyperedge_major_incidence_matrix, "incident_vertices should return a view of the hyperedge's incident vertex ids, " "tail_vertices should return a view of the hyperedge's tail vertex ids: T(e), " "head_vertices should return a view of the hyperedge's head vertex ids: H(e)" @@ -1202,7 +1330,7 @@ TEST_CASE_FIXTURE( } TEST_CASE_FIXTURE( - test_bf_directed_vertex_major_incidence_matrix, + test_bf_directed_hyperedge_major_incidence_matrix, "hyperedge_size should return the number of the hyperedge's incident vertices, " "tail_size should return the number of the hyperedge's tail vertices: |T(e)|, " "head_size should return the number of the hyperedge's head vertices: |H(e)|" @@ -1221,7 +1349,7 @@ TEST_CASE_FIXTURE( } TEST_CASE_FIXTURE( - test_bf_directed_vertex_major_incidence_matrix, + test_bf_directed_hyperedge_major_incidence_matrix, "bind_tail should set the corresponding matrix entry to backward incidence" ) { sut_type sut{constants::n_vertices, constants::n_hyperedges}; @@ -1238,7 +1366,7 @@ TEST_CASE_FIXTURE( } TEST_CASE_FIXTURE( - test_bf_directed_vertex_major_incidence_matrix, + test_bf_directed_hyperedge_major_incidence_matrix, "bind_head should set the corresponding matrix entry to forward incidence" ) { sut_type sut{constants::n_vertices, constants::n_hyperedges}; @@ -1255,7 +1383,7 @@ TEST_CASE_FIXTURE( } TEST_CASE_FIXTURE( - test_bf_directed_vertex_major_incidence_matrix, "unbind should clear the corresponding bit" + test_bf_directed_hyperedge_major_incidence_matrix, "unbind should clear the corresponding bit" ) { sut_type sut{constants::n_vertices, constants::n_hyperedges}; @@ -1279,7 +1407,7 @@ TEST_CASE_FIXTURE( } TEST_CASE_FIXTURE( - test_bf_directed_vertex_major_incidence_matrix, + test_bf_directed_hyperedge_major_incidence_matrix, "are_bound, is_tail, is_head should return true only when the corresponding matrix entry is " "set to a valid, matching incidence type" ) { @@ -1301,6 +1429,88 @@ TEST_CASE_FIXTURE( CHECK_FALSE(sut.is_head(constants::id3, constants::id1)); } +TEST_CASE_FIXTURE( + test_bf_directed_hyperedge_major_incidence_matrix, + "element size map getters should return maps of properly calculated element sizes" +) { + constexpr auto n_elements = 5ull; + sut_type sut{n_elements, n_elements}; + + constexpr auto is_zero = [](const auto& size) { return size == 0ull; }; + REQUIRE(std::ranges::all_of(sut.degree_map(n_elements), is_zero)); + REQUIRE(std::ranges::all_of(sut.out_degree_map(n_elements), is_zero)); + REQUIRE(std::ranges::all_of(sut.in_degree_map(n_elements), is_zero)); + REQUIRE(std::ranges::all_of(sut.hyperedge_size_map(n_elements), is_zero)); + REQUIRE(std::ranges::all_of(sut.tail_size_map(n_elements), is_zero)); + REQUIRE(std::ranges::all_of(sut.head_size_map(n_elements), is_zero)); + + SUBCASE("tail bind") { + for (std::size_t i = 0uz; i < n_elements; i++) + for (std::size_t j = 0uz; j <= i; j++) + sut.bind_tail(i, j); + + const auto deg_map = sut.degree_map(n_elements); + const auto out_deg_map = sut.out_degree_map(n_elements); + const auto esize_map = sut.hyperedge_size_map(n_elements); + const auto tsize_map = sut.tail_size_map(n_elements); + for (std::size_t i = 0uz; i < n_elements; i++) { + CHECK_EQ(deg_map[i], i + 1uz); + CHECK_EQ(out_deg_map[i], i + 1uz); + CHECK_EQ(esize_map[i], n_elements - i); + CHECK_EQ(tsize_map[i], n_elements - i); + } + CHECK(std::ranges::all_of(sut.in_degree_map(n_elements), is_zero)); + CHECK(std::ranges::all_of(sut.head_size_map(n_elements), is_zero)); + } + + SUBCASE("head bind") { + for (std::size_t i = 0uz; i < n_elements; i++) + for (std::size_t j = 0uz; j <= i; j++) + sut.bind_head(i, j); + + const auto deg_map = sut.degree_map(n_elements); + const auto in_deg_map = sut.in_degree_map(n_elements); + const auto esize_map = sut.hyperedge_size_map(n_elements); + const auto hsize_map = sut.head_size_map(n_elements); + for (std::size_t i = 0uz; i < n_elements; i++) { + CHECK_EQ(deg_map[i], i + 1uz); + CHECK_EQ(in_deg_map[i], i + 1uz); + CHECK_EQ(esize_map[i], n_elements - i); + CHECK_EQ(hsize_map[i], n_elements - i); + } + CHECK(std::ranges::all_of(sut.out_degree_map(n_elements), is_zero)); + CHECK(std::ranges::all_of(sut.tail_size_map(n_elements), is_zero)); + } + + // diagonal = tail, everything else is head + for (std::size_t i = 0uz; i < n_elements; i++) { + for (std::size_t j = 0uz; j <= i; j++) { + if (i == j) + sut.bind_tail(i, j); + else + sut.bind_head(i, j); + } + } + + const auto deg_map = sut.degree_map(n_elements); + const auto out_deg_map = sut.out_degree_map(n_elements); + const auto in_deg_map = sut.in_degree_map(n_elements); + + const auto esize_map = sut.hyperedge_size_map(n_elements); + const auto tsize_map = sut.tail_size_map(n_elements); + const auto hsize_map = sut.head_size_map(n_elements); + + for (std::size_t i = 0uz; i < n_elements; i++) { + CHECK_EQ(deg_map[i], i + 1uz); + CHECK_EQ(out_deg_map[i], 1uz); + CHECK_EQ(in_deg_map[i], i); + + CHECK_EQ(esize_map[i], n_elements - i); + CHECK_EQ(tsize_map[i], 1uz); + CHECK_EQ(hsize_map[i], n_elements - i - 1uz); + } +} + TEST_SUITE_END(); // test_incidence_matrix } // namespace hgl_testing