From d5ce4ba2f9e3250ae9ff1835be918fda8fc83524 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Fri, 26 Dec 2025 15:36:46 +0100 Subject: [PATCH 1/8] wip: bf-directed incidence list impl --- include/hgl/impl/incidence_list.hpp | 248 +++++++++++++++++++++++++--- 1 file changed, 223 insertions(+), 25 deletions(-) diff --git a/include/hgl/impl/incidence_list.hpp b/include/hgl/impl/incidence_list.hpp index bae01fd..a48288c 100644 --- a/include/hgl/impl/incidence_list.hpp +++ b/include/hgl/impl/incidence_list.hpp @@ -29,6 +29,7 @@ class incidence_list; template class incidence_list final { public: + using directional_tag = hgl::undirected_t; using layout_tag = LayoutTag; incidence_list(const incidence_list&) = delete; @@ -92,17 +93,23 @@ class incidence_list final { gl_attr_force_inline void bind( const types::id_type vertex_id, const types::id_type hyperedge_id ) noexcept { - this->_bind_impl( - layout_tag::major(vertex_id, hyperedge_id), layout_tag::minor(vertex_id, hyperedge_id) - ); + const auto [major_id, minor_id] = layout_tag::majmin(vertex_id, hyperedge_id); + auto& minor_storage = this->_major_storage[major_id]; + + // insert the id at the correct position to keep the minor-id collection sorted + const auto minor_it = std::ranges::lower_bound(minor_storage, minor_id); + if (minor_it == minor_storage.end() or *minor_it != minor_id) + minor_storage.insert(minor_it, minor_id); } gl_attr_force_inline void unbind( const types::id_type vertex_id, const types::id_type hyperedge_id ) noexcept { - this->_unbind_impl( - layout_tag::major(vertex_id, hyperedge_id), layout_tag::minor(vertex_id, hyperedge_id) - ); + const auto [major_id, minor_id] = layout_tag::majmin(vertex_id, hyperedge_id); + auto& minor_storage = this->_major_storage[major_id]; + const auto minor_it = std::ranges::lower_bound(minor_storage, minor_id); + if (minor_it != minor_storage.end() and *minor_it == minor_id) + minor_storage.erase(minor_it); } [[nodiscard]] gl_attr_force_inline bool are_bound( @@ -169,32 +176,223 @@ class incidence_list final { } } - // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) - void _bind_impl(const types::id_type major_id, const types::id_type minor_id) noexcept { - auto& minor_storage = this->_major_storage[major_id]; + [[nodiscard]] bool _are_bound_impl( + const major_element_type& major_el, const types::id_type minor_id + ) const noexcept { + const auto minor_it = std::ranges::lower_bound(major_el, minor_id); + return minor_it != major_el.end() and *minor_it == minor_id; + } - // insert the id at the correct position to keep the minor-id collection sorted - const auto minor_it = std::ranges::lower_bound(minor_storage, minor_id); - if (minor_it == minor_storage.end() or *minor_it != minor_id) - minor_storage.insert(minor_it, minor_id); + major_storage_type _major_storage; +}; + +template +class incidence_list final { +public: + using directional_tag = hgl::bf_directed_t; + using layout_tag = LayoutTag; + + incidence_list(const incidence_list&) = delete; + incidence_list& operator=(const incidence_list&) = delete; + + incidence_list() = default; + + incidence_list(const types::size_type n_vertices, const types::size_type n_hyperedges) + : _major_storage{layout_tag::major(n_vertices, n_hyperedges)} {} + + incidence_list(incidence_list&&) = default; + incidence_list& operator=(incidence_list&&) = default; + + ~incidence_list() = default; + + // --- vertex methods : general --- + + gl_attr_force_inline void add_vertices(const types::size_type n) noexcept { + this->_add(n); + } + + gl_attr_force_inline void remove_vertex(const types::id_type vertex_id) noexcept { + this->_remove(vertex_id); + } + + // --- vertex methods : incidence queries --- + + [[nodiscard]] gl_attr_force_inline auto incident_hyperedges(const types::id_type vertex_id + ) const noexcept { + return this->_get(vertex_id); + } + + [[nodiscard]] types::size_type degree(const types::id_type vertex_id) const noexcept { + return this->_size(vertex_id); + } + + [[nodiscard]] gl_attr_force_inline auto outgoing_hyperedges(const types::id_type vertex_id + ) const noexcept { + return this->_get(vertex_id); + } + + [[nodiscard]] types::size_type out_degree(const types::id_type vertex_id) const noexcept { + return this->_size(vertex_id); + } + + [[nodiscard]] gl_attr_force_inline auto incoming_hyperedges(const types::id_type vertex_id + ) const noexcept { + return this->_get(vertex_id); + } + + [[nodiscard]] types::size_type in_degree(const types::id_type vertex_id) const noexcept { + return this->_size(vertex_id); + } + + // --- hyperedge methods : general --- + + gl_attr_force_inline void add_hyperedges(const types::size_type n) noexcept { + this->_add(n); + } + + gl_attr_force_inline void remove_hyperedge(const types::id_type hyperedge_id) noexcept { + this->_remove(hyperedge_id); + } + + // --- hyperedge methods : incidence queries --- + + [[nodiscard]] gl_attr_force_inline auto incident_vertices(const types::id_type hyperedge_id + ) const noexcept { + return this->_get(hyperedge_id); + } + + [[nodiscard]] types::size_type hyperedge_size(const types::id_type hyperedge_id + ) const noexcept { + return this->_size(hyperedge_id); + } + + [[nodiscard]] gl_attr_force_inline auto tail_vertices(const types::id_type hyperedge_id + ) const noexcept { + return this->_get(hyperedge_id); } - gl_attr_force_inline void _unbind_impl( - // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) - const types::id_type major_id, - const types::id_type minor_id + [[nodiscard]] types::size_type tail_size(const types::id_type hyperedge_id) const noexcept { + return this->_size(hyperedge_id); + } + + [[nodiscard]] gl_attr_force_inline auto head_vertices(const types::id_type hyperedge_id + ) const noexcept { + return this->_get(hyperedge_id); + } + + [[nodiscard]] types::size_type head_size(const types::id_type hyperedge_id) const noexcept { + return this->_size(hyperedge_id); + } + + // --- binding methods --- + + gl_attr_force_inline void bind_tail( + const types::id_type vertex_id, const types::id_type hyperedge_id ) noexcept { - auto& minor_storage = this->_major_storage[major_id]; - const auto minor_it = std::ranges::lower_bound(minor_storage, minor_id); - if (minor_it != minor_storage.end() and *minor_it == minor_id) - minor_storage.erase(minor_it); + const auto [major_id, minor_id] = layout_tag::majmin(vertex_id, hyperedge_id); + this->_major_storage[major_id].tail.push_back(minor_id); } - [[nodiscard]] bool _are_bound_impl( - const major_element_type& major_el, const types::id_type minor_id + gl_attr_force_inline void bind_head( + const types::id_type vertex_id, const types::id_type hyperedge_id + ) noexcept { + const auto [major_id, minor_id] = layout_tag::majmin(vertex_id, hyperedge_id); + this->_major_storage[major_id].head.push_back(minor_id); + } + + gl_attr_force_inline void unbind( + const types::id_type vertex_id, const types::id_type hyperedge_id + ) noexcept { + const auto [major_id, minor_id] = layout_tag::majmin(vertex_id, hyperedge_id); + auto& entry = this->_major_storage[major_id]; + } + + [[nodiscard]] gl_attr_force_inline bool are_bound( + const types::id_type vertex_id, const types::id_type hyperedge_id ) const noexcept { - const auto minor_it = std::ranges::lower_bound(major_el, minor_id); - return minor_it != major_el.end() and *minor_it == minor_id; + const auto [major_id, minor_id] = layout_tag::majmin(vertex_id, hyperedge_id); + + } + + [[nodiscard]] gl_attr_force_inline bool is_tail( + const types::id_type vertex_id, const types::id_type hyperedge_id + ) const noexcept { + const auto [major_id, minor_id] = layout_tag::majmin(vertex_id, hyperedge_id); + + } + + [[nodiscard]] gl_attr_force_inline bool is_head( + const types::id_type vertex_id, const types::id_type hyperedge_id + ) const noexcept { + const auto [major_id, minor_id] = layout_tag::majmin(vertex_id, hyperedge_id); + + } + +#ifdef HGL_TESTING + friend struct hgl_testing::test_incidence_list; +#endif + +private: + using minor_element_type = types::id_type; + using minor_storage_type = std::vector; + + struct major_element_type { + minor_storage_type tail; + minor_storage_type head; + }; + + 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 + this->_major_storage.erase( + this->_major_storage.begin() + static_cast(id) + ); + } + else { // remove minor + for (auto& minor_storage : this->_major_storage) { + this->_remove_minor(minor_storage.tail, id); + this->_remove_minor(minor_storage.head, id); + } + } + } + + void _remove_minor(minor_storage_type& minor_storage, const types::id_type minor_id) noexcept { + auto minor_it = std::ranges::lower_bound(minor_storage, minor_id); + if (minor_it != minor_storage.end() and *minor_it == minor_id) + minor_it = minor_storage.erase(minor_it); // unbind the element + while (minor_it != minor_storage.end()) + --(*minor_it++); // decrement ids > minor_id + } + + 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]; + return std::views::join(entry.tail, entry.head); + } + else { // get minor + + } + } + + 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; + return size; + } } major_storage_type _major_storage; From 3024e4af2071521d645aca88f876869a4703fd6d Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Mon, 29 Dec 2025 16:46:59 +0100 Subject: [PATCH 2/8] initial implementation of the bf-directed incidence list --- include/hgl/impl/incidence_list.hpp | 185 ++++++++++++++++++++------ include/hgl/impl/incidence_matrix.hpp | 40 +++--- include/hgl/impl/layout_tags.hpp | 16 ++- 3 files changed, 183 insertions(+), 58 deletions(-) diff --git a/include/hgl/impl/incidence_list.hpp b/include/hgl/impl/incidence_list.hpp index a48288c..0e99403 100644 --- a/include/hgl/impl/incidence_list.hpp +++ b/include/hgl/impl/incidence_list.hpp @@ -56,12 +56,14 @@ class incidence_list final { this->_remove(vertex_id); } - [[nodiscard]] gl_attr_force_inline auto incident_hyperedges(const types::id_type vertex_id + [[nodiscard]] gl_attr_force_inline auto incident_hyperedges( + const types::id_type vertex_id ) const noexcept { return this->_incident_with(vertex_id); } - [[nodiscard]] gl_attr_force_inline types::size_type degree(const types::id_type vertex_id + [[nodiscard]] gl_attr_force_inline types::size_type degree( + const types::id_type vertex_id ) const noexcept { return this->_size(vertex_id); } @@ -77,7 +79,8 @@ class incidence_list final { this->_remove(hyperedge_id); } - [[nodiscard]] gl_attr_force_inline auto incident_vertices(const types::id_type hyperedge_id + [[nodiscard]] gl_attr_force_inline auto incident_vertices( + const types::id_type hyperedge_id ) const noexcept { return this->_incident_with(hyperedge_id); } @@ -115,7 +118,7 @@ class incidence_list final { [[nodiscard]] gl_attr_force_inline bool are_bound( const types::id_type vertex_id, const types::id_type hyperedge_id ) const noexcept { - return this->_are_bound_impl( + return this->_contains( this->_major_storage[layout_tag::major(vertex_id, hyperedge_id)], layout_tag::minor(vertex_id, hyperedge_id) ); @@ -157,7 +160,7 @@ class incidence_list final { else { // incident with minor return std::views::iota(0uz, this->_major_storage.size()) | std::views::filter([this, minor_id = id](types::id_type major_id) { - return this->_are_bound_impl(this->_major_storage[major_id], minor_id); + return this->_contains(this->_major_storage[major_id], minor_id); }); } } @@ -170,17 +173,17 @@ class incidence_list final { else { // size minor types::size_type size = 0uz; for (const auto& major_el : this->_major_storage) - if (this->_are_bound_impl(major_el, id)) + if (this->_contains(major_el, id)) ++size; return size; } } - [[nodiscard]] bool _are_bound_impl( - const major_element_type& major_el, const types::id_type minor_id + [[nodiscard]] bool _contains( + const minor_storage_type& minor_storage, const types::id_type minor_id ) const noexcept { - const auto minor_it = std::ranges::lower_bound(major_el, minor_id); - return minor_it != major_el.end() and *minor_it == minor_id; + const auto minor_it = std::ranges::lower_bound(minor_storage, minor_id); + return minor_it != minor_storage.end() and *minor_it == minor_id; } major_storage_type _major_storage; @@ -217,7 +220,8 @@ class incidence_list final { // --- vertex methods : incidence queries --- - [[nodiscard]] gl_attr_force_inline auto incident_hyperedges(const types::id_type vertex_id + [[nodiscard]] gl_attr_force_inline auto incident_hyperedges( + const types::id_type vertex_id ) const noexcept { return this->_get(vertex_id); } @@ -226,22 +230,24 @@ class incidence_list final { return this->_size(vertex_id); } - [[nodiscard]] gl_attr_force_inline auto outgoing_hyperedges(const types::id_type vertex_id + [[nodiscard]] gl_attr_force_inline auto outgoing_hyperedges( + const types::id_type vertex_id ) const noexcept { - return this->_get(vertex_id); + return this->_get_tail(vertex_id); } [[nodiscard]] types::size_type out_degree(const types::id_type vertex_id) const noexcept { - return this->_size(vertex_id); + return this->_tail_size(vertex_id); } - [[nodiscard]] gl_attr_force_inline auto incoming_hyperedges(const types::id_type vertex_id + [[nodiscard]] gl_attr_force_inline auto incoming_hyperedges( + const types::id_type vertex_id ) const noexcept { - return this->_get(vertex_id); + return this->_get_head(vertex_id); } [[nodiscard]] types::size_type in_degree(const types::id_type vertex_id) const noexcept { - return this->_size(vertex_id); + return this->_head_size(vertex_id); } // --- hyperedge methods : general --- @@ -256,32 +262,34 @@ class incidence_list final { // --- hyperedge methods : incidence queries --- - [[nodiscard]] gl_attr_force_inline auto incident_vertices(const types::id_type hyperedge_id + [[nodiscard]] gl_attr_force_inline auto incident_vertices( + const types::id_type hyperedge_id ) const noexcept { return this->_get(hyperedge_id); } - [[nodiscard]] types::size_type hyperedge_size(const types::id_type hyperedge_id - ) const noexcept { + [[nodiscard]] types::size_type hyperedge_size(const types::id_type hyperedge_id) const noexcept { return this->_size(hyperedge_id); } - [[nodiscard]] gl_attr_force_inline auto tail_vertices(const types::id_type hyperedge_id + [[nodiscard]] gl_attr_force_inline auto tail_vertices( + const types::id_type hyperedge_id ) const noexcept { - return this->_get(hyperedge_id); + return this->_get_tail(hyperedge_id); } [[nodiscard]] types::size_type tail_size(const types::id_type hyperedge_id) const noexcept { - return this->_size(hyperedge_id); + return this->_tail_size(hyperedge_id); } - [[nodiscard]] gl_attr_force_inline auto head_vertices(const types::id_type hyperedge_id + [[nodiscard]] gl_attr_force_inline auto head_vertices( + const types::id_type hyperedge_id ) const noexcept { - return this->_get(hyperedge_id); + return this->_get_head(hyperedge_id); } [[nodiscard]] types::size_type head_size(const types::id_type hyperedge_id) const noexcept { - return this->_size(hyperedge_id); + return this->_head_size(hyperedge_id); } // --- binding methods --- @@ -290,14 +298,14 @@ class incidence_list final { const types::id_type vertex_id, const types::id_type hyperedge_id ) noexcept { const auto [major_id, minor_id] = layout_tag::majmin(vertex_id, hyperedge_id); - this->_major_storage[major_id].tail.push_back(minor_id); + this->_unique_insert(this->_major_storage[major_id].tail, minor_id); } gl_attr_force_inline void bind_head( const types::id_type vertex_id, const types::id_type hyperedge_id ) noexcept { const auto [major_id, minor_id] = layout_tag::majmin(vertex_id, hyperedge_id); - this->_major_storage[major_id].head.push_back(minor_id); + this->_unique_insert(this->_major_storage[major_id].head, minor_id); } gl_attr_force_inline void unbind( @@ -305,27 +313,29 @@ class incidence_list final { ) noexcept { const auto [major_id, minor_id] = layout_tag::majmin(vertex_id, hyperedge_id); auto& entry = this->_major_storage[major_id]; + this->_remove_no_align(entry.tail, minor_id); + this->_remove_no_align(entry.head, minor_id); } [[nodiscard]] gl_attr_force_inline bool are_bound( const types::id_type vertex_id, const types::id_type hyperedge_id ) const noexcept { const auto [major_id, minor_id] = layout_tag::majmin(vertex_id, hyperedge_id); - + return this->_contains(this->_major_storage[major_id], minor_id); } [[nodiscard]] gl_attr_force_inline bool is_tail( const types::id_type vertex_id, const types::id_type hyperedge_id ) const noexcept { const auto [major_id, minor_id] = layout_tag::majmin(vertex_id, hyperedge_id); - + return this->_contains(this->_major_storage[major_id].tail, minor_id); } [[nodiscard]] gl_attr_force_inline bool is_head( const types::id_type vertex_id, const types::id_type hyperedge_id ) const noexcept { const auto [major_id, minor_id] = layout_tag::majmin(vertex_id, hyperedge_id); - + return this->_contains(this->_major_storage[major_id].head, minor_id); } #ifdef HGL_TESTING @@ -336,6 +346,13 @@ class incidence_list final { using minor_element_type = types::id_type; using minor_storage_type = std::vector; + /** + * For hyperedge-major representation: tail = T(e), head = H(e) + * For vertex-major representation: + * - tail = {e in E(G) : v in T(e)} + * - head = {e in E(G) : v in H(e)} + * where G is the hypergraph and v is the vertex entry in the outer list + */ struct major_element_type { minor_storage_type tail; minor_storage_type head; @@ -358,18 +375,23 @@ class incidence_list final { } else { // remove minor for (auto& minor_storage : this->_major_storage) { - this->_remove_minor(minor_storage.tail, id); - this->_remove_minor(minor_storage.head, id); + this->_remove(minor_storage.tail, id); + this->_remove(minor_storage.head, id); } } } - void _remove_minor(minor_storage_type& minor_storage, const types::id_type minor_id) noexcept { - auto minor_it = std::ranges::lower_bound(minor_storage, minor_id); - if (minor_it != minor_storage.end() and *minor_it == minor_id) - minor_it = minor_storage.erase(minor_it); // unbind the element + void _remove(minor_storage_type& minor_storage, const types::id_type id) noexcept { + auto minor_it = this->_remove_no_align(minor_storage, id); while (minor_it != minor_storage.end()) - --(*minor_it++); // decrement ids > minor_id + --(*minor_it++); // decrement ids > id + } + + auto _remove_no_align(minor_storage_type& minor_storage, const types::id_type id) noexcept { + auto minor_it = std::ranges::lower_bound(minor_storage, id); + if (minor_it != minor_storage.end() and *minor_it == id) + minor_it = minor_storage.erase(minor_it); // unbind the element + return minor_it; } template @@ -379,22 +401,107 @@ class incidence_list final { return std::views::join(entry.tail, entry.head); } 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 types::size_type _size(const types::id_type id) const noexcept { + [[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 + 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); + }); + } + } + + 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 + ) const noexcept { + if constexpr (Element == layout_tag::major_element) { // size major + return this->_major_storage[id].tail.size(); + } + else { // size minor + types::size_type size = 0uz; + for (const auto& major_el : this->_major_storage) + if (this->_contains(major_el.tail, id)) + ++size; return size; } } + template + [[nodiscard]] gl_attr_force_inline types::size_type _head_size( + const types::id_type id + ) const noexcept { + if constexpr (Element == layout_tag::major_element) { // size major + return this->_major_storage[id].head.size(); + } + 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; + } + } + + void _unique_insert(minor_storage_type& minor_storage, const types::id_type id) noexcept { + const auto minor_it = std::ranges::lower_bound(minor_storage, id); + if (minor_it == minor_storage.end() or *minor_it != id) + minor_storage.insert(minor_it, id); + } + + [[nodiscard]] gl_attr_force_inline bool _contains( + const major_element_type& major_el, const types::id_type id + ) const noexcept { + return this->_contains(major_el.tail, id) or this->_contains(major_el.head, id); + } + + [[nodiscard]] bool _contains( + const minor_storage_type& minor_storage, const types::id_type id + ) const noexcept { + const auto minor_it = std::ranges::lower_bound(minor_storage, id); + return minor_it != minor_storage.end() and *minor_it == id; + } + major_storage_type _major_storage; }; diff --git a/include/hgl/impl/incidence_matrix.hpp b/include/hgl/impl/incidence_matrix.hpp index d240535..77b4537 100644 --- a/include/hgl/impl/incidence_matrix.hpp +++ b/include/hgl/impl/incidence_matrix.hpp @@ -59,7 +59,8 @@ class incidence_matrix final { this->_remove(vertex_id); } - [[nodiscard]] gl_attr_force_inline auto incident_hyperedges(const types::id_type vertex_id + [[nodiscard]] gl_attr_force_inline auto incident_hyperedges( + const types::id_type vertex_id ) const noexcept { return this->_incident_with(vertex_id); } @@ -78,13 +79,13 @@ class incidence_matrix final { this->_remove(hyperedge_id); } - [[nodiscard]] gl_attr_force_inline auto incident_vertices(const types::id_type hyperedge_id + [[nodiscard]] gl_attr_force_inline auto incident_vertices( + const types::id_type hyperedge_id ) const noexcept { return this->_incident_with(hyperedge_id); } - [[nodiscard]] types::size_type hyperedge_size(const types::id_type hyperedge_id - ) const noexcept { + [[nodiscard]] types::size_type hyperedge_size(const types::id_type hyperedge_id) const noexcept { return this->_count(hyperedge_id); } @@ -165,7 +166,9 @@ class incidence_matrix final { } template - gl_attr_force_inline types::size_type _count(const types::id_type id) const noexcept { + [[nodiscard]] gl_attr_force_inline types::size_type _count( + const types::id_type id + ) const noexcept { types::size_type count = 0uz; if constexpr (Element == layout_tag::major_element) { // count major for (const bool bit : this->_matrix[id]) @@ -217,7 +220,8 @@ class incidence_matrix final { // --- vertex methods : incidence queries --- - [[nodiscard]] gl_attr_force_inline auto incident_hyperedges(const types::id_type vertex_id + [[nodiscard]] gl_attr_force_inline auto incident_hyperedges( + const types::id_type vertex_id ) const noexcept { return this->_query(vertex_id, _is_incident); } @@ -226,7 +230,8 @@ class incidence_matrix final { return this->_count(vertex_id, _is_incident); } - [[nodiscard]] gl_attr_force_inline auto outgoing_hyperedges(const types::id_type vertex_id + [[nodiscard]] gl_attr_force_inline auto outgoing_hyperedges( + const types::id_type vertex_id ) const noexcept { return this->_query(vertex_id, _is_tail); } @@ -235,7 +240,8 @@ class incidence_matrix final { return this->_count(vertex_id, _is_tail); } - [[nodiscard]] gl_attr_force_inline auto incoming_hyperedges(const types::id_type vertex_id + [[nodiscard]] gl_attr_force_inline auto incoming_hyperedges( + const types::id_type vertex_id ) const noexcept { return this->_query(vertex_id, _is_head); } @@ -256,17 +262,18 @@ class incidence_matrix final { // --- hyperedge methods : incidence queries --- - [[nodiscard]] gl_attr_force_inline auto incident_vertices(const types::id_type hyperedge_id + [[nodiscard]] gl_attr_force_inline auto incident_vertices( + const types::id_type hyperedge_id ) const noexcept { return this->_query(hyperedge_id, _is_incident); } - [[nodiscard]] types::size_type hyperedge_size(const types::id_type hyperedge_id - ) const noexcept { + [[nodiscard]] types::size_type hyperedge_size(const types::id_type hyperedge_id) const noexcept { return this->_count(hyperedge_id, _is_incident); } - [[nodiscard]] gl_attr_force_inline auto tail_vertices(const types::id_type hyperedge_id + [[nodiscard]] gl_attr_force_inline auto tail_vertices( + const types::id_type hyperedge_id ) const noexcept { return this->_query(hyperedge_id, _is_tail); } @@ -275,7 +282,8 @@ class incidence_matrix final { return this->_count(hyperedge_id, _is_tail); } - [[nodiscard]] gl_attr_force_inline auto head_vertices(const types::id_type hyperedge_id + [[nodiscard]] gl_attr_force_inline auto head_vertices( + const types::id_type hyperedge_id ) const noexcept { return this->_query(hyperedge_id, _is_head); } @@ -393,8 +401,10 @@ class incidence_matrix final { ) const noexcept { if constexpr (Element == layout_tag::major_element) { // query major return std::views::iota(0uz, this->_matrix_row_size) - | std::views::filter([&row = this->_matrix[id], pred](const types::id_type minor_id - ) { return pred(row[minor_id]); }); + | std::views::filter([&row = this->_matrix[id], + pred](const types::id_type minor_id) { + return pred(row[minor_id]); + }); } else { // query minor return std::views::iota(0uz, this->_matrix.size()) diff --git a/include/hgl/impl/layout_tags.hpp b/include/hgl/impl/layout_tags.hpp index 56e2118..baec05f 100644 --- a/include/hgl/impl/layout_tags.hpp +++ b/include/hgl/impl/layout_tags.hpp @@ -20,12 +20,16 @@ struct vertex_major_t { static constexpr element_type minor_element = element_type::hyperedge; template - [[nodiscard]] static constexpr T major(const T& vertex_el, const T& hyperedge_el) noexcept { + [[nodiscard]] static constexpr T major( + const T& vertex_el, [[maybe_unused]] const T& hyperedge_el + ) noexcept { return vertex_el; } template - [[nodiscard]] static constexpr T minor(const T& vertex_el, const T& hyperedge_el) noexcept { + [[nodiscard]] static constexpr T minor( + [[maybe_unused]] const T& vertex_el, const T& hyperedge_el + ) noexcept { return hyperedge_el; } @@ -42,12 +46,16 @@ struct hyperedge_major_t { static constexpr element_type minor_element = element_type::vertex; template - [[nodiscard]] static constexpr T major(const T& vertex_el, const T& hyperedge_el) noexcept { + [[nodiscard]] static constexpr T major( + [[maybe_unused]] const T& vertex_el, const T& hyperedge_el + ) noexcept { return hyperedge_el; } template - [[nodiscard]] static constexpr T minor(const T& vertex_el, const T& hyperedge_el) noexcept { + [[nodiscard]] static constexpr T minor( + const T& vertex_el, [[maybe_unused]] const T& hyperedge_el + ) noexcept { return vertex_el; } From 56af95bb4d865011d4d5117d72b68c33d7b93e80 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Mon, 29 Dec 2025 16:58:56 +0100 Subject: [PATCH 3/8] added an exe argument to the formatting script --- .github/workflows/format.yaml | 5 ++- .github/workflows/licence.yaml | 3 +- include/gl/algorithm/dijkstra.hpp | 3 +- include/hgl/impl/incidence_list.hpp | 44 ++++++++++----------------- include/hgl/impl/incidence_matrix.hpp | 39 +++++++++--------------- scripts/format.py | 20 +++++++----- 6 files changed, 48 insertions(+), 66 deletions(-) diff --git a/.github/workflows/format.yaml b/.github/workflows/format.yaml index 42b9d14..f1d5649 100644 --- a/.github/workflows/format.yaml +++ b/.github/workflows/format.yaml @@ -2,7 +2,7 @@ name: format on: push: branches: - - '*' + - "*" paths: - .github/workflows/format.yaml - scripts/format.py @@ -11,7 +11,6 @@ on: - tests/source/** - tests/app/** - jobs: build: name: Test code formatting @@ -29,4 +28,4 @@ jobs: - name: Test formatting shell: bash run: | - python3 scripts/format.py --check + python3 scripts/format.py --check --clang-format-executable clang-format-18 diff --git a/.github/workflows/licence.yaml b/.github/workflows/licence.yaml index 0ac69af..a7bef3c 100644 --- a/.github/workflows/licence.yaml +++ b/.github/workflows/licence.yaml @@ -2,13 +2,12 @@ name: licence on: push: branches: - - '*' + - "*" paths: - .github/workflows/licence.yaml - scripts/check_licence.py - include/** - jobs: build: name: Test code formatting diff --git a/include/gl/algorithm/dijkstra.hpp b/include/gl/algorithm/dijkstra.hpp index c37f064..6099ced 100644 --- a/include/gl/algorithm/dijkstra.hpp +++ b/include/gl/algorithm/dijkstra.hpp @@ -116,7 +116,8 @@ template < if (negative_edge.has_value()) { const auto& edge = negative_edge.value(); throw std::invalid_argument(std::format( - "[alg::dijkstra_shortest_paths] Found an edge with a negative weight: [{}, {} | w={}]", + "[alg::dijkstra_shortest_paths] Found an edge with a negative weight: [{}, {} | " + "w={}]", edge.source(), edge.target(), get_weight(edge) diff --git a/include/hgl/impl/incidence_list.hpp b/include/hgl/impl/incidence_list.hpp index 0e99403..0d02351 100644 --- a/include/hgl/impl/incidence_list.hpp +++ b/include/hgl/impl/incidence_list.hpp @@ -56,14 +56,12 @@ class incidence_list final { this->_remove(vertex_id); } - [[nodiscard]] gl_attr_force_inline auto incident_hyperedges( - const types::id_type vertex_id + [[nodiscard]] gl_attr_force_inline auto incident_hyperedges(const types::id_type vertex_id ) const noexcept { return this->_incident_with(vertex_id); } - [[nodiscard]] gl_attr_force_inline types::size_type degree( - const types::id_type vertex_id + [[nodiscard]] gl_attr_force_inline types::size_type degree(const types::id_type vertex_id ) const noexcept { return this->_size(vertex_id); } @@ -79,8 +77,7 @@ class incidence_list final { this->_remove(hyperedge_id); } - [[nodiscard]] gl_attr_force_inline auto incident_vertices( - const types::id_type hyperedge_id + [[nodiscard]] gl_attr_force_inline auto incident_vertices(const types::id_type hyperedge_id ) const noexcept { return this->_incident_with(hyperedge_id); } @@ -220,8 +217,7 @@ class incidence_list final { // --- vertex methods : incidence queries --- - [[nodiscard]] gl_attr_force_inline auto incident_hyperedges( - const types::id_type vertex_id + [[nodiscard]] gl_attr_force_inline auto incident_hyperedges(const types::id_type vertex_id ) const noexcept { return this->_get(vertex_id); } @@ -230,8 +226,7 @@ class incidence_list final { return this->_size(vertex_id); } - [[nodiscard]] gl_attr_force_inline auto outgoing_hyperedges( - const types::id_type vertex_id + [[nodiscard]] gl_attr_force_inline auto outgoing_hyperedges(const types::id_type vertex_id ) const noexcept { return this->_get_tail(vertex_id); } @@ -240,8 +235,7 @@ class incidence_list final { return this->_tail_size(vertex_id); } - [[nodiscard]] gl_attr_force_inline auto incoming_hyperedges( - const types::id_type vertex_id + [[nodiscard]] gl_attr_force_inline auto incoming_hyperedges(const types::id_type vertex_id ) const noexcept { return this->_get_head(vertex_id); } @@ -262,18 +256,17 @@ class incidence_list final { // --- hyperedge methods : incidence queries --- - [[nodiscard]] gl_attr_force_inline auto incident_vertices( - const types::id_type hyperedge_id + [[nodiscard]] gl_attr_force_inline auto incident_vertices(const types::id_type hyperedge_id ) const noexcept { return this->_get(hyperedge_id); } - [[nodiscard]] types::size_type hyperedge_size(const types::id_type hyperedge_id) const noexcept { + [[nodiscard]] types::size_type hyperedge_size(const types::id_type hyperedge_id + ) const noexcept { return this->_size(hyperedge_id); } - [[nodiscard]] gl_attr_force_inline auto tail_vertices( - const types::id_type hyperedge_id + [[nodiscard]] gl_attr_force_inline auto tail_vertices(const types::id_type hyperedge_id ) const noexcept { return this->_get_tail(hyperedge_id); } @@ -282,8 +275,7 @@ class incidence_list final { return this->_tail_size(hyperedge_id); } - [[nodiscard]] gl_attr_force_inline auto head_vertices( - const types::id_type hyperedge_id + [[nodiscard]] gl_attr_force_inline auto head_vertices(const types::id_type hyperedge_id ) const noexcept { return this->_get_head(hyperedge_id); } @@ -435,8 +427,7 @@ class incidence_list final { } template - [[nodiscard]] gl_attr_force_inline types::size_type _size( - const types::id_type id + [[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]; @@ -452,8 +443,7 @@ class incidence_list final { } template - [[nodiscard]] gl_attr_force_inline types::size_type _tail_size( - const types::id_type id + [[nodiscard]] gl_attr_force_inline types::size_type _tail_size(const types::id_type id ) const noexcept { if constexpr (Element == layout_tag::major_element) { // size major return this->_major_storage[id].tail.size(); @@ -468,8 +458,7 @@ class incidence_list final { } template - [[nodiscard]] gl_attr_force_inline types::size_type _head_size( - const types::id_type id + [[nodiscard]] gl_attr_force_inline types::size_type _head_size(const types::id_type id ) const noexcept { if constexpr (Element == layout_tag::major_element) { // size major return this->_major_storage[id].head.size(); @@ -495,9 +484,8 @@ class incidence_list final { return this->_contains(major_el.tail, id) or this->_contains(major_el.head, id); } - [[nodiscard]] bool _contains( - const minor_storage_type& minor_storage, const types::id_type id - ) const noexcept { + [[nodiscard]] bool _contains(const minor_storage_type& minor_storage, const types::id_type id) + const noexcept { const auto minor_it = std::ranges::lower_bound(minor_storage, id); return minor_it != minor_storage.end() and *minor_it == id; } diff --git a/include/hgl/impl/incidence_matrix.hpp b/include/hgl/impl/incidence_matrix.hpp index 77b4537..295f007 100644 --- a/include/hgl/impl/incidence_matrix.hpp +++ b/include/hgl/impl/incidence_matrix.hpp @@ -59,8 +59,7 @@ class incidence_matrix final { this->_remove(vertex_id); } - [[nodiscard]] gl_attr_force_inline auto incident_hyperedges( - const types::id_type vertex_id + [[nodiscard]] gl_attr_force_inline auto incident_hyperedges(const types::id_type vertex_id ) const noexcept { return this->_incident_with(vertex_id); } @@ -79,13 +78,13 @@ class incidence_matrix final { this->_remove(hyperedge_id); } - [[nodiscard]] gl_attr_force_inline auto incident_vertices( - const types::id_type hyperedge_id + [[nodiscard]] gl_attr_force_inline auto incident_vertices(const types::id_type hyperedge_id ) const noexcept { return this->_incident_with(hyperedge_id); } - [[nodiscard]] types::size_type hyperedge_size(const types::id_type hyperedge_id) const noexcept { + [[nodiscard]] types::size_type hyperedge_size(const types::id_type hyperedge_id + ) const noexcept { return this->_count(hyperedge_id); } @@ -166,8 +165,7 @@ class incidence_matrix final { } template - [[nodiscard]] gl_attr_force_inline types::size_type _count( - const types::id_type id + [[nodiscard]] gl_attr_force_inline types::size_type _count(const types::id_type id ) const noexcept { types::size_type count = 0uz; if constexpr (Element == layout_tag::major_element) { // count major @@ -220,8 +218,7 @@ class incidence_matrix final { // --- vertex methods : incidence queries --- - [[nodiscard]] gl_attr_force_inline auto incident_hyperedges( - const types::id_type vertex_id + [[nodiscard]] gl_attr_force_inline auto incident_hyperedges(const types::id_type vertex_id ) const noexcept { return this->_query(vertex_id, _is_incident); } @@ -230,8 +227,7 @@ class incidence_matrix final { return this->_count(vertex_id, _is_incident); } - [[nodiscard]] gl_attr_force_inline auto outgoing_hyperedges( - const types::id_type vertex_id + [[nodiscard]] gl_attr_force_inline auto outgoing_hyperedges(const types::id_type vertex_id ) const noexcept { return this->_query(vertex_id, _is_tail); } @@ -240,8 +236,7 @@ class incidence_matrix final { return this->_count(vertex_id, _is_tail); } - [[nodiscard]] gl_attr_force_inline auto incoming_hyperedges( - const types::id_type vertex_id + [[nodiscard]] gl_attr_force_inline auto incoming_hyperedges(const types::id_type vertex_id ) const noexcept { return this->_query(vertex_id, _is_head); } @@ -262,18 +257,17 @@ class incidence_matrix final { // --- hyperedge methods : incidence queries --- - [[nodiscard]] gl_attr_force_inline auto incident_vertices( - const types::id_type hyperedge_id + [[nodiscard]] gl_attr_force_inline auto incident_vertices(const types::id_type hyperedge_id ) const noexcept { return this->_query(hyperedge_id, _is_incident); } - [[nodiscard]] types::size_type hyperedge_size(const types::id_type hyperedge_id) const noexcept { + [[nodiscard]] types::size_type hyperedge_size(const types::id_type hyperedge_id + ) const noexcept { return this->_count(hyperedge_id, _is_incident); } - [[nodiscard]] gl_attr_force_inline auto tail_vertices( - const types::id_type hyperedge_id + [[nodiscard]] gl_attr_force_inline auto tail_vertices(const types::id_type hyperedge_id ) const noexcept { return this->_query(hyperedge_id, _is_tail); } @@ -282,8 +276,7 @@ class incidence_matrix final { return this->_count(hyperedge_id, _is_tail); } - [[nodiscard]] gl_attr_force_inline auto head_vertices( - const types::id_type hyperedge_id + [[nodiscard]] gl_attr_force_inline auto head_vertices(const types::id_type hyperedge_id ) const noexcept { return this->_query(hyperedge_id, _is_head); } @@ -401,10 +394,8 @@ class incidence_matrix final { ) const noexcept { if constexpr (Element == layout_tag::major_element) { // query major return std::views::iota(0uz, this->_matrix_row_size) - | std::views::filter([&row = this->_matrix[id], - pred](const types::id_type minor_id) { - return pred(row[minor_id]); - }); + | std::views::filter([&row = this->_matrix[id], pred](const types::id_type minor_id + ) { return pred(row[minor_id]); }); } else { // query minor return std::views::iota(0uz, this->_matrix.size()) diff --git a/scripts/format.py b/scripts/format.py index 4c7dacc..11bc9c8 100644 --- a/scripts/format.py +++ b/scripts/format.py @@ -12,6 +12,7 @@ class DefaultParameters: file_patterns: list[str] = ["*.cpp", "*.hpp", "*.c", "*.h"] exclude_paths: list[str] = ["tests/external"] check: bool = False + clang_format_executable: str = "clang-format" def parse_args(): @@ -19,7 +20,6 @@ def parse_args(): parser.add_argument( "-m", "--modified-files", - type=bool, default=DefaultParameters.modified_files, action=argparse.BooleanOptionalAction, help="run clang-format only on the files modified since last pushed commit", @@ -30,7 +30,6 @@ def parse_args(): type=str, default=DefaultParameters.search_paths, nargs="*", - action="extend", help="list of search directory paths", ) parser.add_argument( @@ -39,7 +38,6 @@ def parse_args(): type=str, default=DefaultParameters.file_patterns, nargs="*", - action="extend", help="list of file patterns to include", ) parser.add_argument( @@ -48,17 +46,22 @@ def parse_args(): type=str, default=DefaultParameters.exclude_paths, nargs="*", - action="extend", help="list of directory paths to exclude", ) parser.add_argument( "-c", "--check", - type=bool, default=DefaultParameters.check, action=argparse.BooleanOptionalAction, help="run format check", ) + parser.add_argument( + "-exe", + "--clang-format-executable", + type=str, + default=DefaultParameters.clang_format_executable, + help="path or name of the clang-format executable (default: clang-format)", + ) return vars(parser.parse_args()) @@ -81,7 +84,7 @@ def get_modified_files(files: set[Path]) -> set[Path]: raise RuntimeError("Failed to retrieve the modified files.") -def run_clang_format(files: set[Path], check: bool) -> int: +def run_clang_format(clang_format_exec: str, files: set[Path], check: bool) -> int: n_files = len(files) if check: print(f"Files to check: {n_files}") @@ -92,7 +95,7 @@ def run_clang_format(files: set[Path], check: bool) -> int: for i, file in enumerate(files): print(f"[{i + 1}/{n_files}] {file}") - cmd = ["clang-format-18", str(file)] + cmd = [clang_format_exec, str(file)] if check: cmd.extend(["--dry-run", "--Werror"]) else: @@ -114,12 +117,13 @@ def main( file_patterns: list[str], exclude_paths: list[str], check: bool, + clang_format_executable: str, ): files_to_format = find_files(search_paths, file_patterns, exclude_paths) if modified_files: files_to_format = get_modified_files(files_to_format) - sys.exit(run_clang_format(files_to_format, check)) + sys.exit(run_clang_format(clang_format_executable, files_to_format, check)) if __name__ == "__main__": From 8fd55e53f0be685180839e9a41bca757896e4ad1 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Mon, 29 Dec 2025 18:58:20 +0100 Subject: [PATCH 4/8] wip: bf-directed incidence list tests --- include/hgl/impl/incidence_list.hpp | 49 +- tests/source/hgl/test_incidence_list.cpp | 692 +++++++++++++++++++++++ 2 files changed, 724 insertions(+), 17 deletions(-) diff --git a/include/hgl/impl/incidence_list.hpp b/include/hgl/impl/incidence_list.hpp index 0d02351..71af472 100644 --- a/include/hgl/impl/incidence_list.hpp +++ b/include/hgl/impl/incidence_list.hpp @@ -56,12 +56,14 @@ class incidence_list final { this->_remove(vertex_id); } - [[nodiscard]] gl_attr_force_inline auto incident_hyperedges(const types::id_type vertex_id + [[nodiscard]] gl_attr_force_inline auto incident_hyperedges( + const types::id_type vertex_id ) const noexcept { return this->_incident_with(vertex_id); } - [[nodiscard]] gl_attr_force_inline types::size_type degree(const types::id_type vertex_id + [[nodiscard]] gl_attr_force_inline types::size_type degree( + const types::id_type vertex_id ) const noexcept { return this->_size(vertex_id); } @@ -77,7 +79,8 @@ class incidence_list final { this->_remove(hyperedge_id); } - [[nodiscard]] gl_attr_force_inline auto incident_vertices(const types::id_type hyperedge_id + [[nodiscard]] gl_attr_force_inline auto incident_vertices( + const types::id_type hyperedge_id ) const noexcept { return this->_incident_with(hyperedge_id); } @@ -217,7 +220,8 @@ class incidence_list final { // --- vertex methods : incidence queries --- - [[nodiscard]] gl_attr_force_inline auto incident_hyperedges(const types::id_type vertex_id + [[nodiscard]] gl_attr_force_inline auto incident_hyperedges( + const types::id_type vertex_id ) const noexcept { return this->_get(vertex_id); } @@ -226,7 +230,8 @@ class incidence_list final { return this->_size(vertex_id); } - [[nodiscard]] gl_attr_force_inline auto outgoing_hyperedges(const types::id_type vertex_id + [[nodiscard]] gl_attr_force_inline auto outgoing_hyperedges( + const types::id_type vertex_id ) const noexcept { return this->_get_tail(vertex_id); } @@ -235,7 +240,8 @@ class incidence_list final { return this->_tail_size(vertex_id); } - [[nodiscard]] gl_attr_force_inline auto incoming_hyperedges(const types::id_type vertex_id + [[nodiscard]] gl_attr_force_inline auto incoming_hyperedges( + const types::id_type vertex_id ) const noexcept { return this->_get_head(vertex_id); } @@ -256,17 +262,18 @@ class incidence_list final { // --- hyperedge methods : incidence queries --- - [[nodiscard]] gl_attr_force_inline auto incident_vertices(const types::id_type hyperedge_id + [[nodiscard]] gl_attr_force_inline auto incident_vertices( + const types::id_type hyperedge_id ) const noexcept { return this->_get(hyperedge_id); } - [[nodiscard]] types::size_type hyperedge_size(const types::id_type hyperedge_id - ) const noexcept { + [[nodiscard]] types::size_type hyperedge_size(const types::id_type hyperedge_id) const noexcept { return this->_size(hyperedge_id); } - [[nodiscard]] gl_attr_force_inline auto tail_vertices(const types::id_type hyperedge_id + [[nodiscard]] gl_attr_force_inline auto tail_vertices( + const types::id_type hyperedge_id ) const noexcept { return this->_get_tail(hyperedge_id); } @@ -275,7 +282,8 @@ class incidence_list final { return this->_tail_size(hyperedge_id); } - [[nodiscard]] gl_attr_force_inline auto head_vertices(const types::id_type hyperedge_id + [[nodiscard]] gl_attr_force_inline auto head_vertices( + const types::id_type hyperedge_id ) const noexcept { return this->_get_head(hyperedge_id); } @@ -390,7 +398,10 @@ class incidence_list final { [[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]; - return std::views::join(entry.tail, entry.head); + // 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()) @@ -427,7 +438,8 @@ class incidence_list final { } template - [[nodiscard]] gl_attr_force_inline types::size_type _size(const types::id_type id + [[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]; @@ -443,7 +455,8 @@ class incidence_list final { } template - [[nodiscard]] gl_attr_force_inline types::size_type _tail_size(const types::id_type id + [[nodiscard]] gl_attr_force_inline types::size_type _tail_size( + const types::id_type id ) const noexcept { if constexpr (Element == layout_tag::major_element) { // size major return this->_major_storage[id].tail.size(); @@ -458,7 +471,8 @@ class incidence_list final { } template - [[nodiscard]] gl_attr_force_inline types::size_type _head_size(const types::id_type id + [[nodiscard]] gl_attr_force_inline types::size_type _head_size( + const types::id_type id ) const noexcept { if constexpr (Element == layout_tag::major_element) { // size major return this->_major_storage[id].head.size(); @@ -484,8 +498,9 @@ class incidence_list final { return this->_contains(major_el.tail, id) or this->_contains(major_el.head, id); } - [[nodiscard]] bool _contains(const minor_storage_type& minor_storage, const types::id_type id) - const noexcept { + [[nodiscard]] bool _contains( + const minor_storage_type& minor_storage, const types::id_type id + ) const noexcept { const auto minor_it = std::ranges::lower_bound(minor_storage, id); return minor_it != minor_storage.end() and *minor_it == id; } diff --git a/tests/source/hgl/test_incidence_list.cpp b/tests/source/hgl/test_incidence_list.cpp index 9c72dda..2ca0fd7 100644 --- a/tests/source/hgl/test_incidence_list.cpp +++ b/tests/source/hgl/test_incidence_list.cpp @@ -473,6 +473,698 @@ TEST_CASE_FIXTURE( CHECK_EQ(sut.degree(vertex_id), constants::n_hyperedges); } +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 + ) { + std::vector tail_bound, head_bound; + + for (std::size_t i = 0uz; i < n_hyperedges; ++i) { + if (i % 2 == 0) { + sut.bind_tail(vertex_id, i); + tail_bound.push_back(i); + } + else { + sut.bind_head(vertex_id, i); + head_bound.push_back(i); + } + } + + return std::make_pair(std::move(tail_bound), std::move(head_bound)); + } + + auto altbind_to_hyperedge( + auto& sut, const hgl::types::id_type hyperedge_id, const hgl::types::size_type n_vertices + ) { + std::vector tail_bound, head_bound; + + for (std::size_t i = 0uz; i < n_vertices; ++i) { + if (i % 2 == 0) { + sut.bind_tail(i, hyperedge_id); + tail_bound.push_back(i); + } + else { + sut.bind_head(i, hyperedge_id); + head_bound.push_back(i); + } + } + + return std::make_pair(std::move(tail_bound), std::move(head_bound)); + } +}; + +constexpr auto is_empty_entry = [](const auto& entry) { + return entry.tail.size() == 0uz and entry.head.size() == 0uz; +}; + +struct test_bf_directed_vertex_major_incidence_list : public test_bf_directed_incidence_list { + using sut_type = hgl::impl::incidence_list; +}; + +TEST_CASE_FIXTURE( + test_bf_directed_vertex_major_incidence_list, "should initialize an empty list by default" +) { + sut_type sut{}; + CHECK(storage(sut).empty()); +} + +TEST_CASE_FIXTURE( + test_bf_directed_vertex_major_incidence_list, + "initialization with size parameters should properly initialize the list" +) { + sut_type sut(constants::n_vertices, constants::n_hyperedges); + CHECK_EQ(storage(sut).size(), constants::n_vertices); + CHECK(std::ranges::all_of(storage(sut), is_empty_entry)); +} + +TEST_CASE_FIXTURE( + test_bf_directed_vertex_major_incidence_list, "add_vertices should properly extend the list" +) { + sut_type sut{constants::n_vertices, constants::n_hyperedges}; + const auto initial_size = storage(sut).size(); + + sut.add_vertices(2uz); + + CHECK_EQ(storage(sut).size(), initial_size + 2uz); + CHECK(std::ranges::all_of(storage(sut), is_empty_entry)); +} + +TEST_CASE_FIXTURE( + test_bf_directed_vertex_major_incidence_list, + "remove_vertex should properly remove the major entry and implicitly shift vertex IDs" +) { + constexpr hgl::types::size_type n_vertices = 5uz, n_hyperedges = 1uz; + constexpr hgl::types::id_type hyperedge_id = constants::id1; + + sut_type sut{n_vertices, n_hyperedges}; + sut.bind_tail(constants::id2, hyperedge_id); + sut.bind_head(constants::id4, hyperedge_id); + + hgl::types::id_type rem_vid; + hgl::types::size_type expected_hyperedge_size; + std::vector expected_vertices; + + SUBCASE("not present vertex < first incident vertex") { + rem_vid = constants::id1; + expected_hyperedge_size = 2uz; + expected_vertices = {constants::id2 - 1uz, constants::id4 - 1uz}; + } + + SUBCASE("present vertex = first incident vertex") { + rem_vid = constants::id2; + expected_hyperedge_size = 1uz; + expected_vertices = {constants::id4 - 1uz}; + } + + SUBCASE("not present vertex > first incident vertex") { + rem_vid = constants::id3; + expected_hyperedge_size = 2uz; + expected_vertices = {constants::id2, constants::id4 - 1uz}; + } + + SUBCASE("present vertex = last incident vertex") { + rem_vid = constants::id4; + expected_hyperedge_size = 1uz; + expected_vertices = {constants::id2}; + } + + SUBCASE("not present vertex > last incident vertex") { + rem_vid = constants::id4 + 1uz; + expected_hyperedge_size = 2uz; + expected_vertices = {constants::id2, constants::id4}; + } + + CAPTURE(rem_vid); + CAPTURE(expected_hyperedge_size); + CAPTURE(expected_vertices); + + sut.remove_vertex(rem_vid); + CHECK_EQ(storage(sut).size(), n_vertices - 1uz); + CHECK_EQ(sut.hyperedge_size(hyperedge_id), expected_hyperedge_size); + CHECK(std::ranges::equal(sut.incident_vertices(hyperedge_id), expected_vertices)); +} + +TEST_CASE_FIXTURE( + test_bf_directed_vertex_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))" +) { + sut_type sut{constants::n_vertices, constants::n_hyperedges}; + + constexpr auto vertex_id = constants::id1; + REQUIRE(std::ranges::empty(sut.incident_hyperedges(vertex_id))); + + const auto [tail_bound_hyperedges, head_bound_hyperedges] = + altbind_to_vertex(sut, vertex_id, constants::n_hyperedges); + + CHECK( + std::ranges::is_permutation( + sut.incident_hyperedges(vertex_id), constants::hyperedge_ids_view + ) + ); + CHECK(std::ranges::equal(sut.outgoing_hyperedges(vertex_id), tail_bound_hyperedges)); + CHECK(std::ranges::equal(sut.incoming_hyperedges(vertex_id), head_bound_hyperedges)); +} + +TEST_CASE_FIXTURE( + test_bf_directed_vertex_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))" +) { + sut_type sut{constants::n_vertices, constants::n_hyperedges}; + + constexpr auto vertex_id = constants::id1; + REQUIRE_EQ(sut.degree(vertex_id), 0uz); + + const auto [tail_bound_hyperedges, head_bound_hyperedges] = + altbind_to_vertex(sut, vertex_id, constants::n_hyperedges); + + CHECK_EQ(sut.degree(vertex_id), constants::n_hyperedges); + CHECK_EQ(sut.out_degree(vertex_id), tail_bound_hyperedges.size()); + CHECK_EQ(sut.in_degree(vertex_id), head_bound_hyperedges.size()); +} + +TEST_CASE_FIXTURE(test_bf_directed_vertex_major_incidence_list, "add_hyperedges should do nothing") { + sut_type sut{constants::n_vertices, 0uz}; + REQUIRE_EQ(storage(sut).size(), constants::n_vertices); + REQUIRE(std::ranges::all_of(storage(sut), is_empty_entry)); + + sut.add_hyperedges(constants::n_hyperedges); + CHECK(std::ranges::all_of(storage(sut), is_empty_entry)); +} + +TEST_CASE_FIXTURE( + test_bf_directed_vertex_major_incidence_list, + "remove_hyperedge should properly remove the minor entries and shift the hyperedge IDs" +) { + constexpr hgl::types::size_type n_vertices = 1uz, n_hyperedges = 5uz; + constexpr hgl::types::id_type vertex_id = constants::id1; + + sut_type sut{n_vertices, n_hyperedges}; + sut.bind_tail(vertex_id, constants::id2); + sut.bind_head(vertex_id, constants::id4); + + hgl::types::id_type rem_eid; + hgl::types::size_type expected_vertex_degree; + std::vector expected_hyperedges; + + SUBCASE("not present hyperedge < first incident hyperedge") { + rem_eid = constants::id1; + expected_vertex_degree = 2uz; + expected_hyperedges = {constants::id2 - 1uz, constants::id4 - 1uz}; + } + + SUBCASE("present hyperedge = first incident hyperedge") { + rem_eid = constants::id2; + expected_vertex_degree = 1uz; + expected_hyperedges = {constants::id4 - 1uz}; + } + + SUBCASE("not present hyperedge > first incident hyperedge") { + rem_eid = constants::id3; + expected_vertex_degree = 2uz; + expected_hyperedges = {constants::id2, constants::id4 - 1uz}; + } + + SUBCASE("present hyperedge = last incident hyperedge") { + rem_eid = constants::id4; + expected_vertex_degree = 1uz; + expected_hyperedges = {constants::id2}; + } + + SUBCASE("not present hyperedge > last incident hyperedge") { + rem_eid = constants::id4 + 1uz; + expected_vertex_degree = 2uz; + expected_hyperedges = {constants::id2, constants::id4}; + } + + CAPTURE(rem_eid); + CAPTURE(expected_vertex_degree); + CAPTURE(expected_hyperedges); + + sut.remove_hyperedge(rem_eid); + CHECK_EQ(sut.degree(vertex_id), expected_vertex_degree); + CHECK(std::ranges::equal(sut.incident_hyperedges(vertex_id), expected_hyperedges)); +} + +TEST_CASE_FIXTURE( + test_bf_directed_vertex_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)" +) { + sut_type sut{constants::n_vertices, constants::n_hyperedges}; + + constexpr auto hyperedge_id = constants::id1; + REQUIRE(std::ranges::empty(sut.incident_vertices(hyperedge_id))); + + 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::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, + "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)|" +) { + sut_type sut{constants::n_vertices, constants::n_hyperedges}; + + constexpr auto hyperedge_id = constants::id1; + REQUIRE_EQ(sut.hyperedge_size(hyperedge_id), 0uz); + + const auto [tail_bound_vertices, head_bound_vertices] = + altbind_to_hyperedge(sut, hyperedge_id, constants::n_vertices); + + CHECK_EQ(sut.hyperedge_size(hyperedge_id), constants::n_vertices); + CHECK_EQ(sut.tail_size(hyperedge_id), tail_bound_vertices.size()); + CHECK_EQ(sut.head_size(hyperedge_id), head_bound_vertices.size()); +} + +// TEST_CASE_FIXTURE( +// test_bf_directed_vertex_major_incidence_list, +// "bind_tail should set the corresponding matrix entry to backward incidence" +// ) { +// sut_type sut{constants::n_vertices, constants::n_hyperedges}; +// REQUIRE(std::ranges::empty(sut.incident_vertices(constants::id1))); + +// sut.bind_tail(constants::id1, constants::id1); + +// CHECK_EQ(matrix(sut)[constants::id1][constants::id1], incidence_type::backward); + +// const auto vertices = sut.tail_vertices(constants::id1) | std::ranges::to(); +// CHECK_EQ(sut.tail_size(constants::id1), 1uz); +// CHECK_EQ(std::ranges::size(vertices), 1uz); +// CHECK(std::ranges::contains(vertices, constants::id1)); +// } + +// TEST_CASE_FIXTURE( +// test_bf_directed_vertex_major_incidence_list, +// "bind_head should set the corresponding matrix entry to forward incidence" +// ) { +// sut_type sut{constants::n_vertices, constants::n_hyperedges}; +// REQUIRE(std::ranges::empty(sut.incident_vertices(constants::id1))); + +// sut.bind_head(constants::id1, constants::id1); + +// CHECK_EQ(matrix(sut)[constants::id1][constants::id1], incidence_type::forward); + +// const auto vertices = sut.head_vertices(constants::id1) | std::ranges::to(); +// CHECK_EQ(sut.head_size(constants::id1), 1uz); +// CHECK_EQ(std::ranges::size(vertices), 1uz); +// CHECK(std::ranges::contains(vertices, constants::id1)); +// } + +// TEST_CASE_FIXTURE( +// test_bf_directed_vertex_major_incidence_list, "unbind should clear the corresponding bit" +// ) { +// sut_type sut{constants::n_vertices, constants::n_hyperedges}; + +// SUBCASE("tail bound") { +// sut.bind_tail(constants::id1, constants::id1); +// } +// SUBCASE("head bound") { +// sut.bind_head(constants::id1, constants::id1); +// } +// CAPTURE(sut); + +// REQUIRE_EQ(sut.hyperedge_size(constants::id1), 1uz); +// REQUIRE_NE(matrix(sut)[constants::id1][constants::id1], incidence_type::none); + +// sut.unbind(constants::id1, constants::id2); +// CHECK_EQ(sut.hyperedge_size(constants::id1), 1uz); + +// sut.unbind(constants::id1, constants::id1); +// CHECK(std::ranges::empty(sut.incident_vertices(constants::id1))); +// CHECK_EQ(matrix(sut)[constants::id1][constants::id1], incidence_type::none); +// } + +// TEST_CASE_FIXTURE( +// test_bf_directed_vertex_major_incidence_list, +// "are_bound, is_tail, is_head should return true only when the corresponding matrix entry is " +// "set to a valid, matching incidence type" +// ) { +// sut_type sut{constants::n_vertices, constants::n_hyperedges}; + +// sut.bind_tail(constants::id1, constants::id1); +// sut.bind_head(constants::id2, constants::id1); + +// CHECK(sut.are_bound(constants::id1, constants::id1)); +// CHECK(sut.is_tail(constants::id1, constants::id1)); +// CHECK_FALSE(sut.is_head(constants::id1, constants::id1)); + +// CHECK(sut.are_bound(constants::id2, constants::id1)); +// CHECK_FALSE(sut.is_tail(constants::id2, constants::id1)); +// CHECK(sut.is_head(constants::id2, constants::id1)); + +// CHECK_FALSE(sut.are_bound(constants::id3, constants::id1)); +// CHECK_FALSE(sut.is_tail(constants::id3, constants::id1)); +// CHECK_FALSE(sut.is_head(constants::id3, constants::id1)); +// } + +// struct test_bf_directed_hyperedge_major_incidence_matrix +// : public test_bf_directed_incidence_list { +// using sut_type = hgl::impl::incidence_matrix; +// using incidence_type = incidence_descriptor_type; +// }; + +// TEST_CASE_FIXTURE( +// test_bf_directed_hyperedge_major_incidence_matrix, "should initialize empty matrix by default" +// ) { +// sut_type sut{}; +// CHECK(matrix(sut).empty()); +// } + +// TEST_CASE_FIXTURE( +// test_bf_directed_hyperedge_major_incidence_matrix, +// "initialization with size parameters should properly initialize the matrix" +// ) { +// sut_type sut(constants::n_vertices, constants::n_hyperedges); +// CHECK_EQ(matrix(sut).size(), constants::n_hyperedges); +// CHECK(std::ranges::all_of(matrix(sut), [](const auto& row) { +// return row.size() == constants::n_vertices; +// })); +// CHECK(std::ranges::all_of(matrix(sut), [this](const auto& row) { +// return std::ranges::none_of(row, is_incident_pred()); +// })); +// } + +// TEST_CASE_FIXTURE( +// test_bf_directed_hyperedge_major_incidence_matrix, +// "add_vertices should properly extend the hypergraph matrix (resize columns)" +// ) { +// sut_type sut{0uz, constants::n_hyperedges}; +// REQUIRE_EQ(matrix(sut).size(), constants::n_hyperedges); +// REQUIRE(std::ranges::all_of(matrix(sut), [](const auto& row) { return row.size() == 0uz; })); + +// sut.add_vertices(constants::n_vertices); +// CHECK(std::ranges::all_of(matrix(sut), [](const auto& row) { +// return row.size() == constants::n_vertices; +// })); +// } + +// TEST_CASE_FIXTURE( +// test_bf_directed_hyperedge_major_incidence_matrix, +// "remove_vertex should properly erase the proper vertex matrix entries (column)" +// ) { +// sut_type sut{constants::n_vertices, constants::n_hyperedges}; + +// sut.remove_vertex(constants::id1); + +// CHECK_EQ(matrix(sut).size(), constants::n_hyperedges); +// CHECK(std::ranges::all_of(matrix(sut), [](const auto& row) { +// return row.size() == constants::n_vertices - 1uz; +// })); +// } + +// TEST_CASE_FIXTURE( +// test_bf_directed_hyperedge_major_incidence_matrix, +// "remove_vertex should properly remove the column and implicitly shift vertex IDs" +// ) { +// constexpr hgl::types::size_type n_vertices = 5uz, n_hyperedges = 1uz; +// constexpr hgl::types::id_type hyperedge_id = constants::id1; + +// sut_type sut{n_vertices, n_hyperedges}; +// sut.bind_tail(constants::id2, hyperedge_id); +// sut.bind_head(constants::id4, hyperedge_id); + +// hgl::types::id_type rem_vid; +// hgl::types::size_type expected_hyperedge_size; +// std::vector expected_vertices; + +// SUBCASE("not present vertex < first incident vertex") { +// rem_vid = constants::id1; +// expected_hyperedge_size = 2uz; +// expected_vertices = {constants::id2 - 1uz, constants::id4 - 1uz}; +// } + +// SUBCASE("present vertex = first incident vertex") { +// rem_vid = constants::id2; +// expected_hyperedge_size = 1uz; +// expected_vertices = {constants::id4 - 1uz}; +// } + +// SUBCASE("not present vertex > first incident vertex") { +// rem_vid = constants::id3; +// expected_hyperedge_size = 2uz; +// expected_vertices = {constants::id2, constants::id4 - 1uz}; +// } + +// SUBCASE("present vertex = last incident vertex") { +// rem_vid = constants::id4; +// expected_hyperedge_size = 1uz; +// expected_vertices = {constants::id2}; +// } + +// SUBCASE("not present vertex > last incident vertex") { +// rem_vid = constants::id4 + 1uz; +// expected_hyperedge_size = 2uz; +// expected_vertices = {constants::id2, constants::id4}; +// } + +// CAPTURE(rem_vid); +// CAPTURE(expected_hyperedge_size); +// CAPTURE(expected_vertices); + +// sut.remove_vertex(rem_vid); +// CHECK_EQ(matrix(sut)[hyperedge_id].size(), n_vertices - 1uz); +// CHECK_EQ(sut.hyperedge_size(hyperedge_id), expected_hyperedge_size); +// CHECK(std::ranges::equal(sut.incident_vertices(hyperedge_id), expected_vertices)); +// } + +// TEST_CASE_FIXTURE( +// test_bf_directed_vertex_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))" +// ) { +// sut_type sut{constants::n_vertices, constants::n_hyperedges}; + +// constexpr auto vertex_id = constants::id1; +// REQUIRE(std::ranges::empty(sut.incident_hyperedges(vertex_id))); + +// const auto [tail_bound_hyperedges, head_bound_hyperedges] = +// altbind_to_vertex(sut, vertex_id, constants::n_hyperedges); + +// CHECK(std::ranges::equal(sut.incident_hyperedges(vertex_id), constants::hyperedge_ids_view)); +// CHECK(std::ranges::equal(sut.outgoing_hyperedges(vertex_id), tail_bound_hyperedges)); +// CHECK(std::ranges::equal(sut.incoming_hyperedges(vertex_id), head_bound_hyperedges)); +// } + +// TEST_CASE_FIXTURE( +// test_bf_directed_vertex_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))" +// ) { +// sut_type sut{constants::n_vertices, constants::n_hyperedges}; + +// constexpr auto vertex_id = constants::id1; +// REQUIRE_EQ(sut.degree(vertex_id), 0uz); + +// const auto [tail_bound_hyperedges, head_bound_hyperedges] = +// altbind_to_vertex(sut, vertex_id, constants::n_hyperedges); + +// CHECK_EQ(sut.degree(vertex_id), constants::n_hyperedges); +// CHECK_EQ(sut.out_degree(vertex_id), tail_bound_hyperedges.size()); +// CHECK_EQ(sut.in_degree(vertex_id), head_bound_hyperedges.size()); +// } + +// TEST_CASE_FIXTURE( +// test_bf_directed_hyperedge_major_incidence_matrix, +// "add_hyperedges should properly extend matrix" +// ) { +// sut_type sut{constants::n_vertices, constants::n_hyperedges}; +// const auto initial_size = matrix(sut).size(); + +// sut.add_hyperedges(2uz); + +// CHECK_EQ(matrix(sut).size(), initial_size + 2uz); +// CHECK(std::ranges::all_of(matrix(sut), [](const auto& row) { +// return row.size() == constants::n_vertices; +// })); +// } + +// TEST_CASE_FIXTURE( +// test_bf_directed_hyperedge_major_incidence_matrix, +// "remove_hyperedge should properly remove the row and implicitly shift hyperedge IDs" +// ) { +// constexpr hgl::types::size_type n_vertices = 1uz, n_hyperedges = 5uz; +// constexpr hgl::types::id_type vertex_id = constants::id1; + +// sut_type sut{n_vertices, n_hyperedges}; +// sut.bind_tail(vertex_id, constants::id2); +// sut.bind_head(vertex_id, constants::id4); + +// hgl::types::id_type rem_eid; +// hgl::types::size_type expected_vertex_degree; +// std::vector expected_hyperedges; + +// SUBCASE("not present hyperedge < first incident hyperedge") { +// rem_eid = constants::id1; +// expected_vertex_degree = 2uz; +// expected_hyperedges = {constants::id2 - 1uz, constants::id4 - 1uz}; +// } + +// SUBCASE("present hyperedge = first incident hyperedge") { +// rem_eid = constants::id2; +// expected_vertex_degree = 1uz; +// expected_hyperedges = {constants::id4 - 1uz}; +// } + +// SUBCASE("not present hyperedge > first incident hyperedge") { +// rem_eid = constants::id3; +// expected_vertex_degree = 2uz; +// expected_hyperedges = {constants::id2, constants::id4 - 1uz}; +// } + +// SUBCASE("present hyperedge = last incident hyperedge") { +// rem_eid = constants::id4; +// expected_vertex_degree = 1uz; +// expected_hyperedges = {constants::id2}; +// } + +// SUBCASE("not present hyperedge > last incident hyperedge") { +// rem_eid = constants::id4 + 1uz; +// expected_vertex_degree = 2uz; +// expected_hyperedges = {constants::id2, constants::id4}; +// } + +// CAPTURE(rem_eid); +// CAPTURE(expected_vertex_degree); +// CAPTURE(expected_hyperedges); + +// sut.remove_hyperedge(rem_eid); +// CHECK_EQ(matrix(sut).size(), n_hyperedges - 1uz); +// CHECK_EQ(sut.degree(vertex_id), expected_vertex_degree); +// CHECK(std::ranges::equal(sut.incident_hyperedges(vertex_id), expected_hyperedges)); +// } + +// TEST_CASE_FIXTURE( +// test_bf_directed_vertex_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)" +// ) { +// sut_type sut{constants::n_vertices, constants::n_hyperedges}; + +// constexpr auto hyperedge_id = constants::id1; +// REQUIRE(std::ranges::empty(sut.incident_vertices(hyperedge_id))); + +// 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::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, +// "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)|" +// ) { +// sut_type sut{constants::n_vertices, constants::n_hyperedges}; + +// constexpr auto hyperedge_id = constants::id1; +// REQUIRE_EQ(sut.hyperedge_size(hyperedge_id), 0uz); + +// const auto [tail_bound_vertices, head_bound_vertices] = +// altbind_to_hyperedge(sut, hyperedge_id, constants::n_vertices); + +// CHECK_EQ(sut.hyperedge_size(hyperedge_id), constants::n_vertices); +// CHECK_EQ(sut.tail_size(hyperedge_id), tail_bound_vertices.size()); +// CHECK_EQ(sut.head_size(hyperedge_id), head_bound_vertices.size()); +// } + +// TEST_CASE_FIXTURE( +// test_bf_directed_vertex_major_incidence_list, +// "bind_tail should set the corresponding matrix entry to backward incidence" +// ) { +// sut_type sut{constants::n_vertices, constants::n_hyperedges}; +// REQUIRE(std::ranges::empty(sut.incident_vertices(constants::id1))); + +// sut.bind_tail(constants::id1, constants::id1); + +// CHECK_EQ(matrix(sut)[constants::id1][constants::id1], incidence_type::backward); + +// const auto vertices = sut.tail_vertices(constants::id1) | std::ranges::to(); +// CHECK_EQ(sut.tail_size(constants::id1), 1uz); +// CHECK_EQ(std::ranges::size(vertices), 1uz); +// CHECK(std::ranges::contains(vertices, constants::id1)); +// } + +// TEST_CASE_FIXTURE( +// test_bf_directed_vertex_major_incidence_list, +// "bind_head should set the corresponding matrix entry to forward incidence" +// ) { +// sut_type sut{constants::n_vertices, constants::n_hyperedges}; +// REQUIRE(std::ranges::empty(sut.incident_vertices(constants::id1))); + +// sut.bind_head(constants::id1, constants::id1); + +// CHECK_EQ(matrix(sut)[constants::id1][constants::id1], incidence_type::forward); + +// const auto vertices = sut.head_vertices(constants::id1) | std::ranges::to(); +// CHECK_EQ(sut.head_size(constants::id1), 1uz); +// CHECK_EQ(std::ranges::size(vertices), 1uz); +// CHECK(std::ranges::contains(vertices, constants::id1)); +// } + +// TEST_CASE_FIXTURE( +// test_bf_directed_vertex_major_incidence_list, "unbind should clear the corresponding bit" +// ) { +// sut_type sut{constants::n_vertices, constants::n_hyperedges}; + +// SUBCASE("tail bound") { +// sut.bind_tail(constants::id1, constants::id1); +// } +// SUBCASE("head bound") { +// sut.bind_head(constants::id1, constants::id1); +// } +// CAPTURE(sut); + +// REQUIRE_EQ(sut.hyperedge_size(constants::id1), 1uz); +// REQUIRE_NE(matrix(sut)[constants::id1][constants::id1], incidence_type::none); + +// sut.unbind(constants::id1, constants::id2); +// CHECK_EQ(sut.hyperedge_size(constants::id1), 1uz); + +// sut.unbind(constants::id1, constants::id1); +// CHECK(std::ranges::empty(sut.incident_vertices(constants::id1))); +// CHECK_EQ(matrix(sut)[constants::id1][constants::id1], incidence_type::none); +// } + +// TEST_CASE_FIXTURE( +// test_bf_directed_vertex_major_incidence_list, +// "are_bound, is_tail, is_head should return true only when the corresponding matrix entry is " +// "set to a valid, matching incidence type" +// ) { +// sut_type sut{constants::n_vertices, constants::n_hyperedges}; + +// sut.bind_tail(constants::id1, constants::id1); +// sut.bind_head(constants::id2, constants::id1); + +// CHECK(sut.are_bound(constants::id1, constants::id1)); +// CHECK(sut.is_tail(constants::id1, constants::id1)); +// CHECK_FALSE(sut.is_head(constants::id1, constants::id1)); + +// CHECK(sut.are_bound(constants::id2, constants::id1)); +// CHECK_FALSE(sut.is_tail(constants::id2, constants::id1)); +// CHECK(sut.is_head(constants::id2, constants::id1)); + +// CHECK_FALSE(sut.are_bound(constants::id3, constants::id1)); +// CHECK_FALSE(sut.is_tail(constants::id3, constants::id1)); +// CHECK_FALSE(sut.is_head(constants::id3, constants::id1)); +// } + TEST_SUITE_END(); // test_incidence_list } // namespace hgl_testing From 3dd379c4ea3a87ab03861d77cb86244899bb8696 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Mon, 29 Dec 2025 20:15:25 +0100 Subject: [PATCH 5/8] bf-directed vertex-major incidence list tests --- include/hgl/hypergraph.hpp | 6 +- include/hgl/impl/incidence_list.hpp | 2 + tests/source/hgl/test_incidence_list.cpp | 135 ++++++++++++----------- 3 files changed, 78 insertions(+), 65 deletions(-) diff --git a/include/hgl/hypergraph.hpp b/include/hgl/hypergraph.hpp index 659f866..d1225af 100644 --- a/include/hgl/hypergraph.hpp +++ b/include/hgl/hypergraph.hpp @@ -52,7 +52,8 @@ class hypergraph final { if constexpr (type_traits::c_non_empty_properties) { this->_hyperedge_properties.reserve(n_hyperedges); for (const auto _ : this->hyperedge_ids()) - this->_hyperedge_properties.push_back(std::make_unique() + this->_hyperedge_properties.push_back( + std::make_unique() ); } } @@ -257,7 +258,8 @@ class hypergraph final { const auto old_size = this->_hyperedge_properties.size(); this->_hyperedge_properties.reserve(this->_n_hyperedges); for (types::size_type i = old_size; i < this->_n_hyperedges; ++i) - this->_hyperedge_properties.push_back(std::make_unique() + this->_hyperedge_properties.push_back( + std::make_unique() ); } } diff --git a/include/hgl/impl/incidence_list.hpp b/include/hgl/impl/incidence_list.hpp index 71af472..a67684a 100644 --- a/include/hgl/impl/incidence_list.hpp +++ b/include/hgl/impl/incidence_list.hpp @@ -297,6 +297,7 @@ class incidence_list final { gl_attr_force_inline void bind_tail( const types::id_type vertex_id, const types::id_type hyperedge_id ) noexcept { + // TODO: validate if minor_id is not in head const auto [major_id, minor_id] = layout_tag::majmin(vertex_id, hyperedge_id); this->_unique_insert(this->_major_storage[major_id].tail, minor_id); } @@ -304,6 +305,7 @@ class incidence_list final { gl_attr_force_inline void bind_head( const types::id_type vertex_id, const types::id_type hyperedge_id ) noexcept { + // TODO: validate if minor_id is not in tail const auto [major_id, minor_id] = layout_tag::majmin(vertex_id, hyperedge_id); this->_unique_insert(this->_major_storage[major_id].head, minor_id); } diff --git a/tests/source/hgl/test_incidence_list.cpp b/tests/source/hgl/test_incidence_list.cpp index 2ca0fd7..cc422fb 100644 --- a/tests/source/hgl/test_incidence_list.cpp +++ b/tests/source/hgl/test_incidence_list.cpp @@ -747,86 +747,95 @@ TEST_CASE_FIXTURE( CHECK_EQ(sut.head_size(hyperedge_id), head_bound_vertices.size()); } -// TEST_CASE_FIXTURE( -// test_bf_directed_vertex_major_incidence_list, -// "bind_tail should set the corresponding matrix entry to backward incidence" -// ) { -// sut_type sut{constants::n_vertices, constants::n_hyperedges}; -// REQUIRE(std::ranges::empty(sut.incident_vertices(constants::id1))); +TEST_CASE_FIXTURE( + test_bf_directed_vertex_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}; + REQUIRE(std::ranges::empty(sut.incident_vertices(constants::id1))); -// sut.bind_tail(constants::id1, constants::id1); + sut.bind_tail(constants::id1, constants::id1); -// CHECK_EQ(matrix(sut)[constants::id1][constants::id1], incidence_type::backward); + REQUIRE_EQ(storage(sut)[constants::id1].tail.size(), 1uz); + REQUIRE_EQ(storage(sut)[constants::id1].head.size(), 0uz); + CHECK_EQ(storage(sut)[constants::id1].tail.front(), constants::id1); -// const auto vertices = sut.tail_vertices(constants::id1) | std::ranges::to(); -// CHECK_EQ(sut.tail_size(constants::id1), 1uz); -// CHECK_EQ(std::ranges::size(vertices), 1uz); -// CHECK(std::ranges::contains(vertices, constants::id1)); -// } + const auto vertices = sut.tail_vertices(constants::id1) | std::ranges::to(); + CHECK_EQ(sut.tail_size(constants::id1), 1uz); + CHECK_EQ(std::ranges::size(vertices), 1uz); + CHECK(std::ranges::contains(vertices, constants::id1)); +} -// TEST_CASE_FIXTURE( -// test_bf_directed_vertex_major_incidence_list, -// "bind_head should set the corresponding matrix entry to forward incidence" -// ) { -// sut_type sut{constants::n_vertices, constants::n_hyperedges}; -// REQUIRE(std::ranges::empty(sut.incident_vertices(constants::id1))); +TEST_CASE_FIXTURE( + test_bf_directed_vertex_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}; + REQUIRE(std::ranges::empty(sut.incident_vertices(constants::id1))); -// sut.bind_head(constants::id1, constants::id1); + sut.bind_head(constants::id1, constants::id1); -// CHECK_EQ(matrix(sut)[constants::id1][constants::id1], incidence_type::forward); + REQUIRE_EQ(storage(sut)[constants::id1].head.size(), 1uz); + REQUIRE_EQ(storage(sut)[constants::id1].tail.size(), 0uz); + CHECK_EQ(storage(sut)[constants::id1].head.front(), constants::id1); -// const auto vertices = sut.head_vertices(constants::id1) | std::ranges::to(); -// CHECK_EQ(sut.head_size(constants::id1), 1uz); -// CHECK_EQ(std::ranges::size(vertices), 1uz); -// CHECK(std::ranges::contains(vertices, constants::id1)); -// } + const auto vertices = sut.head_vertices(constants::id1) | std::ranges::to(); + CHECK_EQ(sut.head_size(constants::id1), 1uz); + CHECK_EQ(std::ranges::size(vertices), 1uz); + CHECK(std::ranges::contains(vertices, constants::id1)); +} -// TEST_CASE_FIXTURE( -// test_bf_directed_vertex_major_incidence_list, "unbind should clear the corresponding bit" -// ) { -// sut_type sut{constants::n_vertices, constants::n_hyperedges}; +TEST_CASE_FIXTURE( + test_bf_directed_vertex_major_incidence_list, + "unbind should erase the hyperedge id from a proper vertex entry" +) { + sut_type sut{constants::n_vertices, constants::n_hyperedges}; + std::vector expected_storage; -// SUBCASE("tail bound") { -// sut.bind_tail(constants::id1, constants::id1); -// } -// SUBCASE("head bound") { -// sut.bind_head(constants::id1, constants::id1); -// } -// CAPTURE(sut); + SUBCASE("tail bound") { + sut.bind_tail(constants::id1, constants::id1); + expected_storage = storage(sut)[constants::id1].tail; + } + SUBCASE("head bound") { + sut.bind_head(constants::id1, constants::id1); + expected_storage = storage(sut)[constants::id1].head; + } + CAPTURE(sut); + CAPTURE(expected_storage); -// REQUIRE_EQ(sut.hyperedge_size(constants::id1), 1uz); -// REQUIRE_NE(matrix(sut)[constants::id1][constants::id1], incidence_type::none); + REQUIRE_EQ(sut.hyperedge_size(constants::id1), 1uz); + REQUIRE_EQ(expected_storage.size(), 1uz); + REQUIRE_EQ(expected_storage.front(), constants::id1); -// sut.unbind(constants::id1, constants::id2); -// CHECK_EQ(sut.hyperedge_size(constants::id1), 1uz); + sut.unbind(constants::id1, constants::id2); + CHECK_EQ(sut.hyperedge_size(constants::id1), 1uz); -// sut.unbind(constants::id1, constants::id1); -// CHECK(std::ranges::empty(sut.incident_vertices(constants::id1))); -// CHECK_EQ(matrix(sut)[constants::id1][constants::id1], incidence_type::none); -// } + sut.unbind(constants::id1, constants::id1); + CHECK(std::ranges::empty(sut.incident_vertices(constants::id1))); +} -// TEST_CASE_FIXTURE( -// test_bf_directed_vertex_major_incidence_list, -// "are_bound, is_tail, is_head should return true only when the corresponding matrix entry is " -// "set to a valid, matching incidence type" -// ) { -// sut_type sut{constants::n_vertices, constants::n_hyperedges}; +TEST_CASE_FIXTURE( + test_bf_directed_vertex_major_incidence_list, + "are_bound, is_tail, is_head should return true only when the corresponding matrix entry is " + "set to a valid, matching incidence type" +) { + sut_type sut{constants::n_vertices, constants::n_hyperedges}; -// sut.bind_tail(constants::id1, constants::id1); -// sut.bind_head(constants::id2, constants::id1); + sut.bind_tail(constants::id1, constants::id1); + sut.bind_head(constants::id2, constants::id1); -// CHECK(sut.are_bound(constants::id1, constants::id1)); -// CHECK(sut.is_tail(constants::id1, constants::id1)); -// CHECK_FALSE(sut.is_head(constants::id1, constants::id1)); + CHECK(sut.are_bound(constants::id1, constants::id1)); + CHECK(sut.is_tail(constants::id1, constants::id1)); + CHECK_FALSE(sut.is_head(constants::id1, constants::id1)); -// CHECK(sut.are_bound(constants::id2, constants::id1)); -// CHECK_FALSE(sut.is_tail(constants::id2, constants::id1)); -// CHECK(sut.is_head(constants::id2, constants::id1)); + CHECK(sut.are_bound(constants::id2, constants::id1)); + CHECK_FALSE(sut.is_tail(constants::id2, constants::id1)); + CHECK(sut.is_head(constants::id2, constants::id1)); -// CHECK_FALSE(sut.are_bound(constants::id3, constants::id1)); -// CHECK_FALSE(sut.is_tail(constants::id3, constants::id1)); -// CHECK_FALSE(sut.is_head(constants::id3, constants::id1)); -// } + CHECK_FALSE(sut.are_bound(constants::id3, constants::id1)); + CHECK_FALSE(sut.is_tail(constants::id3, constants::id1)); + CHECK_FALSE(sut.is_head(constants::id3, constants::id1)); +} // struct test_bf_directed_hyperedge_major_incidence_matrix // : public test_bf_directed_incidence_list { From 69160512e6d612f7e38374597a9ea30a1c59ff65 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Mon, 29 Dec 2025 20:17:23 +0100 Subject: [PATCH 6/8] format --- include/hgl/hypergraph.hpp | 6 ++-- include/hgl/impl/incidence_list.hpp | 44 +++++++++--------------- tests/source/hgl/test_incidence_list.cpp | 8 ++--- 3 files changed, 21 insertions(+), 37 deletions(-) diff --git a/include/hgl/hypergraph.hpp b/include/hgl/hypergraph.hpp index d1225af..659f866 100644 --- a/include/hgl/hypergraph.hpp +++ b/include/hgl/hypergraph.hpp @@ -52,8 +52,7 @@ class hypergraph final { if constexpr (type_traits::c_non_empty_properties) { this->_hyperedge_properties.reserve(n_hyperedges); for (const auto _ : this->hyperedge_ids()) - this->_hyperedge_properties.push_back( - std::make_unique() + this->_hyperedge_properties.push_back(std::make_unique() ); } } @@ -258,8 +257,7 @@ class hypergraph final { const auto old_size = this->_hyperedge_properties.size(); this->_hyperedge_properties.reserve(this->_n_hyperedges); for (types::size_type i = old_size; i < this->_n_hyperedges; ++i) - this->_hyperedge_properties.push_back( - std::make_unique() + this->_hyperedge_properties.push_back(std::make_unique() ); } } diff --git a/include/hgl/impl/incidence_list.hpp b/include/hgl/impl/incidence_list.hpp index a67684a..f0b41cf 100644 --- a/include/hgl/impl/incidence_list.hpp +++ b/include/hgl/impl/incidence_list.hpp @@ -56,14 +56,12 @@ class incidence_list final { this->_remove(vertex_id); } - [[nodiscard]] gl_attr_force_inline auto incident_hyperedges( - const types::id_type vertex_id + [[nodiscard]] gl_attr_force_inline auto incident_hyperedges(const types::id_type vertex_id ) const noexcept { return this->_incident_with(vertex_id); } - [[nodiscard]] gl_attr_force_inline types::size_type degree( - const types::id_type vertex_id + [[nodiscard]] gl_attr_force_inline types::size_type degree(const types::id_type vertex_id ) const noexcept { return this->_size(vertex_id); } @@ -79,8 +77,7 @@ class incidence_list final { this->_remove(hyperedge_id); } - [[nodiscard]] gl_attr_force_inline auto incident_vertices( - const types::id_type hyperedge_id + [[nodiscard]] gl_attr_force_inline auto incident_vertices(const types::id_type hyperedge_id ) const noexcept { return this->_incident_with(hyperedge_id); } @@ -220,8 +217,7 @@ class incidence_list final { // --- vertex methods : incidence queries --- - [[nodiscard]] gl_attr_force_inline auto incident_hyperedges( - const types::id_type vertex_id + [[nodiscard]] gl_attr_force_inline auto incident_hyperedges(const types::id_type vertex_id ) const noexcept { return this->_get(vertex_id); } @@ -230,8 +226,7 @@ class incidence_list final { return this->_size(vertex_id); } - [[nodiscard]] gl_attr_force_inline auto outgoing_hyperedges( - const types::id_type vertex_id + [[nodiscard]] gl_attr_force_inline auto outgoing_hyperedges(const types::id_type vertex_id ) const noexcept { return this->_get_tail(vertex_id); } @@ -240,8 +235,7 @@ class incidence_list final { return this->_tail_size(vertex_id); } - [[nodiscard]] gl_attr_force_inline auto incoming_hyperedges( - const types::id_type vertex_id + [[nodiscard]] gl_attr_force_inline auto incoming_hyperedges(const types::id_type vertex_id ) const noexcept { return this->_get_head(vertex_id); } @@ -262,18 +256,17 @@ class incidence_list final { // --- hyperedge methods : incidence queries --- - [[nodiscard]] gl_attr_force_inline auto incident_vertices( - const types::id_type hyperedge_id + [[nodiscard]] gl_attr_force_inline auto incident_vertices(const types::id_type hyperedge_id ) const noexcept { return this->_get(hyperedge_id); } - [[nodiscard]] types::size_type hyperedge_size(const types::id_type hyperedge_id) const noexcept { + [[nodiscard]] types::size_type hyperedge_size(const types::id_type hyperedge_id + ) const noexcept { return this->_size(hyperedge_id); } - [[nodiscard]] gl_attr_force_inline auto tail_vertices( - const types::id_type hyperedge_id + [[nodiscard]] gl_attr_force_inline auto tail_vertices(const types::id_type hyperedge_id ) const noexcept { return this->_get_tail(hyperedge_id); } @@ -282,8 +275,7 @@ class incidence_list final { return this->_tail_size(hyperedge_id); } - [[nodiscard]] gl_attr_force_inline auto head_vertices( - const types::id_type hyperedge_id + [[nodiscard]] gl_attr_force_inline auto head_vertices(const types::id_type hyperedge_id ) const noexcept { return this->_get_head(hyperedge_id); } @@ -440,8 +432,7 @@ class incidence_list final { } template - [[nodiscard]] gl_attr_force_inline types::size_type _size( - const types::id_type id + [[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]; @@ -457,8 +448,7 @@ class incidence_list final { } template - [[nodiscard]] gl_attr_force_inline types::size_type _tail_size( - const types::id_type id + [[nodiscard]] gl_attr_force_inline types::size_type _tail_size(const types::id_type id ) const noexcept { if constexpr (Element == layout_tag::major_element) { // size major return this->_major_storage[id].tail.size(); @@ -473,8 +463,7 @@ class incidence_list final { } template - [[nodiscard]] gl_attr_force_inline types::size_type _head_size( - const types::id_type id + [[nodiscard]] gl_attr_force_inline types::size_type _head_size(const types::id_type id ) const noexcept { if constexpr (Element == layout_tag::major_element) { // size major return this->_major_storage[id].head.size(); @@ -500,9 +489,8 @@ class incidence_list final { return this->_contains(major_el.tail, id) or this->_contains(major_el.head, id); } - [[nodiscard]] bool _contains( - const minor_storage_type& minor_storage, const types::id_type id - ) const noexcept { + [[nodiscard]] bool _contains(const minor_storage_type& minor_storage, const types::id_type id) + const noexcept { const auto minor_it = std::ranges::lower_bound(minor_storage, id); return minor_it != minor_storage.end() and *minor_it == id; } diff --git a/tests/source/hgl/test_incidence_list.cpp b/tests/source/hgl/test_incidence_list.cpp index cc422fb..91cc0e6 100644 --- a/tests/source/hgl/test_incidence_list.cpp +++ b/tests/source/hgl/test_incidence_list.cpp @@ -618,11 +618,9 @@ TEST_CASE_FIXTURE( const auto [tail_bound_hyperedges, head_bound_hyperedges] = altbind_to_vertex(sut, vertex_id, constants::n_hyperedges); - CHECK( - std::ranges::is_permutation( - sut.incident_hyperedges(vertex_id), constants::hyperedge_ids_view - ) - ); + CHECK(std::ranges::is_permutation( + sut.incident_hyperedges(vertex_id), constants::hyperedge_ids_view + )); CHECK(std::ranges::equal(sut.outgoing_hyperedges(vertex_id), tail_bound_hyperedges)); CHECK(std::ranges::equal(sut.incoming_hyperedges(vertex_id), head_bound_hyperedges)); } From e229da372904fbdda0092cd71a2f8bc32e4cd490 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Mon, 29 Dec 2025 20:27:52 +0100 Subject: [PATCH 7/8] bf-directed vertex-major incidence list tests --- tests/source/hgl/test_incidence_list.cpp | 663 +++++++++++------------ 1 file changed, 323 insertions(+), 340 deletions(-) diff --git a/tests/source/hgl/test_incidence_list.cpp b/tests/source/hgl/test_incidence_list.cpp index 91cc0e6..fc10e9a 100644 --- a/tests/source/hgl/test_incidence_list.cpp +++ b/tests/source/hgl/test_incidence_list.cpp @@ -747,7 +747,7 @@ TEST_CASE_FIXTURE( TEST_CASE_FIXTURE( test_bf_directed_vertex_major_incidence_list, - "bind_tail should insert the hyperedge id to the tail list of an appropriate vertex entry" + "bind_tail should insert the vertex id to the tail list of an appropriate hyperedge entry" ) { sut_type sut{constants::n_vertices, constants::n_hyperedges}; REQUIRE(std::ranges::empty(sut.incident_vertices(constants::id1))); @@ -766,7 +766,7 @@ TEST_CASE_FIXTURE( TEST_CASE_FIXTURE( test_bf_directed_vertex_major_incidence_list, - "bind_head should insert the hyperedge id to the head list of an appropriate vertex entry" + "bind_head should insert the vertex id to the head list of an appropriate hyperedge entry" ) { sut_type sut{constants::n_vertices, constants::n_hyperedges}; REQUIRE(std::ranges::empty(sut.incident_vertices(constants::id1))); @@ -814,8 +814,8 @@ TEST_CASE_FIXTURE( TEST_CASE_FIXTURE( test_bf_directed_vertex_major_incidence_list, - "are_bound, is_tail, is_head should return true only when the corresponding matrix entry is " - "set to a valid, matching incidence type" + "are_bound, is_tail, is_head should return true only when the corresponding major list entries " + "contain the given minor ids" ) { sut_type sut{constants::n_vertices, constants::n_hyperedges}; @@ -835,342 +835,325 @@ TEST_CASE_FIXTURE( CHECK_FALSE(sut.is_head(constants::id3, constants::id1)); } -// struct test_bf_directed_hyperedge_major_incidence_matrix -// : public test_bf_directed_incidence_list { -// using sut_type = hgl::impl::incidence_matrix; -// using incidence_type = incidence_descriptor_type; -// }; - -// TEST_CASE_FIXTURE( -// test_bf_directed_hyperedge_major_incidence_matrix, "should initialize empty matrix by default" -// ) { -// sut_type sut{}; -// CHECK(matrix(sut).empty()); -// } - -// TEST_CASE_FIXTURE( -// test_bf_directed_hyperedge_major_incidence_matrix, -// "initialization with size parameters should properly initialize the matrix" -// ) { -// sut_type sut(constants::n_vertices, constants::n_hyperedges); -// CHECK_EQ(matrix(sut).size(), constants::n_hyperedges); -// CHECK(std::ranges::all_of(matrix(sut), [](const auto& row) { -// return row.size() == constants::n_vertices; -// })); -// CHECK(std::ranges::all_of(matrix(sut), [this](const auto& row) { -// return std::ranges::none_of(row, is_incident_pred()); -// })); -// } - -// TEST_CASE_FIXTURE( -// test_bf_directed_hyperedge_major_incidence_matrix, -// "add_vertices should properly extend the hypergraph matrix (resize columns)" -// ) { -// sut_type sut{0uz, constants::n_hyperedges}; -// REQUIRE_EQ(matrix(sut).size(), constants::n_hyperedges); -// REQUIRE(std::ranges::all_of(matrix(sut), [](const auto& row) { return row.size() == 0uz; })); - -// sut.add_vertices(constants::n_vertices); -// CHECK(std::ranges::all_of(matrix(sut), [](const auto& row) { -// return row.size() == constants::n_vertices; -// })); -// } - -// TEST_CASE_FIXTURE( -// test_bf_directed_hyperedge_major_incidence_matrix, -// "remove_vertex should properly erase the proper vertex matrix entries (column)" -// ) { -// sut_type sut{constants::n_vertices, constants::n_hyperedges}; - -// sut.remove_vertex(constants::id1); - -// CHECK_EQ(matrix(sut).size(), constants::n_hyperedges); -// CHECK(std::ranges::all_of(matrix(sut), [](const auto& row) { -// return row.size() == constants::n_vertices - 1uz; -// })); -// } - -// TEST_CASE_FIXTURE( -// test_bf_directed_hyperedge_major_incidence_matrix, -// "remove_vertex should properly remove the column and implicitly shift vertex IDs" -// ) { -// constexpr hgl::types::size_type n_vertices = 5uz, n_hyperedges = 1uz; -// constexpr hgl::types::id_type hyperedge_id = constants::id1; - -// sut_type sut{n_vertices, n_hyperedges}; -// sut.bind_tail(constants::id2, hyperedge_id); -// sut.bind_head(constants::id4, hyperedge_id); - -// hgl::types::id_type rem_vid; -// hgl::types::size_type expected_hyperedge_size; -// std::vector expected_vertices; - -// SUBCASE("not present vertex < first incident vertex") { -// rem_vid = constants::id1; -// expected_hyperedge_size = 2uz; -// expected_vertices = {constants::id2 - 1uz, constants::id4 - 1uz}; -// } - -// SUBCASE("present vertex = first incident vertex") { -// rem_vid = constants::id2; -// expected_hyperedge_size = 1uz; -// expected_vertices = {constants::id4 - 1uz}; -// } - -// SUBCASE("not present vertex > first incident vertex") { -// rem_vid = constants::id3; -// expected_hyperedge_size = 2uz; -// expected_vertices = {constants::id2, constants::id4 - 1uz}; -// } - -// SUBCASE("present vertex = last incident vertex") { -// rem_vid = constants::id4; -// expected_hyperedge_size = 1uz; -// expected_vertices = {constants::id2}; -// } - -// SUBCASE("not present vertex > last incident vertex") { -// rem_vid = constants::id4 + 1uz; -// expected_hyperedge_size = 2uz; -// expected_vertices = {constants::id2, constants::id4}; -// } - -// CAPTURE(rem_vid); -// CAPTURE(expected_hyperedge_size); -// CAPTURE(expected_vertices); - -// sut.remove_vertex(rem_vid); -// CHECK_EQ(matrix(sut)[hyperedge_id].size(), n_vertices - 1uz); -// CHECK_EQ(sut.hyperedge_size(hyperedge_id), expected_hyperedge_size); -// CHECK(std::ranges::equal(sut.incident_vertices(hyperedge_id), expected_vertices)); -// } - -// TEST_CASE_FIXTURE( -// test_bf_directed_vertex_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))" -// ) { -// sut_type sut{constants::n_vertices, constants::n_hyperedges}; - -// constexpr auto vertex_id = constants::id1; -// REQUIRE(std::ranges::empty(sut.incident_hyperedges(vertex_id))); - -// const auto [tail_bound_hyperedges, head_bound_hyperedges] = -// altbind_to_vertex(sut, vertex_id, constants::n_hyperedges); - -// CHECK(std::ranges::equal(sut.incident_hyperedges(vertex_id), constants::hyperedge_ids_view)); -// CHECK(std::ranges::equal(sut.outgoing_hyperedges(vertex_id), tail_bound_hyperedges)); -// CHECK(std::ranges::equal(sut.incoming_hyperedges(vertex_id), head_bound_hyperedges)); -// } - -// TEST_CASE_FIXTURE( -// test_bf_directed_vertex_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))" -// ) { -// sut_type sut{constants::n_vertices, constants::n_hyperedges}; - -// constexpr auto vertex_id = constants::id1; -// REQUIRE_EQ(sut.degree(vertex_id), 0uz); - -// const auto [tail_bound_hyperedges, head_bound_hyperedges] = -// altbind_to_vertex(sut, vertex_id, constants::n_hyperedges); - -// CHECK_EQ(sut.degree(vertex_id), constants::n_hyperedges); -// CHECK_EQ(sut.out_degree(vertex_id), tail_bound_hyperedges.size()); -// CHECK_EQ(sut.in_degree(vertex_id), head_bound_hyperedges.size()); -// } - -// TEST_CASE_FIXTURE( -// test_bf_directed_hyperedge_major_incidence_matrix, -// "add_hyperedges should properly extend matrix" -// ) { -// sut_type sut{constants::n_vertices, constants::n_hyperedges}; -// const auto initial_size = matrix(sut).size(); - -// sut.add_hyperedges(2uz); - -// CHECK_EQ(matrix(sut).size(), initial_size + 2uz); -// CHECK(std::ranges::all_of(matrix(sut), [](const auto& row) { -// return row.size() == constants::n_vertices; -// })); -// } - -// TEST_CASE_FIXTURE( -// test_bf_directed_hyperedge_major_incidence_matrix, -// "remove_hyperedge should properly remove the row and implicitly shift hyperedge IDs" -// ) { -// constexpr hgl::types::size_type n_vertices = 1uz, n_hyperedges = 5uz; -// constexpr hgl::types::id_type vertex_id = constants::id1; - -// sut_type sut{n_vertices, n_hyperedges}; -// sut.bind_tail(vertex_id, constants::id2); -// sut.bind_head(vertex_id, constants::id4); - -// hgl::types::id_type rem_eid; -// hgl::types::size_type expected_vertex_degree; -// std::vector expected_hyperedges; - -// SUBCASE("not present hyperedge < first incident hyperedge") { -// rem_eid = constants::id1; -// expected_vertex_degree = 2uz; -// expected_hyperedges = {constants::id2 - 1uz, constants::id4 - 1uz}; -// } - -// SUBCASE("present hyperedge = first incident hyperedge") { -// rem_eid = constants::id2; -// expected_vertex_degree = 1uz; -// expected_hyperedges = {constants::id4 - 1uz}; -// } - -// SUBCASE("not present hyperedge > first incident hyperedge") { -// rem_eid = constants::id3; -// expected_vertex_degree = 2uz; -// expected_hyperedges = {constants::id2, constants::id4 - 1uz}; -// } - -// SUBCASE("present hyperedge = last incident hyperedge") { -// rem_eid = constants::id4; -// expected_vertex_degree = 1uz; -// expected_hyperedges = {constants::id2}; -// } - -// SUBCASE("not present hyperedge > last incident hyperedge") { -// rem_eid = constants::id4 + 1uz; -// expected_vertex_degree = 2uz; -// expected_hyperedges = {constants::id2, constants::id4}; -// } - -// CAPTURE(rem_eid); -// CAPTURE(expected_vertex_degree); -// CAPTURE(expected_hyperedges); - -// sut.remove_hyperedge(rem_eid); -// CHECK_EQ(matrix(sut).size(), n_hyperedges - 1uz); -// CHECK_EQ(sut.degree(vertex_id), expected_vertex_degree); -// CHECK(std::ranges::equal(sut.incident_hyperedges(vertex_id), expected_hyperedges)); -// } - -// TEST_CASE_FIXTURE( -// test_bf_directed_vertex_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)" -// ) { -// sut_type sut{constants::n_vertices, constants::n_hyperedges}; - -// constexpr auto hyperedge_id = constants::id1; -// REQUIRE(std::ranges::empty(sut.incident_vertices(hyperedge_id))); - -// 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::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, -// "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)|" -// ) { -// sut_type sut{constants::n_vertices, constants::n_hyperedges}; - -// constexpr auto hyperedge_id = constants::id1; -// REQUIRE_EQ(sut.hyperedge_size(hyperedge_id), 0uz); - -// const auto [tail_bound_vertices, head_bound_vertices] = -// altbind_to_hyperedge(sut, hyperedge_id, constants::n_vertices); - -// CHECK_EQ(sut.hyperedge_size(hyperedge_id), constants::n_vertices); -// CHECK_EQ(sut.tail_size(hyperedge_id), tail_bound_vertices.size()); -// CHECK_EQ(sut.head_size(hyperedge_id), head_bound_vertices.size()); -// } - -// TEST_CASE_FIXTURE( -// test_bf_directed_vertex_major_incidence_list, -// "bind_tail should set the corresponding matrix entry to backward incidence" -// ) { -// sut_type sut{constants::n_vertices, constants::n_hyperedges}; -// REQUIRE(std::ranges::empty(sut.incident_vertices(constants::id1))); - -// sut.bind_tail(constants::id1, constants::id1); - -// CHECK_EQ(matrix(sut)[constants::id1][constants::id1], incidence_type::backward); - -// const auto vertices = sut.tail_vertices(constants::id1) | std::ranges::to(); -// CHECK_EQ(sut.tail_size(constants::id1), 1uz); -// CHECK_EQ(std::ranges::size(vertices), 1uz); -// CHECK(std::ranges::contains(vertices, constants::id1)); -// } - -// TEST_CASE_FIXTURE( -// test_bf_directed_vertex_major_incidence_list, -// "bind_head should set the corresponding matrix entry to forward incidence" -// ) { -// sut_type sut{constants::n_vertices, constants::n_hyperedges}; -// REQUIRE(std::ranges::empty(sut.incident_vertices(constants::id1))); - -// sut.bind_head(constants::id1, constants::id1); - -// CHECK_EQ(matrix(sut)[constants::id1][constants::id1], incidence_type::forward); - -// const auto vertices = sut.head_vertices(constants::id1) | std::ranges::to(); -// CHECK_EQ(sut.head_size(constants::id1), 1uz); -// CHECK_EQ(std::ranges::size(vertices), 1uz); -// CHECK(std::ranges::contains(vertices, constants::id1)); -// } - -// TEST_CASE_FIXTURE( -// test_bf_directed_vertex_major_incidence_list, "unbind should clear the corresponding bit" -// ) { -// sut_type sut{constants::n_vertices, constants::n_hyperedges}; - -// SUBCASE("tail bound") { -// sut.bind_tail(constants::id1, constants::id1); -// } -// SUBCASE("head bound") { -// sut.bind_head(constants::id1, constants::id1); -// } -// CAPTURE(sut); - -// REQUIRE_EQ(sut.hyperedge_size(constants::id1), 1uz); -// REQUIRE_NE(matrix(sut)[constants::id1][constants::id1], incidence_type::none); - -// sut.unbind(constants::id1, constants::id2); -// CHECK_EQ(sut.hyperedge_size(constants::id1), 1uz); - -// sut.unbind(constants::id1, constants::id1); -// CHECK(std::ranges::empty(sut.incident_vertices(constants::id1))); -// CHECK_EQ(matrix(sut)[constants::id1][constants::id1], incidence_type::none); -// } - -// TEST_CASE_FIXTURE( -// test_bf_directed_vertex_major_incidence_list, -// "are_bound, is_tail, is_head should return true only when the corresponding matrix entry is " -// "set to a valid, matching incidence type" -// ) { -// sut_type sut{constants::n_vertices, constants::n_hyperedges}; - -// sut.bind_tail(constants::id1, constants::id1); -// sut.bind_head(constants::id2, constants::id1); - -// CHECK(sut.are_bound(constants::id1, constants::id1)); -// CHECK(sut.is_tail(constants::id1, constants::id1)); -// CHECK_FALSE(sut.is_head(constants::id1, constants::id1)); - -// CHECK(sut.are_bound(constants::id2, constants::id1)); -// CHECK_FALSE(sut.is_tail(constants::id2, constants::id1)); -// CHECK(sut.is_head(constants::id2, constants::id1)); - -// CHECK_FALSE(sut.are_bound(constants::id3, constants::id1)); -// CHECK_FALSE(sut.is_tail(constants::id3, constants::id1)); -// CHECK_FALSE(sut.is_head(constants::id3, constants::id1)); -// } +struct test_bf_directed_hyperedge_major_incidence_list : public test_bf_directed_incidence_list { + using sut_type = hgl::impl::incidence_list; +}; + +TEST_CASE_FIXTURE( + test_bf_directed_hyperedge_major_incidence_list, "should initialize an empty list by default" +) { + sut_type sut{}; + CHECK(storage(sut).empty()); +} + +TEST_CASE_FIXTURE( + test_bf_directed_hyperedge_major_incidence_list, + "initialization with size parameters should properly initialize the matrix" +) { + sut_type sut(constants::n_vertices, constants::n_hyperedges); + CHECK_EQ(storage(sut).size(), constants::n_hyperedges); + CHECK(std::ranges::all_of(storage(sut), is_empty_entry)); +} + +TEST_CASE_FIXTURE( + test_bf_directed_hyperedge_major_incidence_list, "add_vertices should do nothing" +) { + sut_type sut{0uz, constants::n_hyperedges}; + REQUIRE_EQ(storage(sut).size(), constants::n_hyperedges); + REQUIRE(std::ranges::all_of(storage(sut), is_empty_entry)); + + sut.add_vertices(constants::n_vertices); + CHECK(std::ranges::all_of(storage(sut), is_empty_entry)); +} + +TEST_CASE_FIXTURE( + test_bf_directed_hyperedge_major_incidence_list, + "remove_vertex should properly remove the minor entries and shift the vertex IDs" +) { + constexpr hgl::types::size_type n_vertices = 5uz, n_hyperedges = 1uz; + constexpr hgl::types::id_type hyperedge_id = constants::id1; + + sut_type sut{n_vertices, n_hyperedges}; + sut.bind_tail(constants::id2, hyperedge_id); + sut.bind_head(constants::id4, hyperedge_id); + + hgl::types::id_type rem_vid; + hgl::types::size_type expected_hyperedge_size; + std::vector expected_vertices; + + SUBCASE("not present vertex < first incident vertex") { + rem_vid = constants::id1; + expected_hyperedge_size = 2uz; + expected_vertices = {constants::id2 - 1uz, constants::id4 - 1uz}; + } + + SUBCASE("present vertex = first incident vertex") { + rem_vid = constants::id2; + expected_hyperedge_size = 1uz; + expected_vertices = {constants::id4 - 1uz}; + } + + SUBCASE("not present vertex > first incident vertex") { + rem_vid = constants::id3; + expected_hyperedge_size = 2uz; + expected_vertices = {constants::id2, constants::id4 - 1uz}; + } + + SUBCASE("present vertex = last incident vertex") { + rem_vid = constants::id4; + expected_hyperedge_size = 1uz; + expected_vertices = {constants::id2}; + } + + SUBCASE("not present vertex > last incident vertex") { + rem_vid = constants::id4 + 1uz; + expected_hyperedge_size = 2uz; + expected_vertices = {constants::id2, constants::id4}; + } + + CAPTURE(rem_vid); + CAPTURE(expected_hyperedge_size); + CAPTURE(expected_vertices); + + sut.remove_vertex(rem_vid); + CHECK_EQ(sut.hyperedge_size(hyperedge_id), expected_hyperedge_size); + CHECK(std::ranges::equal(sut.incident_vertices(hyperedge_id), expected_vertices)); +} + +TEST_CASE_FIXTURE( + test_bf_directed_vertex_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))" +) { + sut_type sut{constants::n_vertices, constants::n_hyperedges}; + + constexpr auto vertex_id = constants::id1; + REQUIRE(std::ranges::empty(sut.incident_hyperedges(vertex_id))); + + const auto [tail_bound_hyperedges, head_bound_hyperedges] = + altbind_to_vertex(sut, vertex_id, constants::n_hyperedges); + + CHECK(std::ranges::is_permutation( + sut.incident_hyperedges(vertex_id), constants::hyperedge_ids_view + )); + CHECK(std::ranges::equal(sut.outgoing_hyperedges(vertex_id), tail_bound_hyperedges)); + CHECK(std::ranges::equal(sut.incoming_hyperedges(vertex_id), head_bound_hyperedges)); +} + +TEST_CASE_FIXTURE( + test_bf_directed_vertex_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))" +) { + sut_type sut{constants::n_vertices, constants::n_hyperedges}; + + constexpr auto vertex_id = constants::id1; + REQUIRE_EQ(sut.degree(vertex_id), 0uz); + + const auto [tail_bound_hyperedges, head_bound_hyperedges] = + altbind_to_vertex(sut, vertex_id, constants::n_hyperedges); + + CHECK_EQ(sut.degree(vertex_id), constants::n_hyperedges); + CHECK_EQ(sut.out_degree(vertex_id), tail_bound_hyperedges.size()); + CHECK_EQ(sut.in_degree(vertex_id), head_bound_hyperedges.size()); +} + +TEST_CASE_FIXTURE( + test_bf_directed_hyperedge_major_incidence_list, + "add_hyperedges should properly extend the list" +) { + sut_type sut{constants::n_vertices, constants::n_hyperedges}; + const auto initial_size = storage(sut).size(); + + sut.add_hyperedges(2uz); + + CHECK_EQ(storage(sut).size(), initial_size + 2uz); + CHECK(std::ranges::all_of(storage(sut), is_empty_entry)); +} + +TEST_CASE_FIXTURE( + test_bf_directed_hyperedge_major_incidence_list, + "remove_hyperedge should properly remove the row and implicitly shift hyperedge IDs" +) { + constexpr hgl::types::size_type n_vertices = 1uz, n_hyperedges = 5uz; + constexpr hgl::types::id_type vertex_id = constants::id1; + + sut_type sut{n_vertices, n_hyperedges}; + sut.bind_tail(vertex_id, constants::id2); + sut.bind_head(vertex_id, constants::id4); + + hgl::types::id_type rem_eid; + hgl::types::size_type expected_vertex_degree; + std::vector expected_hyperedges; + + SUBCASE("not present hyperedge < first incident hyperedge") { + rem_eid = constants::id1; + expected_vertex_degree = 2uz; + expected_hyperedges = {constants::id2 - 1uz, constants::id4 - 1uz}; + } + + SUBCASE("present hyperedge = first incident hyperedge") { + rem_eid = constants::id2; + expected_vertex_degree = 1uz; + expected_hyperedges = {constants::id4 - 1uz}; + } + + SUBCASE("not present hyperedge > first incident hyperedge") { + rem_eid = constants::id3; + expected_vertex_degree = 2uz; + expected_hyperedges = {constants::id2, constants::id4 - 1uz}; + } + + SUBCASE("present hyperedge = last incident hyperedge") { + rem_eid = constants::id4; + expected_vertex_degree = 1uz; + expected_hyperedges = {constants::id2}; + } + + SUBCASE("not present hyperedge > last incident hyperedge") { + rem_eid = constants::id4 + 1uz; + expected_vertex_degree = 2uz; + expected_hyperedges = {constants::id2, constants::id4}; + } + + CAPTURE(rem_eid); + CAPTURE(expected_vertex_degree); + CAPTURE(expected_hyperedges); + + sut.remove_hyperedge(rem_eid); + CHECK_EQ(storage(sut).size(), n_hyperedges - 1uz); + CHECK_EQ(sut.degree(vertex_id), expected_vertex_degree); + CHECK(std::ranges::equal(sut.incident_hyperedges(vertex_id), expected_hyperedges)); +} + +TEST_CASE_FIXTURE( + test_bf_directed_vertex_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)" +) { + sut_type sut{constants::n_vertices, constants::n_hyperedges}; + + constexpr auto hyperedge_id = constants::id1; + REQUIRE(std::ranges::empty(sut.incident_vertices(hyperedge_id))); + + 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::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, + "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)|" +) { + sut_type sut{constants::n_vertices, constants::n_hyperedges}; + + constexpr auto hyperedge_id = constants::id1; + REQUIRE_EQ(sut.hyperedge_size(hyperedge_id), 0uz); + + const auto [tail_bound_vertices, head_bound_vertices] = + altbind_to_hyperedge(sut, hyperedge_id, constants::n_vertices); + + CHECK_EQ(sut.hyperedge_size(hyperedge_id), constants::n_vertices); + CHECK_EQ(sut.tail_size(hyperedge_id), tail_bound_vertices.size()); + CHECK_EQ(sut.head_size(hyperedge_id), head_bound_vertices.size()); +} + +TEST_CASE_FIXTURE( + test_bf_directed_vertex_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}; + REQUIRE(std::ranges::empty(sut.incident_vertices(constants::id1))); + + sut.bind_tail(constants::id1, constants::id1); + + REQUIRE_EQ(storage(sut)[constants::id1].tail.size(), 1uz); + REQUIRE_EQ(storage(sut)[constants::id1].head.size(), 0uz); + CHECK_EQ(storage(sut)[constants::id1].tail.front(), constants::id1); + + const auto hyperedges = sut.tail_vertices(constants::id1) | std::ranges::to(); + CHECK_EQ(sut.tail_size(constants::id1), 1uz); + CHECK_EQ(std::ranges::size(hyperedges), 1uz); + CHECK(std::ranges::contains(hyperedges, constants::id1)); +} + +TEST_CASE_FIXTURE( + test_bf_directed_vertex_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}; + REQUIRE(std::ranges::empty(sut.incident_vertices(constants::id1))); + + sut.bind_head(constants::id1, constants::id1); + + REQUIRE_EQ(storage(sut)[constants::id1].head.size(), 1uz); + REQUIRE_EQ(storage(sut)[constants::id1].tail.size(), 0uz); + CHECK_EQ(storage(sut)[constants::id1].head.front(), constants::id1); + + const auto hyperedges = sut.head_vertices(constants::id1) | std::ranges::to(); + CHECK_EQ(sut.head_size(constants::id1), 1uz); + CHECK_EQ(std::ranges::size(hyperedges), 1uz); + CHECK(std::ranges::contains(hyperedges, constants::id1)); +} + +TEST_CASE_FIXTURE( + test_bf_directed_vertex_major_incidence_list, "unbind should clear the corresponding bit" +) { + sut_type sut{constants::n_vertices, constants::n_hyperedges}; + std::vector expected_storage; + + SUBCASE("tail bound") { + sut.bind_tail(constants::id1, constants::id1); + expected_storage = storage(sut)[constants::id1].tail; + } + SUBCASE("head bound") { + sut.bind_head(constants::id1, constants::id1); + expected_storage = storage(sut)[constants::id1].head; + } + CAPTURE(sut); + CAPTURE(expected_storage); + + REQUIRE_EQ(sut.hyperedge_size(constants::id1), 1uz); + REQUIRE_EQ(expected_storage.size(), 1uz); + REQUIRE_EQ(expected_storage.front(), constants::id1); + + sut.unbind(constants::id1, constants::id2); + CHECK_EQ(sut.hyperedge_size(constants::id1), 1uz); + + sut.unbind(constants::id1, constants::id1); + CHECK(std::ranges::empty(sut.incident_vertices(constants::id1))); +} + +TEST_CASE_FIXTURE( + test_bf_directed_vertex_major_incidence_list, + "are_bound, is_tail, is_head should return true only when the corresponding major list entries " + "contain the given minor ids" +) { + sut_type sut{constants::n_vertices, constants::n_hyperedges}; + + sut.bind_tail(constants::id1, constants::id1); + sut.bind_head(constants::id2, constants::id1); + + CHECK(sut.are_bound(constants::id1, constants::id1)); + CHECK(sut.is_tail(constants::id1, constants::id1)); + CHECK_FALSE(sut.is_head(constants::id1, constants::id1)); + + CHECK(sut.are_bound(constants::id2, constants::id1)); + CHECK_FALSE(sut.is_tail(constants::id2, constants::id1)); + CHECK(sut.is_head(constants::id2, constants::id1)); + + CHECK_FALSE(sut.are_bound(constants::id3, constants::id1)); + CHECK_FALSE(sut.is_tail(constants::id3, constants::id1)); + CHECK_FALSE(sut.is_head(constants::id3, constants::id1)); +} TEST_SUITE_END(); // test_incidence_list From 818020eca29ea3cf87f5a6f42f2c1bd886cb0b14 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Mon, 29 Dec 2025 21:02:03 +0100 Subject: [PATCH 8/8] resolved comments --- include/gl/algorithm/dijkstra.hpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/include/gl/algorithm/dijkstra.hpp b/include/gl/algorithm/dijkstra.hpp index 6099ced..c37f064 100644 --- a/include/gl/algorithm/dijkstra.hpp +++ b/include/gl/algorithm/dijkstra.hpp @@ -116,8 +116,7 @@ template < if (negative_edge.has_value()) { const auto& edge = negative_edge.value(); throw std::invalid_argument(std::format( - "[alg::dijkstra_shortest_paths] Found an edge with a negative weight: [{}, {} | " - "w={}]", + "[alg::dijkstra_shortest_paths] Found an edge with a negative weight: [{}, {} | w={}]", edge.source(), edge.target(), get_weight(edge)