From afa40f0eb2bd9b7e074b62dc3f98f829c61d2e98 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Fri, 10 Apr 2026 20:40:07 +0200 Subject: [PATCH 1/9] initial vertex getters impl + impl classes layout refinement --- include/gl/graph.hpp | 30 ++ include/gl/impl/adjacency_list.hpp | 70 +++-- include/gl/impl/adjacency_matrix.hpp | 91 ++++--- .../gl/impl/specialized/adjacency_list.hpp | 233 ++++++++++------ .../gl/impl/specialized/adjacency_matrix.hpp | 206 +++++++++----- .../impl/specialized/flat_adjacency_list.hpp | 256 +++++++++++------- .../specialized/flat_adjacency_matrix.hpp | 216 ++++++++++----- 7 files changed, 704 insertions(+), 398 deletions(-) diff --git a/include/gl/graph.hpp b/include/gl/graph.hpp index f5c9175..7c98df7 100644 --- a/include/gl/graph.hpp +++ b/include/gl/graph.hpp @@ -293,6 +293,36 @@ class graph final { return this->at(vertex.id()); } + [[nodiscard]] gl_attr_force_inline auto neighbors(const id_type vertex_id) const { + return this->neighbor_ids(vertex_id) + | std::views::transform([this](const id_type id) { return this->get_vertex(id); }); + } + + [[nodiscard]] gl_attr_force_inline auto neighbor_ids(const id_type vertex_id) const { + this->_verify_vertex_id(vertex_id); + return this->_impl.neighbor_ids(vertex_id); + } + + [[nodiscard]] gl_attr_force_inline auto predecessors(const id_type vertex_id) const { + return this->predecessor_ids(vertex_id) + | std::views::transform([this](const id_type id) { return this->get_vertex(id); }); + } + + [[nodiscard]] gl_attr_force_inline auto predecessor_ids(const id_type vertex_id) const { + this->_verify_vertex_id(vertex_id); + return this->_impl.predecessor_ids(vertex_id); + } + + [[nodiscard]] gl_attr_force_inline auto successors(const id_type vertex_id) const { + return this->successor_ids(vertex_id) + | std::views::transform([this](const id_type id) { return this->get_vertex(id); }); + } + + [[nodiscard]] gl_attr_force_inline auto successor_ids(const id_type vertex_id) const { + this->_verify_vertex_id(vertex_id); + return this->_impl.successor_ids(vertex_id); + } + [[nodiscard]] inline auto incident_edges(const id_type vertex_id) const { this->_verify_vertex_id(vertex_id); if constexpr (traits::c_non_empty_properties) diff --git a/include/gl/impl/adjacency_list.hpp b/include/gl/impl/adjacency_list.hpp index 6f33346..dee520c 100644 --- a/include/gl/impl/adjacency_list.hpp +++ b/include/gl/impl/adjacency_list.hpp @@ -51,16 +51,38 @@ class adjacency_list final { ~adjacency_list() = default; - // --- vertex methods --- + // --- vertex modifiers --- gl_attr_force_inline void add_vertex() { this->_list.resize(this->_list.size() + 1uz); } - inline void add_vertices(size_type n) { + gl_attr_force_inline void add_vertices(size_type n) { this->_list.resize(this->_list.size() + n); } + std::vector remove_vertex(id_type vertex_id) { + auto removed_edge_ids = specialized_impl::remove_vertex(*this, vertex_id); + this->_remap_element_ids(vertex_id, removed_edge_ids); + return removed_edge_ids; + } + + // --- vertex getters --- + + [[nodiscard]] gl_attr_force_inline auto neighbor_ids(id_type vertex_id) const { + return specialized_impl::neighbor_ids(*this, vertex_id); + } + + [[nodiscard]] gl_attr_force_inline auto predecessor_ids(id_type vertex_id) const { + return specialized_impl::predecessor_ids(*this, vertex_id); + } + + [[nodiscard]] gl_attr_force_inline auto successor_ids(id_type vertex_id) const { + return specialized_impl::successor_ids(*this, vertex_id); + } + + // --- degree getters --- + [[nodiscard]] gl_attr_force_inline size_type degree(id_type vertex_id) const { return specialized_impl::degree(*this, vertex_id); } @@ -85,13 +107,7 @@ class adjacency_list final { return specialized_impl::out_degree_map(*this); } - std::vector remove_vertex(id_type vertex_id) { - auto removed_edge_ids = specialized_impl::remove_vertex(*this, vertex_id); - this->_remap_element_ids(vertex_id, removed_edge_ids); - return removed_edge_ids; - } - - // --- edge methods --- + // --- edge modifiers --- gl_attr_force_inline void add_edge(id_type id, id_type source_id, id_type target_id) { specialized_impl::add_edge(*this, id, source_id, target_id); @@ -105,6 +121,25 @@ class adjacency_list final { specialized_impl::add_edges_from(*this, edge_ids, source_id, target_ids); } + gl_attr_force_inline void remove_edge(const edge_type& edge) { + specialized_impl::remove_edge(*this, edge); + for (auto&& inc : this->_list) + for (auto& item : inc) + item.edge_id -= static_cast(item.edge_id > edge.id()); + } + + std::vector remove_edges(const traits::c_range_of auto& edges) { + for (const auto& edge : edges) + specialized_impl::remove_edge(*this, edge); + auto removed_edge_ids = + edges | std::views::transform([](const auto& edge) { return edge.id(); }) + | std::ranges::to(); + this->_remap_element_ids(invalid_id, removed_edge_ids); + return removed_edge_ids; + } + + // --- edge getters --- + [[nodiscard]] gl_attr_force_inline bool has_edge(id_type source_id, id_type target_id) const { return std::ranges::contains( this->_list[to_idx(source_id)], target_id, &item_type::vertex_id @@ -171,23 +206,6 @@ class adjacency_list final { | std::ranges::to(); } - gl_attr_force_inline void remove_edge(const edge_type& edge) { - specialized_impl::remove_edge(*this, edge); - for (auto&& inc : this->_list) - for (auto& item : inc) - item.edge_id -= static_cast(item.edge_id > edge.id()); - } - - std::vector remove_edges(const traits::c_range_of auto& edges) { - for (const auto& edge : edges) - specialized_impl::remove_edge(*this, edge); - auto removed_edge_ids = - edges | std::views::transform([](const auto& edge) { return edge.id(); }) - | std::ranges::to(); - this->_remap_element_ids(invalid_id, removed_edge_ids); - return removed_edge_ids; - } - [[nodiscard]] gl_attr_force_inline auto incident_edges(id_type vertex_id) const requires(traits::c_has_empty_properties) { diff --git a/include/gl/impl/adjacency_matrix.hpp b/include/gl/impl/adjacency_matrix.hpp index 258f1ef..78bc73c 100644 --- a/include/gl/impl/adjacency_matrix.hpp +++ b/include/gl/impl/adjacency_matrix.hpp @@ -4,6 +4,7 @@ #pragma once +#include "gl/attributes/force_inline.hpp" #include "gl/constants.hpp" #include "gl/impl/specialized/adjacency_matrix.hpp" #include "gl/impl/specialized/flat_adjacency_matrix.hpp" @@ -52,47 +53,63 @@ class adjacency_matrix final { ~adjacency_matrix() = default; - // --- vertex methods --- + // --- vertex modifiers --- - void add_vertex() { + gl_attr_force_inline void add_vertex() { specialized_impl::add_vertex(*this); } - void add_vertices(size_type n) { + gl_attr_force_inline void add_vertices(size_type n) { specialized_impl::add_vertices(*this, n); } - [[nodiscard]] gl_attr_force_inline size_type in_degree(id_type vertex_id) const { - return specialized_impl::in_degree(*this, vertex_id); + std::vector remove_vertex(id_type vertex_id) { + auto removed_edge_ids = specialized_impl::remove_vertex(*this, vertex_id); + this->_remap_element_ids(removed_edge_ids); + return removed_edge_ids; } - [[nodiscard]] gl_attr_force_inline size_type out_degree(id_type vertex_id) const { - return specialized_impl::out_degree(*this, vertex_id); + // --- vertex getters --- + + [[nodiscard]] gl_attr_force_inline auto neighbor_ids(id_type vertex_id) const { + return specialized_impl::neighbor_ids(*this, vertex_id); + } + + [[nodiscard]] gl_attr_force_inline auto predecessor_ids(id_type vertex_id) const { + return specialized_impl::predecessor_ids(*this, vertex_id); + } + + [[nodiscard]] gl_attr_force_inline auto successor_ids(id_type vertex_id) const { + return specialized_impl::successor_ids(*this, vertex_id); + } + + // --- degree getters --- + + [[nodiscard]] gl_attr_force_inline std::vector degree_map() const { + return specialized_impl::degree_map(*this); } [[nodiscard]] gl_attr_force_inline size_type degree(id_type vertex_id) const { return specialized_impl::degree(*this, vertex_id); } - [[nodiscard]] gl_attr_force_inline std::vector in_degree_map() const { - return specialized_impl::in_degree_map(*this); + [[nodiscard]] gl_attr_force_inline size_type in_degree(id_type vertex_id) const { + return specialized_impl::in_degree(*this, vertex_id); } - [[nodiscard]] gl_attr_force_inline std::vector out_degree_map() const { - return specialized_impl::out_degree_map(*this); + [[nodiscard]] gl_attr_force_inline size_type out_degree(id_type vertex_id) const { + return specialized_impl::out_degree(*this, vertex_id); } - [[nodiscard]] gl_attr_force_inline std::vector degree_map() const { - return specialized_impl::degree_map(*this); + [[nodiscard]] gl_attr_force_inline std::vector in_degree_map() const { + return specialized_impl::in_degree_map(*this); } - std::vector remove_vertex(id_type vertex_id) { - auto removed_edge_ids = specialized_impl::remove_vertex(*this, vertex_id); - this->_remap_element_ids(removed_edge_ids); - return removed_edge_ids; + [[nodiscard]] gl_attr_force_inline std::vector out_degree_map() const { + return specialized_impl::out_degree_map(*this); } - // --- edge methods --- + // --- edge modifiers --- gl_attr_force_inline void add_edge(id_type id, id_type source_id, id_type target_id) { specialized_impl::add_edge(*this, id, source_id, target_id); @@ -106,6 +123,26 @@ class adjacency_matrix final { specialized_impl::add_edges_from(*this, edge_ids, source_id, target_ids); } + gl_attr_force_inline void remove_edge(const edge_type& edge) { + specialized_impl::remove_edge(*this, edge); + for (auto&& row : this->_matrix) + for (auto& edge_id : row) + if (edge_id != invalid_id and edge_id > edge.id()) + edge_id--; + } + + std::vector remove_edges(const traits::c_range_of auto& edges) { + for (const auto& edge : edges) + specialized_impl::remove_edge(*this, edge); + auto removed_edge_ids = + edges | std::views::transform([](const auto& edge) { return edge.id(); }) + | std::ranges::to(); + this->_remap_element_ids(removed_edge_ids); + return removed_edge_ids; + } + + // --- edge getters --- + [[nodiscard]] gl_attr_force_inline bool has_edge(id_type source_id, id_type target_id) const { return specialized_impl::get_entry(*this, source_id, target_id) != invalid_id; } @@ -160,24 +197,6 @@ class adjacency_matrix final { }; } - gl_attr_force_inline void remove_edge(const edge_type& edge) { - specialized_impl::remove_edge(*this, edge); - for (auto&& row : this->_matrix) - for (auto& edge_id : row) - if (edge_id != invalid_id and edge_id > edge.id()) - edge_id--; - } - - std::vector remove_edges(const traits::c_range_of auto& edges) { - for (const auto& edge : edges) - specialized_impl::remove_edge(*this, edge); - auto removed_edge_ids = - edges | std::views::transform([](const auto& edge) { return edge.id(); }) - | std::ranges::to(); - this->_remap_element_ids(removed_edge_ids); - return removed_edge_ids; - } - [[nodiscard]] gl_attr_force_inline auto incident_edges(id_type vertex_id) const requires(traits::c_has_empty_properties) { diff --git a/include/gl/impl/specialized/adjacency_list.hpp b/include/gl/impl/specialized/adjacency_list.hpp index 6429406..09c445c 100644 --- a/include/gl/impl/specialized/adjacency_list.hpp +++ b/include/gl/impl/specialized/adjacency_list.hpp @@ -9,6 +9,7 @@ #include "gl/graph_traits.hpp" #include "gl/traits.hpp" #include "gl/types/core.hpp" +#include "gl/util/ranges.hpp" #include #include @@ -60,60 +61,75 @@ struct directed_adjacency_list { using edge_type = typename impl_type::edge_type; using item_type = incidence_item; - [[nodiscard]] static auto in_edges(const impl_type& self, id_type vertex_id) { - std::vector in_edges; - for (id_type src_id = initial_id; src_id < self._list.size(); ++src_id) { - auto in_edges_view = - self._list[to_idx(src_id)] - | std::views::filter([tgt_id = vertex_id](const auto& item) { - return item.vertex_id == tgt_id; - }) - | std::views::transform([src_id](const auto& item) { - return incidence_item{src_id, item.edge_id}; - }); - in_edges.insert(in_edges.end(), in_edges_view.begin(), in_edges_view.end()); + // --- vertex modifiers --- + + static std::vector remove_vertex(impl_type& self, id_type vertex_id) { + const auto vertex_idx = to_idx(vertex_id); + + auto removed_edges = + self._list[vertex_idx] | std::views::transform(&item_type::edge_id) + | std::ranges::to(); + + // remove all edges incident to the vertex + for (auto idx = 0uz; idx < self._list.size(); ++idx) { + auto& inc_edges = self._list[idx]; + if (idx == vertex_idx or inc_edges.empty()) + continue; + + const auto removed_subrng = + std::ranges::remove_if(inc_edges, [vertex_id, &removed_edges](const auto& item) { + if (item.vertex_id == vertex_id) { + removed_edges.push_back(item.edge_id); + return true; + } + return false; + }); + inc_edges.erase(removed_subrng.begin(), removed_subrng.end()); } - return in_edges; + + // remove the list of edges incident from the vertex entirely + self._list.erase(self._list.begin() + to_diff(vertex_id)); + return removed_edges; } - [[nodiscard]] static size_type in_degree(const impl_type& self, id_type vertex_id) { - size_type in_deg = 0uz; - for (const auto& incident_edges : self._list) - in_deg += static_cast( - std::ranges::count(incident_edges, vertex_id, &item_type::vertex_id) - ); + // --- vertex getters --- - return in_deg; + [[nodiscard]] static auto neighbor_ids(const impl_type& self, id_type vertex_id) { + return util::concat(successor_ids(self, vertex_id), predecessor_ids(self, vertex_id)); } - [[nodiscard]] gl_attr_force_inline static size_type out_degree( + [[nodiscard]] static auto predecessor_ids(const impl_type& self, id_type vertex_id) { + return in_edges(self, vertex_id) | std::views::transform(&item_type::vertex_id); + } + + [[nodiscard]] gl_attr_force_inline static auto successor_ids( const impl_type& self, id_type vertex_id ) { - return self._list[to_idx(vertex_id)].size(); + return self._list[to_idx(vertex_id)] | std::views::transform(&item_type::vertex_id); } + // --- degree getters --- + [[nodiscard]] gl_attr_force_inline static size_type degree( const impl_type& self, id_type vertex_id ) { return in_degree(self, vertex_id) + out_degree(self, vertex_id); } - [[nodiscard]] static std::vector in_degree_map(const impl_type& self) { - std::vector in_degree_map(self._list.size(), 0uz); - - for (const auto& inc_edges : self._list) - for (const auto& item : inc_edges) - ++in_degree_map[to_idx(item.vertex_id)]; + [[nodiscard]] static size_type in_degree(const impl_type& self, id_type vertex_id) { + size_type in_deg = 0uz; + for (const auto& incident_edges : self._list) + in_deg += static_cast( + std::ranges::count(incident_edges, vertex_id, &item_type::vertex_id) + ); - return in_degree_map; + return in_deg; } - [[nodiscard]] gl_attr_force_inline static std::vector out_degree_map( - const impl_type& self + [[nodiscard]] gl_attr_force_inline static size_type out_degree( + const impl_type& self, id_type vertex_id ) { - return self._list - | std::views::transform([](const auto& inc_edges) { return inc_edges.size(); }) - | std::ranges::to>(); + return self._list[to_idx(vertex_id)].size(); } [[nodiscard]] static std::vector degree_map(const impl_type& self) { @@ -129,35 +145,26 @@ struct directed_adjacency_list { return degree_map; } - static std::vector remove_vertex(impl_type& self, id_type vertex_id) { - const auto vertex_idx = to_idx(vertex_id); - - auto removed_edges = - self._list[vertex_idx] | std::views::transform(&item_type::edge_id) - | std::ranges::to(); + [[nodiscard]] static std::vector in_degree_map(const impl_type& self) { + std::vector in_degree_map(self._list.size(), 0uz); - // remove all edges incident to the vertex - for (auto idx = 0uz; idx < self._list.size(); ++idx) { - auto& inc_edges = self._list[idx]; - if (idx == vertex_idx or inc_edges.empty()) - continue; + for (const auto& inc_edges : self._list) + for (const auto& item : inc_edges) + ++in_degree_map[to_idx(item.vertex_id)]; - const auto removed_subrng = - std::ranges::remove_if(inc_edges, [vertex_id, &removed_edges](const auto& item) { - if (item.vertex_id == vertex_id) { - removed_edges.push_back(item.edge_id); - return true; - } - return false; - }); - inc_edges.erase(removed_subrng.begin(), removed_subrng.end()); - } + return in_degree_map; + } - // remove the list of edges incident from the vertex entirely - self._list.erase(self._list.begin() + to_diff(vertex_id)); - return removed_edges; + [[nodiscard]] gl_attr_force_inline static std::vector out_degree_map( + const impl_type& self + ) { + return self._list + | std::views::transform([](const auto& inc_edges) { return inc_edges.size(); }) + | std::ranges::to>(); } + // --- edge modifiers --- + gl_attr_force_inline static void add_edge( impl_type& self, id_type edge_id, id_type source_id, id_type target_id ) { @@ -181,6 +188,24 @@ struct directed_adjacency_list { auto& inc_edges = self._list[to_idx(edge.source())]; inc_edges.erase(detail::strict_find(inc_edges, edge)); } + + // --- edge getters --- + + [[nodiscard]] static auto in_edges(const impl_type& self, id_type vertex_id) { + std::vector in_edges; + for (id_type src_id = initial_id; src_id < self._list.size(); ++src_id) { + auto in_edges_view = + self._list[to_idx(src_id)] + | std::views::filter([tgt_id = vertex_id](const auto& item) { + return item.vertex_id == tgt_id; + }) + | std::views::transform([src_id](const auto& item) { + return incidence_item{src_id, item.edge_id}; + }); + in_edges.insert(in_edges.end(), in_edges_view.begin(), in_edges_view.end()); + } + return in_edges; + } }; template AdjacencyList> @@ -191,24 +216,53 @@ struct undirected_adjacency_list { using edge_type = typename impl_type::edge_type; using item_type = incidence_item; - [[nodiscard]] gl_attr_force_inline static auto in_edges( + // --- vertex modifiers --- + + static std::vector remove_vertex(impl_type& self, id_type vertex_id) { + const auto vertex_idx = to_idx(vertex_id); + + // remove all edges incident with the vertex (scan only the selected vertices) + for (const auto& item : self._list[vertex_idx]) { + if (item.vertex_id == vertex_id) + continue; // will be removed with the vertex's list + + auto& inc_edges = self._list[to_idx(item.vertex_id)]; + const auto removed_subrng = std::ranges::remove_if( + inc_edges, [vertex_id](const auto& item) { return item.vertex_id == vertex_id; } + ); + inc_edges.erase(removed_subrng.begin(), removed_subrng.end()); + } + + // remove the list of edges incident from the vertex entirely + const auto removed_edges = + self._list[vertex_idx] | std::views::transform(&item_type::edge_id) + | std::ranges::to(); + self._list.erase(self._list.begin() + to_diff(vertex_id)); + return removed_edges; + } + + // --- vertex getters --- + + [[nodiscard]] gl_attr_force_inline static auto neighbor_ids( const impl_type& self, id_type vertex_id ) { - return std::views::all(self._list[to_idx(vertex_id)]); + return self._list[to_idx(vertex_id)] | std::views::transform(&item_type::vertex_id); } - [[nodiscard]] gl_attr_force_inline static size_type in_degree( + [[nodiscard]] gl_attr_force_inline static auto predecessor_ids( const impl_type& self, id_type vertex_id ) { - return degree(self, vertex_id); + return neighbor_ids(self, vertex_id); } - [[nodiscard]] gl_attr_force_inline static size_type out_degree( + [[nodiscard]] gl_attr_force_inline static auto successor_ids( const impl_type& self, id_type vertex_id ) { - return degree(self, vertex_id); + return neighbor_ids(self, vertex_id); } + // --- degree getters --- + [[nodiscard]] static size_type degree(const impl_type& self, id_type vertex_id) { size_type degree = 0uz; for (const auto& item : self._list[to_idx(vertex_id)]) @@ -216,16 +270,16 @@ struct undirected_adjacency_list { return degree; } - [[nodiscard]] gl_attr_force_inline static std::vector in_degree_map( - const impl_type& self + [[nodiscard]] gl_attr_force_inline static size_type in_degree( + const impl_type& self, id_type vertex_id ) { - return degree_map(self); + return degree(self, vertex_id); } - [[nodiscard]] gl_attr_force_inline static std::vector out_degree_map( - const impl_type& self + [[nodiscard]] gl_attr_force_inline static size_type out_degree( + const impl_type& self, id_type vertex_id ) { - return degree_map(self); + return degree(self, vertex_id); } [[nodiscard]] static std::vector degree_map(const impl_type& self) { @@ -236,29 +290,20 @@ struct undirected_adjacency_list { return degree_map; } - static std::vector remove_vertex(impl_type& self, id_type vertex_id) { - const auto vertex_idx = to_idx(vertex_id); - - // remove all edges incident with the vertex (scan only the selected vertices) - for (const auto& item : self._list[vertex_idx]) { - if (item.vertex_id == vertex_id) - continue; // will be removed with the vertex's list - - auto& inc_edges = self._list[to_idx(item.vertex_id)]; - const auto removed_subrng = std::ranges::remove_if( - inc_edges, [vertex_id](const auto& item) { return item.vertex_id == vertex_id; } - ); - inc_edges.erase(removed_subrng.begin(), removed_subrng.end()); - } + [[nodiscard]] gl_attr_force_inline static std::vector in_degree_map( + const impl_type& self + ) { + return degree_map(self); + } - // remove the list of edges incident from the vertex entirely - const auto removed_edges = - self._list[vertex_idx] | std::views::transform(&item_type::edge_id) - | std::ranges::to(); - self._list.erase(self._list.begin() + to_diff(vertex_id)); - return removed_edges; + [[nodiscard]] gl_attr_force_inline static std::vector out_degree_map( + const impl_type& self + ) { + return degree_map(self); } + // --- edge modifiers --- + static void add_edge(impl_type& self, id_type edge_id, id_type source_id, id_type target_id) { self._list[to_idx(source_id)].emplace_back(target_id, edge_id); if (target_id != source_id) @@ -289,6 +334,14 @@ struct undirected_adjacency_list { if (not edge.is_loop()) inc_edges_second.erase(detail::strict_find(inc_edges_second, edge)); } + + // --- edge getters --- + + [[nodiscard]] gl_attr_force_inline static auto in_edges( + const impl_type& self, id_type vertex_id + ) { + return std::views::all(self._list[to_idx(vertex_id)]); + } }; template AdjacencyList> diff --git a/include/gl/impl/specialized/adjacency_matrix.hpp b/include/gl/impl/specialized/adjacency_matrix.hpp index 017d24f..d320a12 100644 --- a/include/gl/impl/specialized/adjacency_matrix.hpp +++ b/include/gl/impl/specialized/adjacency_matrix.hpp @@ -5,10 +5,12 @@ #pragma once #include "gl/attributes/diagnostics.hpp" +#include "gl/attributes/force_inline.hpp" #include "gl/constants.hpp" #include "gl/decl/impl_tags.hpp" #include "gl/graph_traits.hpp" #include "gl/types/core.hpp" +#include "gl/util/ranges.hpp" #include #include @@ -58,12 +60,22 @@ struct directed_adjacency_matrix { using vertex_type = typename impl_type::vertex_type; using edge_type = typename impl_type::edge_type; + // --- general --- + static void init(impl_type& self, size_type n_vertices) { self._matrix.resize(n_vertices); for (auto& row : self._matrix) row.resize(n_vertices, invalid_id); } + gl_attr_force_inline static id_type get_entry( + const impl_type& self, id_type source_id, id_type target_id + ) { + return self._matrix[to_idx(source_id)][to_idx(target_id)]; + } + + // --- vertex modifiers --- + static void add_vertex(impl_type& self) { for (auto& row : self._matrix) row.emplace_back(invalid_id); @@ -78,30 +90,47 @@ struct directed_adjacency_matrix { self._matrix.emplace_back(new_n_vertices, invalid_id); } - GL_SUPPRESS_WARNING_BEGIN("-Wsign-conversion") + static std::vector remove_vertex(impl_type& self, id_type vertex_id) { + const auto vertex_idx = to_idx(vertex_id); - // NOTE: Indexing into a row which might be a vector (requires size type) or a span/subrange (requires difference type) + auto removed_edges = + self._matrix[vertex_idx] + | std::views::filter([](auto edge_id) { return edge_id != invalid_id; }) + | std::ranges::to(); - [[nodiscard]] gl_attr_force_inline static size_type in_degree( - const impl_type& self, id_type vertex_id - ) { - return static_cast(std::ranges::count_if( - self._matrix, - [vertex_id](const auto& row) { return row[to_idx(vertex_id)] != invalid_id; } - )); + const auto vertex_pos = to_diff(vertex_id); + self._matrix.erase(self._matrix.begin() + vertex_pos); + for (auto& row : self._matrix) { + if (const auto edge_id = row[vertex_idx]; edge_id != invalid_id) + removed_edges.push_back(edge_id); + row.erase(row.begin() + vertex_pos); + } + + return removed_edges; } - GL_SUPPRESS_WARNING_END + // --- vertex getters --- - [[nodiscard]] gl_attr_force_inline static size_type out_degree( - const impl_type& self, id_type vertex_id - ) { - return self._matrix[vertex_id].size() - - static_cast( - std::ranges::count(self._matrix[vertex_id], invalid_id_v) - ); + [[nodiscard]] static auto successor_ids(const impl_type& self, id_type vertex_id) { + return self._matrix[to_idx(vertex_id)] | std::views::enumerate + | std::views::filter([](auto entry) { return entry.second != invalid_id; }) + | std::views::transform([](auto entry) { return static_cast(entry.first); }); + } + + [[nodiscard]] static auto predecessor_ids(const impl_type& self, id_type vertex_id) { + const auto v_idx = to_idx(vertex_id); + return std::views::iota(initial_id_v, static_cast(self._matrix.size())) + | std::views::filter([&](const auto src_id) { + return self._matrix[to_idx(src_id)][v_idx] != invalid_id; + }); + } + + [[nodiscard]] static auto neighbor_ids(const impl_type& self, id_type vertex_id) { + return util::concat(successor_ids(self, vertex_id), predecessor_ids(self, vertex_id)); } + // --- degree getters --- + [[nodiscard]] gl_attr_force_inline static size_type degree( const impl_type& self, id_type vertex_id ) { @@ -114,21 +143,28 @@ struct directed_adjacency_matrix { return deg; } - [[nodiscard]] static std::vector in_degree_map(const impl_type& self) { - std::vector in_degree_map(self._matrix.size(), 0uz); + GL_SUPPRESS_WARNING_BEGIN("-Wsign-conversion") - for (const auto& row : self._matrix) - for (auto [target_id, edge_id] : std::views::enumerate(row)) - in_degree_map[static_cast(target_id)] += - static_cast(edge_id != invalid_id); + // NOTE: Indexing into a row which might be a vector (requires size type) or a span/subrange (requires difference type) - return in_degree_map; + [[nodiscard]] gl_attr_force_inline static size_type in_degree( + const impl_type& self, id_type vertex_id + ) { + return static_cast(std::ranges::count_if( + self._matrix, + [vertex_id](const auto& row) { return row[to_idx(vertex_id)] != invalid_id; } + )); } - [[nodiscard]] static std::vector out_degree_map(const impl_type& self) { - return std::views::iota(initial_id_v, self._matrix.size()) - | std::views::transform([&](id_type id) { return out_degree(self, id); }) - | std::ranges::to(); + GL_SUPPRESS_WARNING_END + + [[nodiscard]] gl_attr_force_inline static size_type out_degree( + const impl_type& self, id_type vertex_id + ) { + return self._matrix[vertex_id].size() + - static_cast( + std::ranges::count(self._matrix[vertex_id], invalid_id_v) + ); } [[nodiscard]] static std::vector degree_map(const impl_type& self) { @@ -146,29 +182,25 @@ struct directed_adjacency_matrix { return degree_map; } - static std::vector remove_vertex(impl_type& self, id_type vertex_id) { - const auto vertex_idx = to_idx(vertex_id); - - auto removed_edges = - self._matrix[vertex_idx] - | std::views::filter([](auto edge_id) { return edge_id != invalid_id; }) - | std::ranges::to(); + [[nodiscard]] static std::vector in_degree_map(const impl_type& self) { + std::vector in_degree_map(self._matrix.size(), 0uz); - const auto vertex_pos = to_diff(vertex_id); - self._matrix.erase(self._matrix.begin() + vertex_pos); - for (auto& row : self._matrix) { - if (const auto edge_id = row[vertex_idx]; edge_id != invalid_id) - removed_edges.push_back(edge_id); - row.erase(row.begin() + vertex_pos); - } + for (const auto& row : self._matrix) + for (auto [target_id, edge_id] : std::views::enumerate(row)) + in_degree_map[static_cast(target_id)] += + static_cast(edge_id != invalid_id); - return removed_edges; + return in_degree_map; } - static id_type get_entry(const impl_type& self, id_type source_id, id_type target_id) { - return self._matrix[to_idx(source_id)][to_idx(target_id)]; + [[nodiscard]] static std::vector out_degree_map(const impl_type& self) { + return std::views::iota(initial_id_v, self._matrix.size()) + | std::views::transform([&](id_type id) { return out_degree(self, id); }) + | std::ranges::to(); } + // --- edge modifiers --- + static inline void add_edge( impl_type& self, id_type edge_id, id_type source_id, id_type target_id ) { @@ -190,7 +222,7 @@ struct directed_adjacency_matrix { matrix_source_row[to_idx(target_id)] = edge_id; } - static inline void remove_edge(impl_type& self, const edge_type& edge) { + gl_attr_force_inline static void remove_edge(impl_type& self, const edge_type& edge) { detail::strict_get(self._matrix, edge) = invalid_id; } }; @@ -203,12 +235,22 @@ struct undirected_adjacency_matrix { using vertex_type = typename impl_type::vertex_type; using edge_type = typename impl_type::edge_type; + // --- general --- + static void init(impl_type& self, size_type n_vertices) { self._matrix.resize(n_vertices); for (auto& row : self._matrix) row.resize(n_vertices, invalid_id); } + gl_attr_force_inline static id_type get_entry( + const impl_type& self, id_type source_id, id_type target_id + ) { + return self._matrix[to_idx(source_id)][to_idx(target_id)]; + } + + // --- vertex modifiers --- + static void add_vertex(impl_type& self) { for (auto& row : self._matrix) row.emplace_back(invalid_id); @@ -223,18 +265,44 @@ struct undirected_adjacency_matrix { self._matrix.emplace_back(new_n_vertices, invalid_id); } - [[nodiscard]] gl_attr_force_inline static size_type in_degree( + static std::vector remove_vertex(impl_type& self, id_type vertex_id) { + const auto vertex_idx = to_idx(vertex_id); + + const auto removed_edges = + self._matrix[vertex_idx] + | std::views::filter([](auto edge_id) { return edge_id != invalid_id; }) + | std::ranges::to(); + + const auto vertex_pos = to_diff(vertex_id); + self._matrix.erase(self._matrix.begin() + vertex_pos); + for (auto& row : self._matrix) + row.erase(row.begin() + vertex_pos); + + return removed_edges; + } + + // --- vertex getters --- + + [[nodiscard]] static auto neighbor_ids(const impl_type& self, id_type vertex_id) { + return self._matrix[to_idx(vertex_id)] | std::views::enumerate + | std::views::filter([](auto entry) { return entry.second != invalid_id; }) + | std::views::transform([](auto entry) { return static_cast(entry.first); }); + } + + [[nodiscard]] gl_attr_force_inline static auto predecessor_ids( const impl_type& self, id_type vertex_id ) { - return degree(self, vertex_id); + return neighbor_ids(self, vertex_id); } - [[nodiscard]] gl_attr_force_inline static size_type out_degree( + [[nodiscard]] gl_attr_force_inline static auto successor_ids( const impl_type& self, id_type vertex_id ) { - return degree(self, vertex_id); + return neighbor_ids(self, vertex_id); } + // --- degree getters --- + [[nodiscard]] gl_attr_force_inline static size_type degree( const impl_type& self, id_type vertex_id ) { @@ -246,16 +314,16 @@ struct undirected_adjacency_matrix { + static_cast(self._matrix[vertex_idx][vertex_idx] != invalid_id); } - [[nodiscard]] gl_attr_force_inline static std::vector in_degree_map( - const impl_type& self + [[nodiscard]] gl_attr_force_inline static size_type in_degree( + const impl_type& self, id_type vertex_id ) { - return degree_map(self); + return degree(self, vertex_id); } - [[nodiscard]] gl_attr_force_inline static std::vector out_degree_map( - const impl_type& self + [[nodiscard]] gl_attr_force_inline static size_type out_degree( + const impl_type& self, id_type vertex_id ) { - return degree_map(self); + return degree(self, vertex_id); } [[nodiscard]] static std::vector degree_map(const impl_type& self) { @@ -273,26 +341,20 @@ struct undirected_adjacency_matrix { return degree_map; } - static std::vector remove_vertex(impl_type& self, id_type vertex_id) { - const auto vertex_idx = to_idx(vertex_id); - - const auto removed_edges = - self._matrix[vertex_idx] - | std::views::filter([](auto edge_id) { return edge_id != invalid_id; }) - | std::ranges::to(); - - const auto vertex_pos = to_diff(vertex_id); - self._matrix.erase(self._matrix.begin() + vertex_pos); - for (auto& row : self._matrix) - row.erase(row.begin() + vertex_pos); - - return removed_edges; + [[nodiscard]] gl_attr_force_inline static std::vector in_degree_map( + const impl_type& self + ) { + return degree_map(self); } - static id_type get_entry(const impl_type& self, id_type source_id, id_type target_id) { - return self._matrix[to_idx(source_id)][to_idx(target_id)]; + [[nodiscard]] gl_attr_force_inline static std::vector out_degree_map( + const impl_type& self + ) { + return degree_map(self); } + // --- edge modifiers --- + static void add_edge(impl_type& self, id_type edge_id, id_type source_id, id_type target_id) { detail::check_edge_override(self._matrix, source_id, target_id); diff --git a/include/gl/impl/specialized/flat_adjacency_list.hpp b/include/gl/impl/specialized/flat_adjacency_list.hpp index 4552b89..9897166 100644 --- a/include/gl/impl/specialized/flat_adjacency_list.hpp +++ b/include/gl/impl/specialized/flat_adjacency_list.hpp @@ -25,55 +25,76 @@ struct directed_flat_adjacency_list { using edge_type = typename impl_type::edge_type; using item_type = incidence_item; - [[nodiscard]] static auto in_edges(const impl_type& self, id_type vertex_id) { - std::vector in_edges; - for (id_type src_id = initial_id; src_id < self._list.size(); ++src_id) { - auto in_edges_view = - self._list[to_idx(src_id)] - | std::views::filter([tgt_id = vertex_id](const auto& item) { - return item.vertex_id == tgt_id; - }) - | std::views::transform([src_id](const auto& item) { - return incidence_item{src_id, item.edge_id}; - }); - in_edges.insert(in_edges.end(), in_edges_view.begin(), in_edges_view.end()); + // --- vertex modifiers --- + + static std::vector remove_vertex(impl_type& self, id_type vertex_id) { + const auto vertex_idx = to_idx(vertex_id); + std::vector removed_edges; + + // extract out-edges + for (const auto& item : self._list[vertex_idx]) + removed_edges.push_back(item.edge_id); + + // rebuild the graph (faster then shifting the entire data block for each removed edge) + typename impl_type::adjacency_storage_type new_list; + new_list.reserve_segments(self._list.size() - 1uz); + new_list.reserve_data(self._list.data_size() - self._list[vertex_idx].size()); + + std::vector buffer; + for (auto idx = 0uz; idx < self._list.size(); ++idx) { + if (idx == vertex_idx) + continue; + + buffer.clear(); + for (const auto& item : self._list[idx]) { + if (item.vertex_id == vertex_id) + removed_edges.push_back(item.edge_id); // remove in-edge + else + buffer.push_back(item); + } + new_list.push_back(buffer); } - return in_edges; + + self._list = std::move(new_list); + return removed_edges; } - [[nodiscard]] static size_type in_degree(const impl_type& self, id_type vertex_id) { - return static_cast( - std::ranges::count(self._list.data_view(), vertex_id, &item_type::vertex_id) - ); + // --- vertex getters --- + + [[nodiscard]] static auto neighbor_ids(const impl_type& self, id_type vertex_id) { + return util::concat(predecessor_ids(self, vertex_id), successor_ids(self, vertex_id)); } - [[nodiscard]] gl_attr_force_inline static size_type out_degree( + [[nodiscard]] static auto predecessor_ids(const impl_type& self, id_type vertex_id) { + return in_edges(self, vertex_id) | std::views::transform(&item_type::vertex_id); + } + + [[nodiscard]] gl_attr_force_inline static auto successor_ids( const impl_type& self, id_type vertex_id ) { - return self._list[to_idx(vertex_id)].size(); + return self._list[to_idx(vertex_id)] | std::views::transform(&item_type::vertex_id); } + // --- degree getters --- + [[nodiscard]] gl_attr_force_inline static size_type degree( const impl_type& self, id_type vertex_id ) { return in_degree(self, vertex_id) + out_degree(self, vertex_id); } - [[nodiscard]] static std::vector in_degree_map(const impl_type& self) { - std::vector in_degree_map(self._list.size(), 0uz); - for (const auto& item : self._list.data_view()) - ++in_degree_map[to_idx(item.vertex_id)]; - return in_degree_map; + [[nodiscard]] gl_attr_force_inline static size_type in_degree( + const impl_type& self, id_type vertex_id + ) { + return static_cast( + std::ranges::count(self._list.data_view(), vertex_id, &item_type::vertex_id) + ); } - [[nodiscard]] gl_attr_force_inline static std::vector out_degree_map( - const impl_type& self + [[nodiscard]] gl_attr_force_inline static size_type out_degree( + const impl_type& self, id_type vertex_id ) { - std::vector out_degree; - out_degree.reserve(self._list.size()); - for (auto idx = 0uz; idx < self._list.size(); ++idx) - out_degree.push_back(self._list.segment_size(idx)); - return out_degree; + return self._list[to_idx(vertex_id)].size(); } [[nodiscard]] static std::vector degree_map(const impl_type& self) { @@ -88,38 +109,25 @@ struct directed_flat_adjacency_list { return degree_map; } - static std::vector remove_vertex(impl_type& self, id_type vertex_id) { - const auto vertex_idx = to_idx(vertex_id); - std::vector removed_edges; - - // extract out-edges - for (const auto& item : self._list[vertex_idx]) - removed_edges.push_back(item.edge_id); - - // rebuild the graph (faster then shifting the entire data block for each removed edge) - typename impl_type::adjacency_storage_type new_list; - new_list.reserve_segments(self._list.size() - 1uz); - new_list.reserve_data(self._list.data_size() - self._list[vertex_idx].size()); - - std::vector buffer; - for (auto idx = 0uz; idx < self._list.size(); ++idx) { - if (idx == vertex_idx) - continue; - - buffer.clear(); - for (const auto& item : self._list[idx]) { - if (item.vertex_id == vertex_id) - removed_edges.push_back(item.edge_id); // remove in-edge - else - buffer.push_back(item); - } - new_list.push_back(buffer); - } + [[nodiscard]] static std::vector in_degree_map(const impl_type& self) { + std::vector in_degree_map(self._list.size(), 0uz); + for (const auto& item : self._list.data_view()) + ++in_degree_map[to_idx(item.vertex_id)]; + return in_degree_map; + } - self._list = std::move(new_list); - return removed_edges; + [[nodiscard]] gl_attr_force_inline static std::vector out_degree_map( + const impl_type& self + ) { + std::vector out_degree; + out_degree.reserve(self._list.size()); + for (auto idx = 0uz; idx < self._list.size(); ++idx) + out_degree.push_back(self._list.segment_size(idx)); + return out_degree; } + // --- edge modifiers --- + gl_attr_force_inline static void add_edge( impl_type& self, id_type edge_id, id_type source_id, id_type target_id ) { @@ -143,6 +151,24 @@ struct directed_flat_adjacency_list { const auto pos = static_cast(std::distance(segment.begin(), it)); self._list.erase(edge_src, pos); } + + // --- edge getters --- + + [[nodiscard]] static auto in_edges(const impl_type& self, id_type vertex_id) { + std::vector in_edges; + for (id_type src_id = initial_id; src_id < self._list.size(); ++src_id) { + auto in_edges_view = + self._list[to_idx(src_id)] + | std::views::filter([tgt_id = vertex_id](const auto& item) { + return item.vertex_id == tgt_id; + }) + | std::views::transform([src_id](const auto& item) { + return incidence_item{src_id, item.edge_id}; + }); + in_edges.insert(in_edges.end(), in_edges_view.begin(), in_edges_view.end()); + } + return in_edges; + } }; template AdjacencyList> @@ -153,24 +179,62 @@ struct undirected_flat_adjacency_list { using edge_type = typename impl_type::edge_type; using item_type = incidence_item; - [[nodiscard]] gl_attr_force_inline static auto in_edges( + // --- vertex modifiers --- + + static std::vector remove_vertex(impl_type& self, id_type vertex_id) { + const auto vertex_idx = to_idx(vertex_id); + + // all removed edges are stored in the vertex's list segment + auto removed_edges = + self._list[vertex_idx] | std::views::transform(&item_type::edge_id) + | std::ranges::to(); + + // rebuild the graph (faster then shifting the entire data block for each removed edge) + typename impl_type::adjacency_storage_type new_list; + new_list.reserve_segments(self._list.size() - 1uz); + const auto estimated_new_size = + self._list.data_size() - (self._list[vertex_idx].size() * 2uz); + new_list.reserve_data(estimated_new_size); + + std::vector buffer; + for (auto idx = 0uz; idx < self._list.size(); ++idx) { + if (idx == vertex_idx) + continue; + + buffer.clear(); + for (const auto& item : self._list[idx]) + if (item.vertex_id != vertex_id) + buffer.push_back(item); + + new_list.push_back(buffer); + } + + self._list = std::move(new_list); + return removed_edges; + } + + // --- vertex getters --- + + [[nodiscard]] gl_attr_force_inline static auto neighbor_ids( const impl_type& self, id_type vertex_id ) { - return self._list[to_idx(vertex_id)]; + return self._list[to_idx(vertex_id)] | std::views::transform(&item_type::vertex_id); } - [[nodiscard]] gl_attr_force_inline static size_type in_degree( + [[nodiscard]] gl_attr_force_inline static auto predecessor_ids( const impl_type& self, id_type vertex_id ) { - return degree(self, vertex_id); + return neighbor_ids(self, vertex_id); } - [[nodiscard]] gl_attr_force_inline static size_type out_degree( + [[nodiscard]] gl_attr_force_inline static auto successor_ids( const impl_type& self, id_type vertex_id ) { - return degree(self, vertex_id); + return neighbor_ids(self, vertex_id); } + // --- degree getters --- + [[nodiscard]] static size_type degree(const impl_type& self, id_type vertex_id) { size_type degree = 0uz; for (const auto& item : self._list[to_idx(vertex_id)]) @@ -178,16 +242,16 @@ struct undirected_flat_adjacency_list { return degree; } - [[nodiscard]] gl_attr_force_inline static std::vector in_degree_map( - const impl_type& self + [[nodiscard]] gl_attr_force_inline static size_type in_degree( + const impl_type& self, id_type vertex_id ) { - return degree_map(self); + return degree(self, vertex_id); } - [[nodiscard]] gl_attr_force_inline static std::vector out_degree_map( - const impl_type& self + [[nodiscard]] gl_attr_force_inline static size_type out_degree( + const impl_type& self, id_type vertex_id ) { - return degree_map(self); + return degree(self, vertex_id); } [[nodiscard]] static std::vector degree_map(const impl_type& self) { @@ -198,38 +262,20 @@ struct undirected_flat_adjacency_list { return degree_map; } - static std::vector remove_vertex(impl_type& self, id_type vertex_id) { - const auto vertex_idx = to_idx(vertex_id); - - // all removed edges are stored in the vertex's list segment - auto removed_edges = - self._list[vertex_idx] | std::views::transform(&item_type::edge_id) - | std::ranges::to(); - - // rebuild the graph (faster then shifting the entire data block for each removed edge) - typename impl_type::adjacency_storage_type new_list; - new_list.reserve_segments(self._list.size() - 1uz); - const auto estimated_new_size = - self._list.data_size() - (self._list[vertex_idx].size() * 2uz); - new_list.reserve_data(estimated_new_size); - - std::vector buffer; - for (auto idx = 0uz; idx < self._list.size(); ++idx) { - if (idx == vertex_idx) - continue; - - buffer.clear(); - for (const auto& item : self._list[idx]) - if (item.vertex_id != vertex_id) - buffer.push_back(item); - - new_list.push_back(buffer); - } + [[nodiscard]] gl_attr_force_inline static std::vector in_degree_map( + const impl_type& self + ) { + return degree_map(self); + } - self._list = std::move(new_list); - return removed_edges; + [[nodiscard]] gl_attr_force_inline static std::vector out_degree_map( + const impl_type& self + ) { + return degree_map(self); } + // --- edge modifiers --- + static void add_edge(impl_type& self, id_type edge_id, id_type source_id, id_type target_id) { self._list.push_back(to_idx(source_id), {target_id, edge_id}); if (target_id != source_id) @@ -269,6 +315,14 @@ struct undirected_flat_adjacency_list { self._list.erase(tgt_idx, pos); } } + + // --- edge getters --- + + [[nodiscard]] gl_attr_force_inline static auto in_edges( + const impl_type& self, id_type vertex_id + ) { + return self._list[to_idx(vertex_id)]; + } }; template AdjacencyList> diff --git a/include/gl/impl/specialized/flat_adjacency_matrix.hpp b/include/gl/impl/specialized/flat_adjacency_matrix.hpp index a0aa5dc..73e4184 100644 --- a/include/gl/impl/specialized/flat_adjacency_matrix.hpp +++ b/include/gl/impl/specialized/flat_adjacency_matrix.hpp @@ -5,6 +5,7 @@ #pragma once #include "gl/attributes/diagnostics.hpp" +#include "gl/attributes/force_inline.hpp" #include "gl/constants.hpp" #include "gl/decl/impl_tags.hpp" #include "gl/graph_traits.hpp" @@ -60,10 +61,20 @@ struct directed_flat_adjacency_matrix { using vertex_type = typename impl_type::vertex_type; using edge_type = typename impl_type::edge_type; - static void init(impl_type& self, size_type n_vertices) { + // --- general --- + + gl_attr_force_inline static void init(impl_type& self, size_type n_vertices) { self._matrix = storage_type(n_vertices, n_vertices, invalid_id); } + gl_attr_force_inline static id_type get_entry( + const impl_type& self, id_type source_id, id_type target_id + ) { + return self._matrix[to_idx(source_id), to_idx(target_id)]; + } + + // --- vertex modifiers --- + static void add_vertex(impl_type& self) { const auto new_size = self._matrix.n_rows() + 1uz; self._matrix.resize(new_size, new_size, invalid_id); @@ -74,21 +85,62 @@ struct directed_flat_adjacency_matrix { self._matrix.resize(new_size, new_size, invalid_id); } - [[nodiscard]] gl_attr_force_inline static size_type in_degree( + static std::vector remove_vertex(impl_type& self, id_type vertex_id) { + const auto vertex_idx = to_idx(vertex_id); + std::vector removed_edges; + removed_edges.reserve(self._matrix.size()); + + // extract out-edges + for (auto edge_id : self._matrix[vertex_idx]) + if (edge_id != invalid_id) + removed_edges.push_back(edge_id); + + // extract in-edges + const auto col = self._matrix.col(vertex_idx); + for (auto r_idx = 0uz; r_idx < self._matrix.n_rows(); ++r_idx) { + if (r_idx == vertex_idx) + continue; + + const auto edge_id = col[to_diff(r_idx)]; + if (edge_id != invalid_id) + removed_edges.push_back(edge_id); + } + + // elegantly remove from the 2D grid + self._matrix.erase_row(vertex_idx); + self._matrix.erase_col(vertex_idx); + + return removed_edges; + } + + // --- vertex getters --- + + [[nodiscard]] gl_attr_force_inline static auto neighbor_ids( const impl_type& self, id_type vertex_id ) { - return static_cast(std::ranges::count_if( - self._matrix.col(to_idx(vertex_id)), [](auto edge_id) { return edge_id != invalid_id; } - )); + return util::concat(predecessor_ids(self, vertex_id), successor_ids(self, vertex_id)); } - [[nodiscard]] gl_attr_force_inline static size_type out_degree( + [[nodiscard]] gl_attr_force_inline static auto predecessor_ids( const impl_type& self, id_type vertex_id ) { - const auto row = self._matrix[to_idx(vertex_id)]; - return row.size() - static_cast(std::ranges::count(row, invalid_id_v)); + return std::views::iota(initial_id, static_cast(self._matrix.n_rows())) + | std::views::filter([&self, v_idx = to_idx(vertex_id)](const auto r_id) { + return self._matrix[to_idx(r_id), v_idx] != invalid_id; + }); + } + + [[nodiscard]] gl_attr_force_inline static auto successor_ids( + const impl_type& self, id_type vertex_id + ) { + return std::views::iota(initial_id, static_cast(self._matrix.n_cols())) + | std::views::filter([&self, v_idx = to_idx(vertex_id)](const auto c_id) { + return self._matrix[v_idx, to_idx(c_id)] != invalid_id; + }); } + // --- degree getters --- + [[nodiscard]] gl_attr_force_inline static size_type degree( const impl_type& self, id_type vertex_id ) { @@ -105,21 +157,19 @@ struct directed_flat_adjacency_matrix { return deg; } - [[nodiscard]] static std::vector in_degree_map(const impl_type& self) { - std::vector in_degree_map(self._matrix.n_rows(), 0uz); - - for (const auto row : self._matrix.rows()) - for (auto [target_id, edge_id] : std::views::enumerate(row)) - in_degree_map[static_cast(target_id)] += - static_cast(edge_id != invalid_id); - - return in_degree_map; + [[nodiscard]] gl_attr_force_inline static size_type in_degree( + const impl_type& self, id_type vertex_id + ) { + return static_cast(std::ranges::count_if( + self._matrix.col(to_idx(vertex_id)), [](auto edge_id) { return edge_id != invalid_id; } + )); } - [[nodiscard]] static std::vector out_degree_map(const impl_type& self) { - return std::views::iota(initial_id_v, static_cast(self._matrix.n_rows())) - | std::views::transform([&](id_type id) { return out_degree(self, id); }) - | std::ranges::to(); + [[nodiscard]] gl_attr_force_inline static size_type out_degree( + const impl_type& self, id_type vertex_id + ) { + const auto row = self._matrix[to_idx(vertex_id)]; + return row.size() - static_cast(std::ranges::count(row, invalid_id_v)); } [[nodiscard]] static std::vector degree_map(const impl_type& self) { @@ -137,38 +187,25 @@ struct directed_flat_adjacency_matrix { return degree_map; } - static std::vector remove_vertex(impl_type& self, id_type vertex_id) { - const auto vertex_idx = to_idx(vertex_id); - std::vector removed_edges; - removed_edges.reserve(self._matrix.size()); - - // extract out-edges - for (auto edge_id : self._matrix[vertex_idx]) - if (edge_id != invalid_id) - removed_edges.push_back(edge_id); - - // extract in-edges - const auto col = self._matrix.col(vertex_idx); - for (auto r_idx = 0uz; r_idx < self._matrix.n_rows(); ++r_idx) { - if (r_idx == vertex_idx) - continue; - - const auto edge_id = col[to_diff(r_idx)]; - if (edge_id != invalid_id) - removed_edges.push_back(edge_id); - } + [[nodiscard]] static std::vector in_degree_map(const impl_type& self) { + std::vector in_degree_map(self._matrix.n_rows(), 0uz); - // elegantly remove from the 2D grid - self._matrix.erase_row(vertex_idx); - self._matrix.erase_col(vertex_idx); + for (const auto row : self._matrix.rows()) + for (auto [target_id, edge_id] : std::views::enumerate(row)) + in_degree_map[static_cast(target_id)] += + static_cast(edge_id != invalid_id); - return removed_edges; + return in_degree_map; } - static id_type get_entry(const impl_type& self, id_type source_id, id_type target_id) { - return self._matrix[to_idx(source_id), to_idx(target_id)]; + [[nodiscard]] static std::vector out_degree_map(const impl_type& self) { + return std::views::iota(initial_id_v, static_cast(self._matrix.n_rows())) + | std::views::transform([&](id_type id) { return out_degree(self, id); }) + | std::ranges::to(); } + // --- edge modifiers --- + static inline void add_edge( impl_type& self, id_type edge_id, id_type source_id, id_type target_id ) { @@ -190,7 +227,7 @@ struct directed_flat_adjacency_matrix { matrix_source_row[to_diff(target_id)] = edge_id; } - static inline void remove_edge(impl_type& self, const edge_type& edge) { + gl_attr_force_inline static void remove_edge(impl_type& self, const edge_type& edge) { detail::strict_get(self._matrix, edge) = invalid_id; } }; @@ -205,10 +242,20 @@ struct undirected_flat_adjacency_matrix { using vertex_type = typename impl_type::vertex_type; using edge_type = typename impl_type::edge_type; - static void init(impl_type& self, size_type n_vertices) { + // --- general --- + + gl_attr_force_inline static void init(impl_type& self, size_type n_vertices) { self._matrix = storage_type(n_vertices, n_vertices, invalid_id); } + gl_attr_force_inline static id_type get_entry( + const impl_type& self, id_type source_id, id_type target_id + ) { + return self._matrix[to_idx(source_id), to_idx(target_id)]; + } + + // --- vertex modifiers --- + static void add_vertex(impl_type& self) { const auto new_size = self._matrix.n_rows() + 1uz; self._matrix.resize(new_size, new_size, invalid_id); @@ -219,18 +266,45 @@ struct undirected_flat_adjacency_matrix { self._matrix.resize(new_size, new_size, invalid_id); } - [[nodiscard]] gl_attr_force_inline static size_type in_degree( + static std::vector remove_vertex(impl_type& self, id_type vertex_id) { + const auto vertex_idx = to_idx(vertex_id); + + const auto removed_edges = + self._matrix[vertex_idx] + | std::views::filter([](auto edge_id) { return edge_id != invalid_id; }) + | std::ranges::to(); + + self._matrix.erase_row(vertex_idx); + self._matrix.erase_col(vertex_idx); + + return removed_edges; + } + + // --- vertex getters --- + + [[nodiscard]] gl_attr_force_inline static auto neighbor_ids( const impl_type& self, id_type vertex_id ) { - return degree(self, vertex_id); + return std::views::iota(initial_id, static_cast(self._matrix.n_cols())) + | std::views::filter([&self, v_idx = to_idx(vertex_id)](const auto c_id) { + return self._matrix[v_idx, to_idx(c_id)] != invalid_id; + }); } - [[nodiscard]] gl_attr_force_inline static size_type out_degree( + [[nodiscard]] gl_attr_force_inline static auto predecessor_ids( const impl_type& self, id_type vertex_id ) { - return degree(self, vertex_id); + return neighbor_ids(self, vertex_id); } + [[nodiscard]] gl_attr_force_inline static auto successor_ids( + const impl_type& self, id_type vertex_id + ) { + return neighbor_ids(self, vertex_id); + } + + // --- degree getters --- + [[nodiscard]] gl_attr_force_inline static size_type degree( const impl_type& self, id_type vertex_id ) { @@ -241,16 +315,16 @@ struct undirected_flat_adjacency_matrix { + static_cast(self._matrix[vertex_idx, vertex_idx] != invalid_id); } - [[nodiscard]] gl_attr_force_inline static std::vector in_degree_map( - const impl_type& self + [[nodiscard]] gl_attr_force_inline static size_type in_degree( + const impl_type& self, id_type vertex_id ) { - return degree_map(self); + return degree(self, vertex_id); } - [[nodiscard]] gl_attr_force_inline static std::vector out_degree_map( - const impl_type& self + [[nodiscard]] gl_attr_force_inline static size_type out_degree( + const impl_type& self, id_type vertex_id ) { - return degree_map(self); + return degree(self, vertex_id); } [[nodiscard]] static std::vector degree_map(const impl_type& self) { @@ -268,24 +342,20 @@ struct undirected_flat_adjacency_matrix { return degree_map; } - static std::vector remove_vertex(impl_type& self, id_type vertex_id) { - const auto vertex_idx = to_idx(vertex_id); - - const auto removed_edges = - self._matrix[vertex_idx] - | std::views::filter([](auto edge_id) { return edge_id != invalid_id; }) - | std::ranges::to(); - - self._matrix.erase_row(vertex_idx); - self._matrix.erase_col(vertex_idx); - - return removed_edges; + [[nodiscard]] gl_attr_force_inline static std::vector in_degree_map( + const impl_type& self + ) { + return degree_map(self); } - static id_type get_entry(const impl_type& self, id_type source_id, id_type target_id) { - return self._matrix[to_idx(source_id), to_idx(target_id)]; + [[nodiscard]] gl_attr_force_inline static std::vector out_degree_map( + const impl_type& self + ) { + return degree_map(self); } + // --- edge modifiers --- + static void add_edge(impl_type& self, id_type edge_id, id_type source_id, id_type target_id) { detail::check_edge_override(self._matrix, source_id, target_id); From 948c7f7df884e73ef773d2d4d57eed9ffb08350c Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Fri, 10 Apr 2026 20:51:21 +0200 Subject: [PATCH 2/9] tests layout refinement --- tests/source/gl/test_adjacency_list.cpp | 567 ++++++++++--------- tests/source/gl/test_adjacency_matrix.cpp | 648 +++++++++++----------- 2 files changed, 633 insertions(+), 582 deletions(-) diff --git a/tests/source/gl/test_adjacency_list.cpp b/tests/source/gl/test_adjacency_list.cpp index 9451ad4..aea8e61 100644 --- a/tests/source/gl/test_adjacency_list.cpp +++ b/tests/source/gl/test_adjacency_list.cpp @@ -29,6 +29,8 @@ struct test_adjacency_list { TEST_CASE_TEMPLATE_DEFINE("common adjacency list tests", SutType, common_adj_list_template) { test_adjacency_list fixture; + // --- general --- + SUBCASE("should be initialized with no vertices and no edges by default") { SutType sut{}; CHECK_EQ(fixture.size(sut), 0uz); @@ -43,6 +45,8 @@ TEST_CASE_TEMPLATE_DEFINE("common adjacency list tests", SutType, common_adj_lis })); } + // --- vertex modifiers --- + SUBCASE("add_vertex should properly extend the current adjacency list") { SutType sut{}; constexpr gl::size_type target_n_vertices = constants::n_elements; @@ -68,7 +72,9 @@ TEST_CASE_TEMPLATE_DEFINE("common adjacency list tests", SutType, common_adj_lis })); } - SUBCASE("equality operator should correctly comparge matrices") { + // --- comparison --- + + SUBCASE("equality operator should correctly comparge lists") { SutType sut1(constants::n_elements); sut1.add_edge(fixture.next_edge_id++, constants::v1_id, constants::v2_id); sut1.add_edge(fixture.next_edge_id++, constants::v2_id, constants::v3_id); @@ -107,7 +113,7 @@ TEST_CASE_TEMPLATE_INSTANTIATE( namespace { -constexpr gl::size_type n_incident_edges_for_fully_connected_vertex = constants::n_elements - 1uz; +constexpr gl::size_type n_inc_edged_for_fully_connected_vertex = constants::n_elements - 1uz; } // namespace @@ -136,11 +142,11 @@ struct test_directed_adjacency_list : public test_adjacency_list { if (no_loops) REQUIRE(std::ranges::all_of(get(sut), [&](const auto& adj_items) { - return adj_items.size() == n_incident_edges_for_fully_connected_vertex; + return adj_items.size() == n_inc_edged_for_fully_connected_vertex; })); else REQUIRE(std::ranges::all_of(get(sut), [&](const auto& adj_items) { - return adj_items.size() == n_incident_edges_for_fully_connected_vertex + 1uz; + return adj_items.size() == n_inc_edged_for_fully_connected_vertex + 1uz; })); } @@ -165,25 +171,156 @@ TEST_CASE_TEMPLATE_DEFINE("directed adjacency list tests", SutType, directed_adj fixture.init_complete_graph(no_loops); }; + // --- vertex modifiers --- + + SUBCASE("remove_vertex should remove the given vertex and all edges incident with it") { + const auto edge1 = add_edge(constants::v1_id, constants::v2_id); + const auto edge3 = add_edge(constants::v2_id, constants::v1_id); + const auto edge2 = add_edge(constants::v1_id, constants::v3_id); + const auto edge4 = add_edge(constants::v3_id, constants::v1_id); + + const auto edge5 = add_edge(constants::v2_id, constants::v3_id); + const auto edge6 = add_edge(constants::v3_id, constants::v2_id); + + const auto removed_vertex_id = constants::v1_id; + const auto removed_edge_ids = sut.remove_vertex(removed_vertex_id); + + constexpr gl::size_type n_removed_edges = 4uz; + REQUIRE_EQ(removed_edge_ids.size(), n_removed_edges); + for (const auto edge_id : {edge1.id(), edge2.id(), edge3.id(), edge4.id()}) + CHECK(std::ranges::contains(removed_edge_ids, edge_id)); + for (const auto edge_id : {edge5.id(), edge6.id()}) + CHECK_FALSE(std::ranges::contains(removed_edge_ids, edge_id)); + + // Check the structure of the graph considering the aligned IDs + CHECK_EQ(size(sut), constants::n_elements - 1uz); + + const auto inc_edges_1 = sut.incident_edges(constants::v1_id); + CHECK_EQ(inc_edges_1.size(), 1uz); + CHECK_EQ(inc_edges_1.front().id(), edge5.id() - n_removed_edges); + CHECK_EQ(inc_edges_1.front().target(), constants::v2_id); + + const auto inc_edges_2 = sut.incident_edges(constants::v2_id); + CHECK_EQ(inc_edges_2.size(), 1uz); + CHECK_EQ(inc_edges_2.front().id(), edge6.id() - n_removed_edges); + CHECK_EQ(inc_edges_2.front().target(), constants::v1_id); + } + + // --- vertex getters --- + + // --- degree getters --- + + SUBCASE("degree should return the number of edges incident with the given vertex") { + init_complete_graph(); + const auto deg_proj = [&sut](const auto vertex_id) { return sut.degree(vertex_id); }; + + CHECK(std::ranges::all_of( + constants::vertex_id_view, + [](const auto deg) { return deg == 2uz * n_inc_edged_for_fully_connected_vertex; }, + deg_proj + )); + + add_edge(constants::v1_id, constants::v1_id); + + CHECK_EQ(deg_proj(constants::v1_id), 2uz * (n_inc_edged_for_fully_connected_vertex + 1uz)); + } + + SUBCASE("in/out_degree should return the number of edges incident {to/from} the given vertex") { + init_complete_graph(); + + std::function deg_proj; + + SUBCASE("in_degree") { + deg_proj = [&sut](const auto vertex_id) { return sut.in_degree(vertex_id); }; + } + + SUBCASE("out_degree") { + deg_proj = [&sut](const auto vertex_id) { return sut.out_degree(vertex_id); }; + } + + CAPTURE(deg_proj); + + CHECK(std::ranges::all_of( + constants::vertex_id_view, + [](const auto deg) { return deg == n_inc_edged_for_fully_connected_vertex; }, + deg_proj + )); + + add_edge(constants::v1_id, constants::v1_id); + + CHECK_EQ(deg_proj(constants::v1_id), n_inc_edged_for_fully_connected_vertex + 1uz); + } + + SUBCASE("degree_map should return a map of the numbers of edges incident with the " + "corresponding vertices") { + init_complete_graph(false); + const auto expected_deg = constants::n_elements * 2uz; + + std::vector degree_map = sut.degree_map(); + + REQUIRE_EQ(degree_map.size(), constants::n_elements); + CHECK_EQ(std::ranges::count(degree_map, expected_deg), constants::n_elements); + } + + SUBCASE("in/out_degree_map should return a map of numbers of edges incident {to/from} the " + "corresponding vertices") { + init_complete_graph(false); + const auto expected_deg = constants::n_elements; + + std::vector degree_map; + + SUBCASE("in_degree") { + degree_map = sut.in_degree_map(); + } + + SUBCASE("out_degree") { + degree_map = sut.out_degree_map(); + } + + CAPTURE(degree_map); + + REQUIRE_EQ(degree_map.size(), constants::n_elements); + CHECK_EQ(std::ranges::count(degree_map, expected_deg), constants::n_elements); + } + + // --- edge modifiers --- + SUBCASE("add_edge should add the edge only to the source vertex list") { const auto new_edge = add_edge(constants::v1_id, constants::v2_id); REQUIRE(new_edge.is_incident_from(constants::v1_id)); REQUIRE(new_edge.is_incident_to(constants::v2_id)); - const auto incident_edges_1 = sut.incident_edges(constants::v1_id); - CHECK_EQ(incident_edges_1.size(), 1uz); + const auto inc_edged_1 = sut.incident_edges(constants::v1_id); + CHECK_EQ(inc_edged_1.size(), 1uz); CHECK_EQ(sut.incident_edges(constants::v2_id).size(), 0uz); - const auto& new_edge_extracted = incident_edges_1[0uz]; + const auto& new_edge_extracted = inc_edged_1[0uz]; CHECK_EQ(new_edge_extracted, new_edge); } - SUBCASE("at should return the incident edges of a vertex") { - init_complete_graph(); - for (const auto vertex_id : std::views::iota(constants::v1_id, constants::n_elements)) - CHECK(std::ranges::equal(sut.at(vertex_id), sut.incident_edges(vertex_id))); + SUBCASE("remove_edge should throw when an edge is invalid") { + // not existing edge between valid vertices + const edge_type not_existing_edge{gl::invalid_id, constants::v1_id, constants::v2_id}; + CHECK_THROWS_AS(sut.remove_edge(not_existing_edge), std::invalid_argument); + } + + SUBCASE("remove_edge should remove the edge from the source vertex's list") { + fully_connect_vertex(constants::v1_id); + + auto incident_edges = sut.incident_edges(constants::v1_id); + REQUIRE_EQ(incident_edges.size(), n_inc_edged_for_fully_connected_vertex); + + const auto& edge_to_remove = incident_edges[0uz]; + sut.remove_edge(edge_to_remove); + + incident_edges = sut.incident_edges(constants::v1_id); + REQUIRE_EQ(incident_edges.size(), n_inc_edged_for_fully_connected_vertex - 1uz); + // validate that the incident edges list has been properly aligned + CHECK_EQ(std::ranges::find(incident_edges, edge_to_remove), incident_edges.end()); } + // --- edge getters --- + SUBCASE("has_edge(id, id) should return true if there is an edge in the graph which connects " "vertices with the given ids in the specified direction") { add_edge(constants::v1_id, constants::v2_id); @@ -244,27 +381,6 @@ TEST_CASE_TEMPLATE_DEFINE("directed adjacency list tests", SutType, directed_adj CHECK(sut.get_edges(constants::v2_id, constants::v2_id).empty()); } - SUBCASE("remove_edge should throw when an edge is invalid") { - // not existing edge between valid vertices - const edge_type not_existing_edge{gl::invalid_id, constants::v1_id, constants::v2_id}; - CHECK_THROWS_AS(sut.remove_edge(not_existing_edge), std::invalid_argument); - } - - SUBCASE("remove_edge should remove the edge from the source vertex's list") { - fully_connect_vertex(constants::v1_id); - - auto incident_edges = sut.incident_edges(constants::v1_id); - REQUIRE_EQ(incident_edges.size(), n_incident_edges_for_fully_connected_vertex); - - const auto& edge_to_remove = incident_edges[0uz]; - sut.remove_edge(edge_to_remove); - - incident_edges = sut.incident_edges(constants::v1_id); - REQUIRE_EQ(incident_edges.size(), n_incident_edges_for_fully_connected_vertex - 1uz); - // validate that the incident edges list has been properly aligned - CHECK_EQ(std::ranges::find(incident_edges, edge_to_remove), incident_edges.end()); - } - SUBCASE("in_edges should return edges where the vertex is the target") { const auto edge1 = add_edge(constants::v2_id, constants::v1_id); const auto edge2 = add_edge(constants::v3_id, constants::v1_id); @@ -287,114 +403,12 @@ TEST_CASE_TEMPLATE_DEFINE("directed adjacency list tests", SutType, directed_adj CHECK(std::ranges::contains(out_edges, edge2)); } - SUBCASE("{in/out}_degree should return the number of edges incident {to/from} the given vertex" - ) { - init_complete_graph(); - - std::function deg_proj; - - SUBCASE("in_degree") { - deg_proj = [&sut](const auto vertex_id) { return sut.in_degree(vertex_id); }; - } - - SUBCASE("out_degree") { - deg_proj = [&sut](const auto vertex_id) { return sut.out_degree(vertex_id); }; - } - - CAPTURE(deg_proj); - - CHECK(std::ranges::all_of( - constants::vertex_id_view, - [](const auto deg) { return deg == n_incident_edges_for_fully_connected_vertex; }, - deg_proj - )); - - add_edge(constants::v1_id, constants::v1_id); - - CHECK_EQ(deg_proj(constants::v1_id), n_incident_edges_for_fully_connected_vertex + 1uz); - } + // --- access operators --- - SUBCASE("degree should return the number of edges incident with the given vertex") { + SUBCASE("at should return the incident edges of a vertex") { init_complete_graph(); - const auto deg_proj = [&sut](const auto vertex_id) { return sut.degree(vertex_id); }; - - CHECK(std::ranges::all_of( - constants::vertex_id_view, - [](const auto deg) { return deg == 2uz * n_incident_edges_for_fully_connected_vertex; }, - deg_proj - )); - - add_edge(constants::v1_id, constants::v1_id); - - CHECK_EQ( - deg_proj(constants::v1_id), 2uz * (n_incident_edges_for_fully_connected_vertex + 1uz) - ); - } - - SUBCASE("{in/out}_degree_map should return a map of numbers of edges incident {to/from} the " - "corresponding vertices") { - init_complete_graph(false); - const auto expected_deg = constants::n_elements; - - std::vector degree_map; - - SUBCASE("in_degree") { - degree_map = sut.in_degree_map(); - } - - SUBCASE("out_degree") { - degree_map = sut.out_degree_map(); - } - - CAPTURE(degree_map); - - REQUIRE_EQ(degree_map.size(), constants::n_elements); - CHECK_EQ(std::ranges::count(degree_map, expected_deg), constants::n_elements); - } - - SUBCASE("degree_map should return a map of the numbers of edges incident with the " - "corresponding " - "vertices") { - init_complete_graph(false); - const auto expected_deg = constants::n_elements * 2uz; - - std::vector degree_map = sut.degree_map(); - - REQUIRE_EQ(degree_map.size(), constants::n_elements); - CHECK_EQ(std::ranges::count(degree_map, expected_deg), constants::n_elements); - } - - SUBCASE("remove_vertex should remove the given vertex and all edges incident with it") { - const auto edge1 = add_edge(constants::v1_id, constants::v2_id); - const auto edge3 = add_edge(constants::v2_id, constants::v1_id); - const auto edge2 = add_edge(constants::v1_id, constants::v3_id); - const auto edge4 = add_edge(constants::v3_id, constants::v1_id); - - const auto edge5 = add_edge(constants::v2_id, constants::v3_id); - const auto edge6 = add_edge(constants::v3_id, constants::v2_id); - - const auto removed_vertex_id = constants::v1_id; - const auto removed_edge_ids = sut.remove_vertex(removed_vertex_id); - - constexpr gl::size_type n_removed_edges = 4uz; - REQUIRE_EQ(removed_edge_ids.size(), n_removed_edges); - for (const auto edge_id : {edge1.id(), edge2.id(), edge3.id(), edge4.id()}) - CHECK(std::ranges::contains(removed_edge_ids, edge_id)); - for (const auto edge_id : {edge5.id(), edge6.id()}) - CHECK_FALSE(std::ranges::contains(removed_edge_ids, edge_id)); - - // Check the structure of the graph considering the aligned IDs - CHECK_EQ(size(sut), constants::n_elements - 1uz); - - const auto adj_edges_1 = sut.incident_edges(constants::v1_id); - CHECK_EQ(adj_edges_1.size(), 1uz); - CHECK_EQ(adj_edges_1.front().id(), edge5.id() - n_removed_edges); - CHECK_EQ(adj_edges_1.front().target(), constants::v2_id); - - const auto adj_edges_2 = sut.incident_edges(constants::v2_id); - CHECK_EQ(adj_edges_2.size(), 1uz); - CHECK_EQ(adj_edges_2.front().id(), edge6.id() - n_removed_edges); - CHECK_EQ(adj_edges_2.front().target(), constants::v1_id); + for (const auto vertex_id : std::views::iota(constants::v1_id, constants::n_elements)) + CHECK(std::ranges::equal(sut.at(vertex_id), sut.incident_edges(vertex_id))); } } @@ -433,18 +447,18 @@ struct test_undirected_adjacency_list : public test_adjacency_list { if (no_loops) REQUIRE(std::ranges::all_of(get(sut), [&](const auto& adj_items) { - return adj_items.size() == n_incident_edges_for_fully_connected_vertex; + return adj_items.size() == n_inc_edged_for_fully_connected_vertex; })); else REQUIRE(std::ranges::all_of(get(sut), [&](const auto& adj_items) { - return adj_items.size() == n_incident_edges_for_fully_connected_vertex + 1uz; + return adj_items.size() == n_inc_edged_for_fully_connected_vertex + 1uz; })); } sut_type sut{constants::n_elements}; const gl::size_type n_unique_edges_in_full_graph = - (n_incident_edges_for_fully_connected_vertex * constants::n_elements) / 2uz; + (n_inc_edged_for_fully_connected_vertex * constants::n_elements) / 2uz; }; TEST_CASE_TEMPLATE_DEFINE("undirected adjacency list tests", SutType, undirected_adj_list_template) { @@ -465,21 +479,119 @@ TEST_CASE_TEMPLATE_DEFINE("undirected adjacency list tests", SutType, undirected fixture.init_complete_graph(no_loops); }; + // --- vertex modifiers --- + + SUBCASE("remove_vertex should remove the given vertex and all edges incident with it") { + const auto edge1 = add_edge(constants::v1_id, constants::v2_id); + const auto edge3 = add_edge(constants::v2_id, constants::v1_id); + const auto edge2 = add_edge(constants::v1_id, constants::v3_id); + const auto edge4 = add_edge(constants::v3_id, constants::v1_id); + + const auto edge5 = add_edge(constants::v2_id, constants::v3_id); + + const auto removed_vertex_id = 0uz; + const auto removed_edge_ids = sut.remove_vertex(removed_vertex_id); + + constexpr gl::size_type n_removed_edges = 4uz; + REQUIRE_EQ(removed_edge_ids.size(), n_removed_edges); + for (const auto edge_id : {edge1.id(), edge2.id(), edge3.id(), edge4.id()}) + CHECK(std::ranges::contains(removed_edge_ids, edge_id)); + CHECK_FALSE(std::ranges::contains(removed_edge_ids, edge5.id())); + + // Check the structure of the graph considering the aligned IDs + CHECK_EQ(size(sut), constants::n_elements - 1uz); + + const auto inc_edges_1 = sut.incident_edges(constants::v1_id); + CHECK_EQ(inc_edges_1.size(), 1uz); + CHECK_EQ(inc_edges_1.front().id(), edge5.id() - n_removed_edges); + CHECK_EQ(inc_edges_1.front().target(), constants::v2_id); + + const auto inc_edges_2 = sut.incident_edges(constants::v2_id); + CHECK_EQ(inc_edges_2.size(), 1uz); + CHECK_EQ(inc_edges_2.front().id(), edge5.id() - n_removed_edges); + CHECK_EQ(inc_edges_2.front().target(), constants::v1_id); + } + + // --- vertex getters --- + + // --- degree getters --- + + SUBCASE("-/in/out__degree should return the number of edges incident with/to/from the given " + "vertex") { + init_complete_graph(); + + std::function deg_proj; + + SUBCASE("degree") { + deg_proj = [&sut](const auto vertex_id) { return sut.degree(vertex_id); }; + } + + SUBCASE("in_degree") { + deg_proj = [&sut](const auto vertex_id) { return sut.in_degree(vertex_id); }; + } + + SUBCASE("out_degree") { + deg_proj = [&sut](const auto vertex_id) { return sut.out_degree(vertex_id); }; + } + + CAPTURE(deg_proj); + + CHECK(std::ranges::all_of( + constants::vertex_id_view, + [](const auto deg) { return deg == n_inc_edged_for_fully_connected_vertex; }, + deg_proj + )); + + add_edge(constants::v1_id, constants::v1_id); + + CHECK_EQ( + deg_proj(constants::v1_id), + n_inc_edged_for_fully_connected_vertex + 2uz // loops counted twice + ); + } + + SUBCASE("-/in/out_degree_map should return a map of numbers of edges incident with/to/from " + "the corresponding vertices") { + init_complete_graph(false); + const auto expected_deg = constants::n_elements + 1; + + std::vector degree_map; + + SUBCASE("in_degree") { + degree_map = sut.in_degree_map(); + } + + SUBCASE("out_degree") { + degree_map = sut.out_degree_map(); + } + + SUBCASE("degree") { + degree_map = sut.degree_map(); + } + + CAPTURE(degree_map); + + REQUIRE_EQ(degree_map.size(), constants::n_elements); + CHECK_EQ(std::ranges::count(degree_map, expected_deg), constants::n_elements); + } + + // --- edge modifiers --- + SUBCASE("add_edge should add the edge to the lists of both vertices") { const auto new_edge = add_edge(constants::v1_id, constants::v2_id); REQUIRE(new_edge.is_incident_from(constants::v1_id)); REQUIRE(new_edge.is_incident_to(constants::v2_id)); - const auto incident_edges_1 = sut.incident_edges(constants::v1_id); - const auto incident_edges_2 = sut.incident_edges(constants::v2_id); + const auto inc_edged_1 = sut.incident_edges(constants::v1_id); + const auto inc_edged_2 = sut.incident_edges(constants::v2_id); - REQUIRE_EQ(incident_edges_1.size(), 1uz); - REQUIRE_EQ(incident_edges_2.size(), 1uz); + REQUIRE_EQ(inc_edged_1.size(), 1uz); + REQUIRE_EQ(inc_edged_2.size(), 1uz); - const auto& new_edge_extracted_1 = incident_edges_1[0uz]; + const auto& new_edge_extracted_1 = inc_edged_1[0uz]; CHECK_EQ(new_edge_extracted_1, new_edge); - const auto& new_edge_extracted_2 = incident_edges_2[0uz]; + const auto& new_edge_extracted_2 = inc_edged_2[0uz]; CHECK_EQ(new_edge_extracted_2, new_edge); } @@ -495,12 +607,38 @@ TEST_CASE_TEMPLATE_DEFINE("undirected adjacency list tests", SutType, undirected CHECK_EQ(new_edge_extracted_1, new_edge); } - SUBCASE("at should return the incident edges of a vertex") { - init_complete_graph(); - for (const auto vertex_id : std::views::iota(constants::v1_id, constants::n_elements)) - CHECK(std::ranges::equal(sut.at(vertex_id), sut.incident_edges(vertex_id))); + SUBCASE("remove_edge should throw when an edge is invalid") { + // not existing edge between valid vertices + const edge_type not_existing_edge{gl::invalid_id, constants::v1_id, constants::v2_id}; + CHECK_THROWS_AS(sut.remove_edge(not_existing_edge), std::invalid_argument); } + SUBCASE("remove_edge should remove the edge from both the first and second vertices' list") { + fully_connect_vertex(constants::v1_id); + + auto inc_edged_first = sut.incident_edges(constants::v1_id); + REQUIRE_EQ(inc_edged_first.size(), n_inc_edged_for_fully_connected_vertex); + + const auto& edge_to_remove = inc_edged_first[0uz]; + + const auto target_id = edge_to_remove.target(); + REQUIRE_EQ(sut.incident_edges(target_id).size(), 1uz); + + sut.remove_edge(edge_to_remove); + + // validate that the first incident edges list has been properly aligned + inc_edged_first = sut.incident_edges(0uz); + REQUIRE_EQ(inc_edged_first.size(), n_inc_edged_for_fully_connected_vertex - 1uz); + CHECK_EQ(std::ranges::find(inc_edged_first, edge_to_remove), inc_edged_first.end()); + + // validate that the second adjacent edges list has been properly aligned + const auto inc_edged_second = sut.incident_edges(target_id); + REQUIRE_EQ(inc_edged_second.size(), 0uz); + CHECK_EQ(std::ranges::find(inc_edged_second, edge_to_remove), inc_edged_second.end()); + } + + // --- edge getters --- + SUBCASE("has_edge(id, id) should return true if there is an edge in the graph which connects " "vertices with the given ids in any direction") { add_edge(constants::v1_id, constants::v2_id); @@ -568,40 +706,6 @@ TEST_CASE_TEMPLATE_DEFINE("undirected adjacency list tests", SutType, undirected )); } - SUBCASE("remove_edge should throw when an edge is invalid") { - // not existing edge between valid vertices - const edge_type not_existing_edge{gl::invalid_id, constants::v1_id, constants::v2_id}; - CHECK_THROWS_AS(sut.remove_edge(not_existing_edge), std::invalid_argument); - } - - SUBCASE("remove_edge should remove the edge from both the first and second vertices' list") { - fully_connect_vertex(constants::v1_id); - - auto incident_edges_first = sut.incident_edges(constants::v1_id); - REQUIRE_EQ(incident_edges_first.size(), n_incident_edges_for_fully_connected_vertex); - - const auto& edge_to_remove = incident_edges_first[0uz]; - - const auto target_id = edge_to_remove.target(); - REQUIRE_EQ(sut.incident_edges(target_id).size(), 1uz); - - sut.remove_edge(edge_to_remove); - - // validate that the first incident edges list has been properly aligned - incident_edges_first = sut.incident_edges(0uz); - REQUIRE_EQ(incident_edges_first.size(), n_incident_edges_for_fully_connected_vertex - 1uz); - CHECK_EQ( - std::ranges::find(incident_edges_first, edge_to_remove), incident_edges_first.end() - ); - - // validate that the second adjacent edges list has been properly aligned - const auto incident_edges_second = sut.incident_edges(target_id); - REQUIRE_EQ(incident_edges_second.size(), 0uz); - CHECK_EQ( - std::ranges::find(incident_edges_second, edge_to_remove), incident_edges_second.end() - ); - } - SUBCASE("in_edges and out_edges should return the same edges for undirected graphs") { add_edge(constants::v1_id, constants::v2_id); add_edge(constants::v1_id, constants::v3_id); @@ -613,95 +717,12 @@ TEST_CASE_TEMPLATE_DEFINE("undirected adjacency list tests", SutType, undirected CHECK(std::ranges::equal(out_edges, sut.incident_edges(constants::v1_id))); } - SUBCASE("{in_/out_/}degree should return the number of edges incident {to/from} the given " - "vertex") { - init_complete_graph(); - - std::function deg_proj; - - SUBCASE("degree") { - deg_proj = [&sut](const auto vertex_id) { return sut.degree(vertex_id); }; - } - - SUBCASE("in_degree") { - deg_proj = [&sut](const auto vertex_id) { return sut.in_degree(vertex_id); }; - } - - SUBCASE("out_degree") { - deg_proj = [&sut](const auto vertex_id) { return sut.out_degree(vertex_id); }; - } - - CAPTURE(deg_proj); - - CHECK(std::ranges::all_of( - constants::vertex_id_view, - [](const auto deg) { return deg == n_incident_edges_for_fully_connected_vertex; }, - deg_proj - )); - - add_edge(constants::v1_id, constants::v1_id); - - CHECK_EQ( - deg_proj(constants::v1_id), - n_incident_edges_for_fully_connected_vertex + 2uz // loops counted twice - ); - } - - SUBCASE("{in_/out_/}degree_map should return a map of numbers of edges incident {to/from/with} " - "the " - "corresponding vertices") { - init_complete_graph(false); - const auto expected_deg = constants::n_elements + 1; - - std::vector degree_map; - - SUBCASE("in_degree") { - degree_map = sut.in_degree_map(); - } - - SUBCASE("out_degree") { - degree_map = sut.out_degree_map(); - } - - SUBCASE("degree") { - degree_map = sut.degree_map(); - } + // --- access operators --- - CAPTURE(degree_map); - - REQUIRE_EQ(degree_map.size(), constants::n_elements); - CHECK_EQ(std::ranges::count(degree_map, expected_deg), constants::n_elements); - } - - SUBCASE("remove_vertex should remove the given vertex and all edges incident with it") { - const auto edge1 = add_edge(constants::v1_id, constants::v2_id); - const auto edge3 = add_edge(constants::v2_id, constants::v1_id); - const auto edge2 = add_edge(constants::v1_id, constants::v3_id); - const auto edge4 = add_edge(constants::v3_id, constants::v1_id); - - const auto edge5 = add_edge(constants::v2_id, constants::v3_id); - - const auto removed_vertex_id = 0uz; - const auto removed_edge_ids = sut.remove_vertex(removed_vertex_id); - - constexpr gl::size_type n_removed_edges = 4uz; - REQUIRE_EQ(removed_edge_ids.size(), n_removed_edges); - for (const auto edge_id : {edge1.id(), edge2.id(), edge3.id(), edge4.id()}) - CHECK(std::ranges::contains(removed_edge_ids, edge_id)); - CHECK_FALSE(std::ranges::contains(removed_edge_ids, edge5.id())); - - // Check the structure of the graph considering the aligned IDs - CHECK_EQ(size(sut), constants::n_elements - 1uz); - - const auto adj_edges_1 = sut.incident_edges(constants::v1_id); - CHECK_EQ(adj_edges_1.size(), 1uz); - CHECK_EQ(adj_edges_1.front().id(), edge5.id() - n_removed_edges); - CHECK_EQ(adj_edges_1.front().target(), constants::v2_id); - - const auto adj_edges_2 = sut.incident_edges(constants::v2_id); - CHECK_EQ(adj_edges_2.size(), 1uz); - CHECK_EQ(adj_edges_2.front().id(), edge5.id() - n_removed_edges); - CHECK_EQ(adj_edges_2.front().target(), constants::v1_id); + SUBCASE("at should return the incident edges of a vertex") { + init_complete_graph(); + for (const auto vertex_id : std::views::iota(constants::v1_id, constants::n_elements)) + CHECK(std::ranges::equal(sut.at(vertex_id), sut.incident_edges(vertex_id))); } } diff --git a/tests/source/gl/test_adjacency_matrix.cpp b/tests/source/gl/test_adjacency_matrix.cpp index 81266e7..5689850 100644 --- a/tests/source/gl/test_adjacency_matrix.cpp +++ b/tests/source/gl/test_adjacency_matrix.cpp @@ -34,6 +34,8 @@ TEST_CASE_TEMPLATE_DEFINE( ) { test_adjacency_matrix fixture; + // --- general --- + SUBCASE("should be initialized with no vertices and no edges by default") { SutType sut{}; CHECK_EQ(fixture.size(sut), 0uz); @@ -48,6 +50,8 @@ TEST_CASE_TEMPLATE_DEFINE( })); } + // --- vertex modifiers --- + SUBCASE("add_vertex should properly extend the current adjacency matrix") { SutType sut{}; constexpr gl::size_type target_n_vertices = constants::n_elements; @@ -73,6 +77,8 @@ TEST_CASE_TEMPLATE_DEFINE( })); } + // --- edge modifiers --- + SUBCASE("add_edge should throw an error if the vertices are already incident") { SutType sut{constants::n_elements}; sut.add_edge(fixture.next_edge_id++, constants::v1_id, constants::v2_id); @@ -103,6 +109,8 @@ TEST_CASE_TEMPLATE_DEFINE( }); } + // --- comparison --- + SUBCASE("equality operator should correctly comparge matrices") { SutType sut1(constants::n_elements); sut1.add_edge(fixture.next_edge_id++, constants::v1_id, constants::v2_id); @@ -209,6 +217,122 @@ TEST_CASE_TEMPLATE_DEFINE("directed adjacency matrix tests", SutType, directed_a fixture.init_complete_graph(no_loops); }; + // --- vertex modifiers --- + + SUBCASE("remove_vertex should remove the given vertex and all edges incident with it") { + const auto edge1 = add_edge(constants::v1_id, constants::v2_id); + const auto edge3 = add_edge(constants::v2_id, constants::v1_id); + const auto edge2 = add_edge(constants::v1_id, constants::v3_id); + const auto edge4 = add_edge(constants::v3_id, constants::v1_id); + + const auto edge5 = add_edge(constants::v2_id, constants::v3_id); + const auto edge6 = add_edge(constants::v3_id, constants::v2_id); + + const auto removed_vertex_id = constants::v1_id; + const auto removed_edge_ids = sut.remove_vertex(removed_vertex_id); + + constexpr gl::size_type n_removed_edges = 4uz; + REQUIRE_EQ(removed_edge_ids.size(), n_removed_edges); + for (const auto edge_id : {edge1.id(), edge2.id(), edge3.id(), edge4.id()}) + CHECK(std::ranges::contains(removed_edge_ids, edge_id)); + for (const auto edge_id : {edge5.id(), edge6.id()}) + CHECK_FALSE(std::ranges::contains(removed_edge_ids, edge_id)); + + // Check the structure of the graph considering the aligned IDs + CHECK_EQ(size(sut), constants::n_elements - 1uz); + + auto adj_edges_1 = sut.incident_edges(constants::v1_id); + CHECK_EQ(gl::util::range_size(adj_edges_1), 1uz); + CHECK_EQ((*std::ranges::begin(adj_edges_1)).id(), edge5.id() - n_removed_edges); + CHECK_EQ((*std::ranges::begin(adj_edges_1)).target(), constants::v2_id); + + auto adj_edges_2 = sut.incident_edges(constants::v2_id); + CHECK_EQ(gl::util::range_size(adj_edges_2), 1uz); + CHECK_EQ((*std::ranges::begin(adj_edges_2)).id(), edge6.id() - n_removed_edges); + CHECK_EQ((*std::ranges::begin(adj_edges_2)).target(), constants::v1_id); + } + + // --- vertex getters --- + + // --- degree getters --- + + SUBCASE("degree should return the number of edges incident with the given vertex") { + init_complete_graph(); + const auto deg_proj = [&sut](const auto vertex_id) { return sut.degree(vertex_id); }; + + CHECK(std::ranges::all_of( + constants::vertex_id_view, + [](const auto deg) { return deg == 2uz * n_incident_edges_for_fully_connected_vertex; }, + deg_proj + )); + + add_edge(constants::v1_id, constants::v1_id); + + CHECK_EQ( + deg_proj(constants::v1_id), 2uz * (n_incident_edges_for_fully_connected_vertex + 1uz) + ); + } + + SUBCASE("in/out_degree should return the number of edges incident {to/from} the given vertex") { + init_complete_graph(); + + std::function deg_proj; + + SUBCASE("in_degree") { + deg_proj = [&sut](const auto vertex_id) { return sut.in_degree(vertex_id); }; + } + + SUBCASE("out_degree") { + deg_proj = [&sut](const auto vertex_id) { return sut.out_degree(vertex_id); }; + } + + CAPTURE(deg_proj); + + CHECK(std::ranges::all_of( + constants::vertex_id_view, + [](const auto deg) { return deg == n_incident_edges_for_fully_connected_vertex; }, + deg_proj + )); + + add_edge(constants::v1_id, constants::v1_id); + + CHECK_EQ(deg_proj(constants::v1_id), n_incident_edges_for_fully_connected_vertex + 1uz); + } + + SUBCASE("degree_map should return a map of the numbers of edges incident with the " + "corresponding vertices") { + init_complete_graph(false); + const auto expected_deg = constants::n_elements * 2uz; + + std::vector degree_map = sut.degree_map(); + + REQUIRE_EQ(degree_map.size(), constants::n_elements); + CHECK_EQ(std::ranges::count(degree_map, expected_deg), constants::n_elements); + } + + SUBCASE("in/out_degree_map should return a map of numbers of edges incident {to/from} the " + "corresponding vertices") { + init_complete_graph(false); + const auto expected_deg = constants::n_elements; + + std::vector degree_map; + + SUBCASE("in_degree") { + degree_map = sut.in_degree_map(); + } + + SUBCASE("out_degree") { + degree_map = sut.out_degree_map(); + } + + CAPTURE(degree_map); + + REQUIRE_EQ(degree_map.size(), constants::n_elements); + CHECK_EQ(std::ranges::count(degree_map, expected_deg), constants::n_elements); + } + + // --- edge modifiers --- + SUBCASE("add_edge should add the edge only to the source vertex list") { const auto new_edge = add_edge(constants::v1_id, constants::v2_id); @@ -223,31 +347,35 @@ TEST_CASE_TEMPLATE_DEFINE("directed adjacency matrix tests", SutType, directed_a CHECK_EQ(new_edge_extracted, new_edge); } - SUBCASE("at should return a view equivalent to the matrix row of the given vertex") { - for (const auto vertex_id : std::views::iota(constants::v1_id, constants::n_elements)) { - const auto target_id = - static_cast((vertex_id + 1u) % constants::n_elements); - const auto edge = add_edge(vertex_id, target_id); - auto row_view = sut.at(vertex_id); + SUBCASE("remove_edge should throw when an edge is invalid") { + // not existing edge between valid vertices + CHECK_THROWS_AS( + sut.remove_edge(edge_type{fixture.next_edge_id++, constants::v1_id, constants::v2_id}), + std::invalid_argument + ); + } - REQUIRE_EQ(std::ranges::count_if(row_view, &edge_type::is_valid), 1uz); + SUBCASE("remove_edge should remove the edge from the source vertex's list") { + fully_connect_vertex(constants::v1_id); - const auto edge_it = std::ranges::find_if(row_view, &edge_type::is_valid); - REQUIRE_NE(edge_it, row_view.end()); - REQUIRE_EQ(*edge_it, edge); - } - } + auto incident_edges = sut.incident_edges(constants::v1_id); + REQUIRE_EQ( + gl::util::range_size(incident_edges), n_incident_edges_for_fully_connected_vertex + ); - SUBCASE("incident_edges should return a filtered view of edges incident with the given vertex" - ) { - const auto vertex_id = constants::v1_id; - const auto edge = add_edge(vertex_id, constants::v2_id); - auto incident_edges = sut.incident_edges(vertex_id); + const auto edge_to_remove = *std::ranges::begin(incident_edges); + sut.remove_edge(edge_to_remove); - REQUIRE_EQ(gl::util::range_size(incident_edges), 1uz); - CHECK_EQ(*std::ranges::begin(incident_edges), edge); + // validate that the incident edges list has been properly aligned + incident_edges = sut.incident_edges(constants::v1_id); + REQUIRE_EQ( + gl::util::range_size(incident_edges), n_incident_edges_for_fully_connected_vertex - 1uz + ); + CHECK_EQ(std::ranges::find(incident_edges, edge_to_remove), incident_edges.end()); } + // --- edge getters --- + SUBCASE("has_edge(id, id) should return true if there is an edge in the graph which connects " "vertices with the given ids in the specified direction") { add_edge(constants::v1_id, constants::v2_id); @@ -285,31 +413,14 @@ TEST_CASE_TEMPLATE_DEFINE("directed adjacency matrix tests", SutType, directed_a CHECK_FALSE(sut.get_edge(constants::v2_id, constants::v2_id)); } - SUBCASE("remove_edge should throw when an edge is invalid") { - // not existing edge between valid vertices - CHECK_THROWS_AS( - sut.remove_edge(edge_type{fixture.next_edge_id++, constants::v1_id, constants::v2_id}), - std::invalid_argument - ); - } - - SUBCASE("remove_edge should remove the edge from the source vertex's list") { - fully_connect_vertex(constants::v1_id); - - auto incident_edges = sut.incident_edges(constants::v1_id); - REQUIRE_EQ( - gl::util::range_size(incident_edges), n_incident_edges_for_fully_connected_vertex - ); - - const auto edge_to_remove = *std::ranges::begin(incident_edges); - sut.remove_edge(edge_to_remove); + SUBCASE("incident_edges should return a filtered view of edges incident with the given vertex" + ) { + const auto vertex_id = constants::v1_id; + const auto edge = add_edge(vertex_id, constants::v2_id); + auto incident_edges = sut.incident_edges(vertex_id); - // validate that the incident edges list has been properly aligned - incident_edges = sut.incident_edges(constants::v1_id); - REQUIRE_EQ( - gl::util::range_size(incident_edges), n_incident_edges_for_fully_connected_vertex - 1uz - ); - CHECK_EQ(std::ranges::find(incident_edges, edge_to_remove), incident_edges.end()); + REQUIRE_EQ(gl::util::range_size(incident_edges), 1uz); + CHECK_EQ(*std::ranges::begin(incident_edges), edge); } SUBCASE("in_edges should return edges where the vertex is the target") { @@ -334,141 +445,49 @@ TEST_CASE_TEMPLATE_DEFINE("directed adjacency matrix tests", SutType, directed_a CHECK(std::ranges::contains(out_edges, edge2)); } - SUBCASE("{in/out}_degree should return the number of edges incident {to/from} the given vertex" - ) { - init_complete_graph(); + // --- access operators --- - std::function deg_proj; + SUBCASE("at should return a view equivalent to the matrix row of the given vertex") { + for (const auto vertex_id : std::views::iota(constants::v1_id, constants::n_elements)) { + const auto target_id = + static_cast((vertex_id + 1u) % constants::n_elements); + const auto edge = add_edge(vertex_id, target_id); + auto row_view = sut.at(vertex_id); - SUBCASE("in_degree") { - deg_proj = [&sut](const auto vertex_id) { return sut.in_degree(vertex_id); }; - } + REQUIRE_EQ(std::ranges::count_if(row_view, &edge_type::is_valid), 1uz); - SUBCASE("out_degree") { - deg_proj = [&sut](const auto vertex_id) { return sut.out_degree(vertex_id); }; + const auto edge_it = std::ranges::find_if(row_view, &edge_type::is_valid); + REQUIRE_NE(edge_it, row_view.end()); + REQUIRE_EQ(*edge_it, edge); } - - CAPTURE(deg_proj); - - CHECK(std::ranges::all_of( - constants::vertex_id_view, - [](const auto deg) { return deg == n_incident_edges_for_fully_connected_vertex; }, - deg_proj - )); - - add_edge(constants::v1_id, constants::v1_id); - - CHECK_EQ(deg_proj(constants::v1_id), n_incident_edges_for_fully_connected_vertex + 1uz); } +} - SUBCASE("degree should return the number of edges incident with the given vertex") { - init_complete_graph(); - const auto deg_proj = [&sut](const auto vertex_id) { return sut.degree(vertex_id); }; - - CHECK(std::ranges::all_of( - constants::vertex_id_view, - [](const auto deg) { return deg == 2uz * n_incident_edges_for_fully_connected_vertex; }, - deg_proj - )); +TEST_CASE_TEMPLATE_INSTANTIATE( + directed_adj_matrix_template, + gl::impl::adjacency_matrix>, // normal adj matrix + gl::impl::adjacency_matrix> // flat adj matrix +); - add_edge(constants::v1_id, constants::v1_id); +template +struct test_undirected_adjacency_matrix : public test_adjacency_matrix { + using sut_type = SutType; + using edge_type = typename sut_type::edge_type; - CHECK_EQ( - deg_proj(constants::v1_id), 2uz * (n_incident_edges_for_fully_connected_vertex + 1uz) - ); + edge_type add_edge(const auto source_id, const auto target_id) { + const auto new_edge_id = this->next_edge_id++; + sut.add_edge(new_edge_id, source_id, target_id); + return edge_type{new_edge_id, source_id, target_id}; } - SUBCASE("{in/out}_degree_map should return a map of numbers of edges incident {to/from} the " - "corresponding vertices") { - init_complete_graph(false); - const auto expected_deg = constants::n_elements; - - std::vector degree_map; + void fully_connect_vertex(const auto source_id, const bool no_loops = true) { + for (const auto target_id : constants::vertex_id_view) { + if (target_id == source_id and no_loops) + continue; - SUBCASE("in_degree") { - degree_map = sut.in_degree_map(); + add_edge(source_id, target_id); } - - SUBCASE("out_degree") { - degree_map = sut.out_degree_map(); - } - - CAPTURE(degree_map); - - REQUIRE_EQ(degree_map.size(), constants::n_elements); - CHECK_EQ(std::ranges::count(degree_map, expected_deg), constants::n_elements); - } - - SUBCASE("degree_map should return a map of the numbers of edges incident with the " - "corresponding vertices") { - init_complete_graph(false); - const auto expected_deg = constants::n_elements * 2uz; - - std::vector degree_map = sut.degree_map(); - - REQUIRE_EQ(degree_map.size(), constants::n_elements); - CHECK_EQ(std::ranges::count(degree_map, expected_deg), constants::n_elements); - } - - SUBCASE("remove_vertex should remove the given vertex and all edges incident with it") { - const auto edge1 = add_edge(constants::v1_id, constants::v2_id); - const auto edge3 = add_edge(constants::v2_id, constants::v1_id); - const auto edge2 = add_edge(constants::v1_id, constants::v3_id); - const auto edge4 = add_edge(constants::v3_id, constants::v1_id); - - const auto edge5 = add_edge(constants::v2_id, constants::v3_id); - const auto edge6 = add_edge(constants::v3_id, constants::v2_id); - - const auto removed_vertex_id = constants::v1_id; - const auto removed_edge_ids = sut.remove_vertex(removed_vertex_id); - - constexpr gl::size_type n_removed_edges = 4uz; - REQUIRE_EQ(removed_edge_ids.size(), n_removed_edges); - for (const auto edge_id : {edge1.id(), edge2.id(), edge3.id(), edge4.id()}) - CHECK(std::ranges::contains(removed_edge_ids, edge_id)); - for (const auto edge_id : {edge5.id(), edge6.id()}) - CHECK_FALSE(std::ranges::contains(removed_edge_ids, edge_id)); - - // Check the structure of the graph considering the aligned IDs - CHECK_EQ(size(sut), constants::n_elements - 1uz); - - auto adj_edges_1 = sut.incident_edges(constants::v1_id); - CHECK_EQ(gl::util::range_size(adj_edges_1), 1uz); - CHECK_EQ((*std::ranges::begin(adj_edges_1)).id(), edge5.id() - n_removed_edges); - CHECK_EQ((*std::ranges::begin(adj_edges_1)).target(), constants::v2_id); - - auto adj_edges_2 = sut.incident_edges(constants::v2_id); - CHECK_EQ(gl::util::range_size(adj_edges_2), 1uz); - CHECK_EQ((*std::ranges::begin(adj_edges_2)).id(), edge6.id() - n_removed_edges); - CHECK_EQ((*std::ranges::begin(adj_edges_2)).target(), constants::v1_id); - } -} - -TEST_CASE_TEMPLATE_INSTANTIATE( - directed_adj_matrix_template, - gl::impl::adjacency_matrix>, // normal adj matrix - gl::impl::adjacency_matrix> // flat adj matrix -); - -template -struct test_undirected_adjacency_matrix : public test_adjacency_matrix { - using sut_type = SutType; - using edge_type = typename sut_type::edge_type; - - edge_type add_edge(const auto source_id, const auto target_id) { - const auto new_edge_id = this->next_edge_id++; - sut.add_edge(new_edge_id, source_id, target_id); - return edge_type{new_edge_id, source_id, target_id}; - } - - void fully_connect_vertex(const auto source_id, const bool no_loops = true) { - for (const auto target_id : constants::vertex_id_view) { - if (target_id == source_id and no_loops) - continue; - - add_edge(source_id, target_id); - } - } + } void init_complete_graph(const bool no_loops = true) { for (const auto source_id : constants::vertex_id_view) { @@ -515,6 +534,101 @@ TEST_CASE_TEMPLATE_DEFINE( fixture.init_complete_graph(no_loops); }; + // --- vertex modifiers --- + + SUBCASE("remove_vertex should remove the given vertex and all edges incident with it") { + const auto edge1 = add_edge(constants::v1_id, constants::v2_id); + const auto edge2 = add_edge(constants::v1_id, constants::v3_id); + const auto edge3 = add_edge(constants::v2_id, constants::v3_id); + + const auto removed_vertex_id = 0uz; + const auto removed_edge_ids = sut.remove_vertex(removed_vertex_id); + + constexpr gl::size_type n_removed_edges = 2uz; + REQUIRE_EQ(removed_edge_ids.size(), n_removed_edges); + for (const auto edge_id : {edge1.id(), edge2.id()}) + CHECK(std::ranges::contains(removed_edge_ids, edge_id)); + CHECK_FALSE(std::ranges::contains(removed_edge_ids, edge3.id())); + + // Check the structure of the graph considering the aligned IDs + CHECK_EQ(size(sut), constants::n_elements - 1uz); + + auto adj_edges_1 = sut.incident_edges(constants::v1_id); + CHECK_EQ(gl::util::range_size(adj_edges_1), 1uz); + CHECK_EQ((*std::ranges::begin(adj_edges_1)).id(), edge3.id() - n_removed_edges); + CHECK_EQ((*std::ranges::begin(adj_edges_1)).target(), constants::v2_id); + + auto adj_edges_2 = sut.incident_edges(constants::v2_id); + CHECK_EQ(gl::util::range_size(adj_edges_2), 1uz); + CHECK_EQ((*std::ranges::begin(adj_edges_2)).id(), edge3.id() - n_removed_edges); + CHECK_EQ((*std::ranges::begin(adj_edges_2)).target(), constants::v1_id); + } + + // --- vertex getters --- + + // --- degree getters --- + + SUBCASE("-/in/out_degree should return the number of edges incident with/to/from the given " + "vertex") { + init_complete_graph(); + + std::function deg_proj; + + SUBCASE("degree") { + deg_proj = [&sut](const auto vertex_id) { return sut.degree(vertex_id); }; + } + + SUBCASE("in_degree") { + deg_proj = [&sut](const auto vertex_id) { return sut.in_degree(vertex_id); }; + } + + SUBCASE("out_degree") { + deg_proj = [&sut](const auto vertex_id) { return sut.out_degree(vertex_id); }; + } + + CAPTURE(deg_proj); + + CHECK(std::ranges::all_of( + constants::vertex_id_view, + [](const auto deg) { return deg == n_incident_edges_for_fully_connected_vertex; }, + deg_proj + )); + + add_edge(constants::v1_id, constants::v1_id); + + CHECK_EQ( + deg_proj(constants::v1_id), + n_incident_edges_for_fully_connected_vertex + 2uz // loops counted twice + ); + } + + SUBCASE("-/in/out_degree_map should return a map of numbers of edges incident with/to/from " + "the corresponding vertices") { + init_complete_graph(false); + const auto expected_deg = constants::n_elements + 1; + + std::vector degree_map; + + SUBCASE("in_degree") { + degree_map = sut.in_degree_map(); + } + + SUBCASE("out_degree") { + degree_map = sut.out_degree_map(); + } + + SUBCASE("degree") { + degree_map = sut.degree_map(); + } + + CAPTURE(degree_map); + + REQUIRE_EQ(degree_map.size(), constants::n_elements); + CHECK_EQ(std::ranges::count(degree_map, expected_deg), constants::n_elements); + } + + // --- edge modifiers --- + SUBCASE("add_edge should add the edge to the lists of both vertices") { const auto new_edge = add_edge(constants::v1_id, constants::v2_id); REQUIRE(new_edge.is_incident_from(constants::v1_id)); @@ -545,37 +659,49 @@ TEST_CASE_TEMPLATE_DEFINE( CHECK_EQ(new_edge_extracted_1, new_edge); } - SUBCASE("at should return a view equivalent to the matrix row of the given vertex") { - const auto edge1 = add_edge(constants::v1_id, constants::v2_id); - const auto edge2 = add_edge(constants::v2_id, constants::v3_id); - const auto edge3 = add_edge(constants::v3_id, constants::v1_id); + SUBCASE("remove_edge should throw when an edge is invalid") { + // not existing edge between valid vertices + CHECK_THROWS_AS( + sut.remove_edge(edge_type{fixture.next_edge_id++, constants::v1_id, constants::v2_id}), + std::invalid_argument + ); + } - auto v1_row_view = sut.at(constants::v1_id); - REQUIRE_EQ(std::ranges::count_if(v1_row_view, &edge_type::is_valid), 2uz); - CHECK_EQ(v1_row_view[constants::v2_id], edge1); - CHECK_EQ(v1_row_view[constants::v3_id], edge3); + SUBCASE("remove_edge should remove the edge from both the first and second vertices' list") { + fully_connect_vertex(constants::v1_id); - auto v2_row_view = sut.at(constants::v2_id); - REQUIRE_EQ(std::ranges::count_if(v2_row_view, &edge_type::is_valid), 2uz); - CHECK_EQ(v2_row_view[constants::v1_id], edge1); - CHECK_EQ(v2_row_view[constants::v3_id], edge2); + auto incident_edges_first = sut.incident_edges(constants::v1_id); + REQUIRE_EQ( + gl::util::range_size(incident_edges_first), n_incident_edges_for_fully_connected_vertex + ); - auto v3_row_view = sut.at(constants::v3_id); - REQUIRE_EQ(std::ranges::count_if(v3_row_view, &edge_type::is_valid), 2uz); - CHECK_EQ(v3_row_view[constants::v1_id], edge3); - CHECK_EQ(v3_row_view[constants::v2_id], edge2); - } + const auto edge_to_remove = *std::ranges::begin(incident_edges_first); - SUBCASE("incident_edges should return a filtered view of edges incident with the given vertex" - ) { - const auto vertex_id = constants::v1_id; - const auto edge = add_edge(vertex_id, constants::v2_id); - auto incident_edges = sut.incident_edges(vertex_id); + const auto target_id = edge_to_remove.target(); + REQUIRE_EQ(gl::util::range_size(sut.incident_edges(target_id)), 1uz); - REQUIRE_EQ(gl::util::range_size(incident_edges), 1uz); - CHECK_EQ(*std::ranges::begin(incident_edges), edge); + sut.remove_edge(edge_to_remove); + + // validate that the first incident edges list has been properly aligned + incident_edges_first = sut.incident_edges(constants::v1_id); + REQUIRE_EQ( + gl::util::range_size(incident_edges_first), + n_incident_edges_for_fully_connected_vertex - 1uz + ); + CHECK_EQ( + std::ranges::find(incident_edges_first, edge_to_remove), incident_edges_first.end() + ); + + // validate that the second incident edges list has been properly aligned + auto incident_edges_second = sut.incident_edges(target_id); + REQUIRE_EQ(gl::util::range_size(incident_edges_second), 0uz); + CHECK_EQ( + std::ranges::find(incident_edges_second, edge_to_remove), incident_edges_second.end() + ); } + // --- edge getters --- + SUBCASE("has_edge(id, id) should return true if there is an edge in the graph which connects " "vertices with the given ids in any direction") { add_edge(constants::v1_id, constants::v2_id); @@ -615,45 +741,14 @@ TEST_CASE_TEMPLATE_DEFINE( CHECK_EQ(*edge_opt_2, edge_1); } - SUBCASE("remove_edge should throw when an edge is invalid") { - // not existing edge between valid vertices - CHECK_THROWS_AS( - sut.remove_edge(edge_type{fixture.next_edge_id++, constants::v1_id, constants::v2_id}), - std::invalid_argument - ); - } - - SUBCASE("remove_edge should remove the edge from both the first and second vertices' list") { - fully_connect_vertex(constants::v1_id); - - auto incident_edges_first = sut.incident_edges(constants::v1_id); - REQUIRE_EQ( - gl::util::range_size(incident_edges_first), n_incident_edges_for_fully_connected_vertex - ); - - const auto edge_to_remove = *std::ranges::begin(incident_edges_first); - - const auto target_id = edge_to_remove.target(); - REQUIRE_EQ(gl::util::range_size(sut.incident_edges(target_id)), 1uz); - - sut.remove_edge(edge_to_remove); - - // validate that the first incident edges list has been properly aligned - incident_edges_first = sut.incident_edges(constants::v1_id); - REQUIRE_EQ( - gl::util::range_size(incident_edges_first), - n_incident_edges_for_fully_connected_vertex - 1uz - ); - CHECK_EQ( - std::ranges::find(incident_edges_first, edge_to_remove), incident_edges_first.end() - ); + SUBCASE("incident_edges should return a filtered view of edges incident with the given vertex" + ) { + const auto vertex_id = constants::v1_id; + const auto edge = add_edge(vertex_id, constants::v2_id); + auto incident_edges = sut.incident_edges(vertex_id); - // validate that the second incident edges list has been properly aligned - auto incident_edges_second = sut.incident_edges(target_id); - REQUIRE_EQ(gl::util::range_size(incident_edges_second), 0uz); - CHECK_EQ( - std::ranges::find(incident_edges_second, edge_to_remove), incident_edges_second.end() - ); + REQUIRE_EQ(gl::util::range_size(incident_edges), 1uz); + CHECK_EQ(*std::ranges::begin(incident_edges), edge); } SUBCASE("in_edges and out_edges should return the same edges for undirected graphs") { @@ -667,92 +762,27 @@ TEST_CASE_TEMPLATE_DEFINE( CHECK(std::ranges::equal(out_edges, sut.incident_edges(constants::v1_id))); } - SUBCASE("{in_/out_/}degree should return the number of edges incident {to/from/with} the given " - "vertex") { - init_complete_graph(); + // --- access operators --- - std::function deg_proj; - - SUBCASE("degree") { - deg_proj = [&sut](const auto vertex_id) { return sut.degree(vertex_id); }; - } - - SUBCASE("in_degree") { - deg_proj = [&sut](const auto vertex_id) { return sut.in_degree(vertex_id); }; - } - - SUBCASE("out_degree") { - deg_proj = [&sut](const auto vertex_id) { return sut.out_degree(vertex_id); }; - } - - CAPTURE(deg_proj); - - CHECK(std::ranges::all_of( - constants::vertex_id_view, - [](const auto deg) { return deg == n_incident_edges_for_fully_connected_vertex; }, - deg_proj - )); - - add_edge(constants::v1_id, constants::v1_id); - - CHECK_EQ( - deg_proj(constants::v1_id), - n_incident_edges_for_fully_connected_vertex + 2uz // loops counted twice - ); - } - - SUBCASE("{in_/out_/}degree_map should return a map of numbers of edges incident {to/from/with} " - "the " - "corresponding vertices") { - init_complete_graph(false); - const auto expected_deg = constants::n_elements + 1; - - std::vector degree_map; - - SUBCASE("in_degree") { - degree_map = sut.in_degree_map(); - } - - SUBCASE("out_degree") { - degree_map = sut.out_degree_map(); - } - - SUBCASE("degree") { - degree_map = sut.degree_map(); - } - - CAPTURE(degree_map); - - REQUIRE_EQ(degree_map.size(), constants::n_elements); - CHECK_EQ(std::ranges::count(degree_map, expected_deg), constants::n_elements); - } - - SUBCASE("remove_vertex should remove the given vertex and all edges incident with it") { + SUBCASE("at should return a view equivalent to the matrix row of the given vertex") { const auto edge1 = add_edge(constants::v1_id, constants::v2_id); - const auto edge2 = add_edge(constants::v1_id, constants::v3_id); - const auto edge3 = add_edge(constants::v2_id, constants::v3_id); - - const auto removed_vertex_id = 0uz; - const auto removed_edge_ids = sut.remove_vertex(removed_vertex_id); - - constexpr gl::size_type n_removed_edges = 2uz; - REQUIRE_EQ(removed_edge_ids.size(), n_removed_edges); - for (const auto edge_id : {edge1.id(), edge2.id()}) - CHECK(std::ranges::contains(removed_edge_ids, edge_id)); - CHECK_FALSE(std::ranges::contains(removed_edge_ids, edge3.id())); + const auto edge2 = add_edge(constants::v2_id, constants::v3_id); + const auto edge3 = add_edge(constants::v3_id, constants::v1_id); - // Check the structure of the graph considering the aligned IDs - CHECK_EQ(size(sut), constants::n_elements - 1uz); + auto v1_row_view = sut.at(constants::v1_id); + REQUIRE_EQ(std::ranges::count_if(v1_row_view, &edge_type::is_valid), 2uz); + CHECK_EQ(v1_row_view[constants::v2_id], edge1); + CHECK_EQ(v1_row_view[constants::v3_id], edge3); - auto adj_edges_1 = sut.incident_edges(constants::v1_id); - CHECK_EQ(gl::util::range_size(adj_edges_1), 1uz); - CHECK_EQ((*std::ranges::begin(adj_edges_1)).id(), edge3.id() - n_removed_edges); - CHECK_EQ((*std::ranges::begin(adj_edges_1)).target(), constants::v2_id); + auto v2_row_view = sut.at(constants::v2_id); + REQUIRE_EQ(std::ranges::count_if(v2_row_view, &edge_type::is_valid), 2uz); + CHECK_EQ(v2_row_view[constants::v1_id], edge1); + CHECK_EQ(v2_row_view[constants::v3_id], edge2); - auto adj_edges_2 = sut.incident_edges(constants::v2_id); - CHECK_EQ(gl::util::range_size(adj_edges_2), 1uz); - CHECK_EQ((*std::ranges::begin(adj_edges_2)).id(), edge3.id() - n_removed_edges); - CHECK_EQ((*std::ranges::begin(adj_edges_2)).target(), constants::v1_id); + auto v3_row_view = sut.at(constants::v3_id); + REQUIRE_EQ(std::ranges::count_if(v3_row_view, &edge_type::is_valid), 2uz); + CHECK_EQ(v3_row_view[constants::v1_id], edge3); + CHECK_EQ(v3_row_view[constants::v2_id], edge2); } } From abef96d0bc6de60e376134562cecd403903b22b4 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Fri, 10 Apr 2026 21:14:16 +0200 Subject: [PATCH 3/9] move this shit around --- benchmarks/suites/is_bipartite.cpp | 2 +- include/gl/graph.hpp | 280 +++++----- include/gl/{topologies.hpp => topology.hpp} | 0 tests/source/gl/test_alg_bfs.cpp | 2 +- tests/source/gl/test_alg_coloring.cpp | 2 +- tests/source/gl/test_alg_dfs.cpp | 2 +- tests/source/gl/test_alg_dijkstra.cpp | 2 +- tests/source/gl/test_alg_prim_mst.cpp | 2 +- tests/source/gl/test_alg_topological_sort.cpp | 2 +- tests/source/gl/test_alg_types.cpp | 2 +- tests/source/gl/test_graph.cpp | 503 ++++++++++++++---- tests/source/gl/test_graph_file_io.cpp | 2 +- tests/source/gl/test_graph_incidence.cpp | 170 ------ tests/source/gl/test_graph_io.cpp | 2 +- .../gl/test_graph_topology_builders.cpp | 2 +- .../source/gl/test_vertex_degree_getters.cpp | 178 ------- tests/source/gl/test_vertex_descriptor.cpp | 22 +- 17 files changed, 576 insertions(+), 599 deletions(-) rename include/gl/{topologies.hpp => topology.hpp} (100%) delete mode 100644 tests/source/gl/test_graph_incidence.cpp delete mode 100644 tests/source/gl/test_vertex_degree_getters.cpp diff --git a/benchmarks/suites/is_bipartite.cpp b/benchmarks/suites/is_bipartite.cpp index 74321e6..3f28dbc 100644 --- a/benchmarks/suites/is_bipartite.cpp +++ b/benchmarks/suites/is_bipartite.cpp @@ -3,7 +3,7 @@ #include #include -#include +#include #include diff --git a/include/gl/graph.hpp b/include/gl/graph.hpp index 7c98df7..82db400 100644 --- a/include/gl/graph.hpp +++ b/include/gl/graph.hpp @@ -107,7 +107,7 @@ class graph final { ~graph() = default; - // --- general methods --- + // --- size methods --- [[nodiscard]] gl_attr_force_inline size_type order() const noexcept { return this->_n_vertices; @@ -117,44 +117,7 @@ class graph final { return this->_n_edges; } - // --- vertex methods --- - - [[nodiscard]] gl_attr_force_inline auto vertices() const - requires(traits::c_empty_properties) - { - return this->vertex_ids() - | std::views::transform([](const id_type id) { return vertex_descriptor{id}; }); - } - - [[nodiscard]] gl_attr_force_inline auto vertices() const - requires(traits::c_non_empty_properties) - { - return this->_vertex_properties | std::views::enumerate - | std::views::transform([](const auto& x) { - const auto& [id, ptr] = x; - return vertex_descriptor{static_cast(id), *ptr}; - }); - } - - [[nodiscard]] gl_attr_force_inline auto vertex_ids() const noexcept { - return std::views::iota(initial_id_v, this->_n_vertices); - } - - [[nodiscard]] vertex_type get_vertex(const id_type vertex_id) const { - this->_verify_vertex_id(vertex_id); - if constexpr (traits::c_non_empty_properties) - return vertex_descriptor{vertex_id, *this->_vertex_properties[vertex_id]}; - else - return vertex_descriptor{vertex_id}; - } - - [[nodiscard]] gl_attr_force_inline bool has_vertex(const id_type vertex_id) const { - return vertex_id < this->_n_vertices; - } - - [[nodiscard]] gl_attr_force_inline bool has_vertex(const vertex_type& vertex) const { - return this->has_vertex(vertex.id()); - } + // --- vertex modifiers --- const vertex_type add_vertex() { this->_impl.add_vertex(); @@ -242,55 +205,27 @@ class graph final { this->_remove_vertex_impl(vertex.id()); } - [[nodiscard]] gl_attr_force_inline size_type in_degree(const id_type vertex_id) const { - this->_verify_vertex_id(vertex_id); - return this->_impl.in_degree(vertex_id); - } - - [[nodiscard]] gl_attr_force_inline size_type in_degree(const vertex_type& vertex) const { - return this->in_degree(vertex.id()); - } - - [[nodiscard]] gl_attr_force_inline std::vector in_degree_map() const { - return this->_impl.in_degree_map(); - } - - [[nodiscard]] gl_attr_force_inline size_type out_degree(const id_type vertex_id) const { - this->_verify_vertex_id(vertex_id); - return this->_impl.out_degree(vertex_id); - } - - [[nodiscard]] gl_attr_force_inline size_type out_degree(const vertex_type& vertex) const { - return this->out_degree(vertex.id()); - } - - [[nodiscard]] gl_attr_force_inline std::vector out_degree_map() const { - return this->_impl.out_degree_map(); - } + // --- vertex getters --- - [[nodiscard]] gl_attr_force_inline size_type degree(const id_type vertex_id) const { - this->_verify_vertex_id(vertex_id); - return this->_impl.degree(vertex_id); - } - - [[nodiscard]] gl_attr_force_inline size_type degree(const vertex_type& vertex) const { - return this->degree(vertex.id()); - } - - [[nodiscard]] gl_attr_force_inline std::vector degree_map() const { - return this->_impl.degree_map(); + [[nodiscard]] gl_attr_force_inline auto vertices() const + requires(traits::c_empty_properties) + { + return this->vertex_ids() + | std::views::transform([](const id_type id) { return vertex_descriptor{id}; }); } - [[nodiscard]] inline auto at(const id_type vertex_id) const { - this->_verify_vertex_id(vertex_id); - if constexpr (traits::c_non_empty_properties) - return this->_impl.at(vertex_id, this->_edge_properties); - else - return this->_impl.at(vertex_id); + [[nodiscard]] gl_attr_force_inline auto vertices() const + requires(traits::c_non_empty_properties) + { + return this->_vertex_properties | std::views::enumerate + | std::views::transform([](const auto& x) { + const auto& [id, ptr] = x; + return vertex_descriptor{static_cast(id), *ptr}; + }); } - [[nodiscard]] gl_attr_force_inline auto at(const vertex_type& vertex) const { - return this->at(vertex.id()); + [[nodiscard]] gl_attr_force_inline auto vertex_ids() const noexcept { + return std::views::iota(initial_id_v, this->_n_vertices); } [[nodiscard]] gl_attr_force_inline auto neighbors(const id_type vertex_id) const { @@ -323,48 +258,65 @@ class graph final { return this->_impl.successor_ids(vertex_id); } - [[nodiscard]] inline auto incident_edges(const id_type vertex_id) const { + [[nodiscard]] vertex_type get_vertex(const id_type vertex_id) const { this->_verify_vertex_id(vertex_id); - if constexpr (traits::c_non_empty_properties) - return this->_impl.incident_edges(vertex_id, this->_edge_properties); + if constexpr (traits::c_non_empty_properties) + return vertex_descriptor{vertex_id, *this->_vertex_properties[vertex_id]}; else - return this->_impl.incident_edges(vertex_id); + return vertex_descriptor{vertex_id}; } - [[nodiscard]] gl_attr_force_inline auto incident_edges(const vertex_type& vertex) const { - return this->incident_edges(vertex.id()); + [[nodiscard]] gl_attr_force_inline bool has_vertex(const id_type vertex_id) const { + return vertex_id < this->_n_vertices; } - [[nodiscard]] inline auto in_edges(const id_type vertex_id) const { + [[nodiscard]] gl_attr_force_inline bool has_vertex(const vertex_type& vertex) const { + return this->has_vertex(vertex.id()); + } + + // --- degree getters --- + + [[nodiscard]] gl_attr_force_inline size_type degree(const id_type vertex_id) const { this->_verify_vertex_id(vertex_id); - if constexpr (traits::c_non_empty_properties) - return this->_impl.in_edges(vertex_id, this->_edge_properties); - else - return this->_impl.in_edges(vertex_id); + return this->_impl.degree(vertex_id); } - [[nodiscard]] gl_attr_force_inline auto in_edges(const vertex_type& vertex) const { - return this->in_edges(vertex.id()); + [[nodiscard]] gl_attr_force_inline size_type degree(const vertex_type& vertex) const { + return this->degree(vertex.id()); } - [[nodiscard]] inline auto out_edges(const id_type vertex_id) const { + [[nodiscard]] gl_attr_force_inline std::vector degree_map() const { + return this->_impl.degree_map(); + } + + [[nodiscard]] gl_attr_force_inline size_type in_degree(const id_type vertex_id) const { this->_verify_vertex_id(vertex_id); - if constexpr (traits::c_non_empty_properties) - return this->_impl.out_edges(vertex_id, this->_edge_properties); - else - return this->_impl.out_edges(vertex_id); + return this->_impl.in_degree(vertex_id); } - [[nodiscard]] gl_attr_force_inline auto out_edges(const vertex_type& vertex) const { - return this->out_edges(vertex.id()); + [[nodiscard]] gl_attr_force_inline size_type in_degree(const vertex_type& vertex) const { + return this->in_degree(vertex.id()); } - // --- edge methods --- + [[nodiscard]] gl_attr_force_inline std::vector in_degree_map() const { + return this->_impl.in_degree_map(); + } - [[nodiscard]] gl_attr_force_inline auto edge_ids() const noexcept { - return std::views::iota(initial_id_v, this->_n_edges); + [[nodiscard]] gl_attr_force_inline size_type out_degree(const id_type vertex_id) const { + this->_verify_vertex_id(vertex_id); + return this->_impl.out_degree(vertex_id); + } + + [[nodiscard]] gl_attr_force_inline size_type out_degree(const vertex_type& vertex) const { + return this->out_degree(vertex.id()); + } + + [[nodiscard]] gl_attr_force_inline std::vector out_degree_map() const { + return this->_impl.out_degree_map(); } + // --- edge modifiers --- + const edge_type add_edge(const id_type source_id, const id_type target_id) { this->_verify_vertex_id(source_id); this->_verify_vertex_id(target_id); @@ -401,8 +353,7 @@ class graph final { // clang-format off // gl_attr_force_inline misplacement - gl_attr_force_inline const edge_type - add_edge(const vertex_type& source, const vertex_type& target) { + gl_attr_force_inline const edge_type add_edge(const vertex_type& source, const vertex_type& target) { return this->add_edge(source.id(), target.id()); } @@ -456,6 +407,67 @@ class graph final { ); } + void remove_edge(const edge_type& edge) { + this->_verify_edge(edge); + if constexpr (traits::c_non_empty_properties) + this->_edge_properties.erase(this->_edge_properties.begin() + edge.id()); + this->_impl.remove_edge(edge); + this->_n_edges--; + } + + void remove_edges(const traits::c_range_of auto& edges) { + const auto removed_edge_ids = this->_impl.remove_edges(edges); + this->_n_edges -= removed_edge_ids.size(); + + if constexpr (traits::c_non_empty_properties) { + // IDs are sorted and do not contain duplicates + for (const auto edge_id : std::views::reverse(removed_edge_ids)) + this->_edge_properties.erase(this->_edge_properties.begin() + edge_id); + } + } + + // --- edge getters --- + + [[nodiscard]] gl_attr_force_inline auto edge_ids() const noexcept { + return std::views::iota(initial_id_v, this->_n_edges); + } + + [[nodiscard]] inline auto incident_edges(const id_type vertex_id) const { + this->_verify_vertex_id(vertex_id); + if constexpr (traits::c_non_empty_properties) + return this->_impl.incident_edges(vertex_id, this->_edge_properties); + else + return this->_impl.incident_edges(vertex_id); + } + + [[nodiscard]] gl_attr_force_inline auto incident_edges(const vertex_type& vertex) const { + return this->incident_edges(vertex.id()); + } + + [[nodiscard]] inline auto in_edges(const id_type vertex_id) const { + this->_verify_vertex_id(vertex_id); + if constexpr (traits::c_non_empty_properties) + return this->_impl.in_edges(vertex_id, this->_edge_properties); + else + return this->_impl.in_edges(vertex_id); + } + + [[nodiscard]] gl_attr_force_inline auto in_edges(const vertex_type& vertex) const { + return this->in_edges(vertex.id()); + } + + [[nodiscard]] inline auto out_edges(const id_type vertex_id) const { + this->_verify_vertex_id(vertex_id); + if constexpr (traits::c_non_empty_properties) + return this->_impl.out_edges(vertex_id, this->_edge_properties); + else + return this->_impl.out_edges(vertex_id); + } + + [[nodiscard]] gl_attr_force_inline auto out_edges(const vertex_type& vertex) const { + return this->out_edges(vertex.id()); + } + [[nodiscard]] gl_attr_force_inline bool has_edge( const id_type source_id, const id_type target_id ) const { @@ -510,27 +522,9 @@ class graph final { return this->get_edges(source.id(), target.id()); } - gl_attr_force_inline void remove_edge(const edge_type& edge) { - this->_verify_edge(edge); - if constexpr (traits::c_non_empty_properties) - this->_edge_properties.erase(this->_edge_properties.begin() + edge.id()); - this->_impl.remove_edge(edge); - this->_n_edges--; - } - - inline void remove_edges(const traits::c_range_of auto& edges) { - const auto removed_edge_ids = this->_impl.remove_edges(edges); - this->_n_edges -= removed_edge_ids.size(); - - if constexpr (traits::c_non_empty_properties) { - // IDs are sorted and do not contain duplicates - for (const auto edge_id : std::views::reverse(removed_edge_ids)) - this->_edge_properties.erase(this->_edge_properties.begin() + edge_id); - } - } - - // --- incidence methods --- + // --- adjacency and incidence methods --- + // TODO: rename to are_adjacent [[nodiscard]] bool are_incident(const id_type source_id, const id_type target_id) const { this->_verify_vertex_id(source_id); if (source_id == target_id) @@ -544,12 +538,20 @@ class graph final { return this->has_edge(source_id, target_id); } + // TODO: rename to are_adjacent [[nodiscard]] gl_attr_force_inline bool are_incident( const vertex_type& source, const vertex_type& target ) const { return this->are_incident(source.id(), target.id()); } + // TODO: rename to are_adjacent + [[nodiscard]] bool are_incident(const edge_type& edge_1, const edge_type& edge_2) const { + this->_verify_edge(edge_1); + this->_verify_edge(edge_2); + return edge_1.is_incident_with(edge_2.source()) or edge_1.is_incident_with(edge_2.target()); + } + [[nodiscard]] bool are_incident(const vertex_type& vertex, const edge_type& edge) const { this->_verify_vertex_id(vertex.id()); this->_verify_edge(edge); @@ -562,13 +564,7 @@ class graph final { return this->are_incident(vertex, edge); } - [[nodiscard]] bool are_incident(const edge_type& edge_1, const edge_type& edge_2) const { - this->_verify_edge(edge_1); - this->_verify_edge(edge_2); - return edge_1.is_incident_with(edge_2.source()) or edge_1.is_incident_with(edge_2.target()); - } - - // --- property methods --- + // --- property getters --- [[nodiscard]] gl_attr_force_inline auto vertex_properties_map() const noexcept requires(traits::c_non_empty_properties) @@ -600,6 +596,20 @@ class graph final { return *this->_edge_properties[id]; } + // --- access operators --- + + [[nodiscard]] inline auto at(const id_type vertex_id) const { + this->_verify_vertex_id(vertex_id); + if constexpr (traits::c_non_empty_properties) + return this->_impl.at(vertex_id, this->_edge_properties); + else + return this->_impl.at(vertex_id); + } + + [[nodiscard]] gl_attr_force_inline auto at(const vertex_type& vertex) const { + return this->at(vertex.id()); + } + // --- comparison --- [[nodiscard]] friend bool operator==(const graph& lhs, const graph& rhs) noexcept { diff --git a/include/gl/topologies.hpp b/include/gl/topology.hpp similarity index 100% rename from include/gl/topologies.hpp rename to include/gl/topology.hpp diff --git a/tests/source/gl/test_alg_bfs.cpp b/tests/source/gl/test_alg_bfs.cpp index 0a55626..c7eae4a 100644 --- a/tests/source/gl/test_alg_bfs.cpp +++ b/tests/source/gl/test_alg_bfs.cpp @@ -2,7 +2,7 @@ #include "testing/gl/constants.hpp" #include -#include +#include #include diff --git a/tests/source/gl/test_alg_coloring.cpp b/tests/source/gl/test_alg_coloring.cpp index 4e6d97c..68112d7 100644 --- a/tests/source/gl/test_alg_coloring.cpp +++ b/tests/source/gl/test_alg_coloring.cpp @@ -3,7 +3,7 @@ #include #include -#include +#include #include diff --git a/tests/source/gl/test_alg_dfs.cpp b/tests/source/gl/test_alg_dfs.cpp index 549151f..b8de856 100644 --- a/tests/source/gl/test_alg_dfs.cpp +++ b/tests/source/gl/test_alg_dfs.cpp @@ -2,7 +2,7 @@ #include "testing/gl/constants.hpp" #include -#include +#include #include diff --git a/tests/source/gl/test_alg_dijkstra.cpp b/tests/source/gl/test_alg_dijkstra.cpp index b702648..1d73517 100644 --- a/tests/source/gl/test_alg_dijkstra.cpp +++ b/tests/source/gl/test_alg_dijkstra.cpp @@ -6,7 +6,7 @@ #include #include -#include +#include #include diff --git a/tests/source/gl/test_alg_prim_mst.cpp b/tests/source/gl/test_alg_prim_mst.cpp index dad55e2..a3b8b6c 100644 --- a/tests/source/gl/test_alg_prim_mst.cpp +++ b/tests/source/gl/test_alg_prim_mst.cpp @@ -5,7 +5,7 @@ #include #include -#include +#include #include diff --git a/tests/source/gl/test_alg_topological_sort.cpp b/tests/source/gl/test_alg_topological_sort.cpp index 8e8df88..380112a 100644 --- a/tests/source/gl/test_alg_topological_sort.cpp +++ b/tests/source/gl/test_alg_topological_sort.cpp @@ -3,7 +3,7 @@ #include #include -#include +#include #include diff --git a/tests/source/gl/test_alg_types.cpp b/tests/source/gl/test_alg_types.cpp index 9ce2afb..eb0c067 100644 --- a/tests/source/gl/test_alg_types.cpp +++ b/tests/source/gl/test_alg_types.cpp @@ -2,7 +2,7 @@ #include "testing/gl/constants.hpp" #include -#include +#include #include diff --git a/tests/source/gl/test_graph.cpp b/tests/source/gl/test_graph.cpp index 42c0fbd..a7e000e 100644 --- a/tests/source/gl/test_graph.cpp +++ b/tests/source/gl/test_graph.cpp @@ -1,17 +1,18 @@ -#include "gl/directional_tags.hpp" -#include "gl/graph_traits.hpp" -#include "gl/types/core.hpp" +#include "doctest.h" #include "testing/gl/constants.hpp" #include "testing/gl/functional.hpp" #include "testing/gl/types.hpp" +#include #include +#include +#include +#include #include -#include - #include #include +#include #include namespace gl_testing { @@ -113,7 +114,7 @@ using vertex_id_list = std::vector; inline constexpr auto get_id = [](auto&& element) -> gl::default_id_type { return element.id(); }; -TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_template) { +TEST_CASE_TEMPLATE_DEFINE("common graph structure tests", TraitsType, common_graph_traits_template) { using fixture_type = test_graph; using sut_type = typename fixture_type::sut_type; using traits_type = typename fixture_type::traits_type; @@ -122,7 +123,7 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp fixture_type fixture; - // --- general tests --- + // --- size methods --- SUBCASE("graph should be initialized with no vertices and no edges by default") { sut_type sut; @@ -153,7 +154,7 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp )); } - // --- vertex method tests --- + // --- vertex modifiers --- SUBCASE("add_vertex should return a vertex_descriptor with an incremented id and no edges") { sut_type sut; @@ -211,43 +212,6 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp )); } - SUBCASE("has_vertex(id) should return true when a vertex with the given id is present in " - "the graph") { - sut_type sut{constants::n_elements}; - - CHECK(std::ranges::all_of(constants::vertex_id_view, [&sut](const auto vertex_id) { - return sut.has_vertex(vertex_id); - })); - CHECK_FALSE(sut.has_vertex(constants::out_of_rng_idx)); - } - - SUBCASE("get_vertex should throw if the given id is invalid") { - sut_type sut{constants::n_elements}; - CHECK_THROWS_AS( - static_cast(sut.get_vertex(static_cast(sut.order()))), - std::out_of_range - ); - } - - SUBCASE("get_vertex should return a vertex with the given id") { - sut_type sut; - const auto added_vertex = sut.add_vertex(); - CHECK_EQ(sut.get_vertex(added_vertex.id()), added_vertex); - } - - SUBCASE("vertices should return the correct vertex collection view") { - sut_type sut{constants::n_elements}; - - CHECK(std::ranges::equal( - sut.vertices(), constants::vertex_id_view, std::ranges::equal_to{}, get_id - )); - } - - SUBCASE("vertex_ids should return a correct view") { - sut_type sut{constants::n_elements}; - CHECK(std::ranges::equal(sut.vertex_ids(), constants::vertex_id_view)); - } - SUBCASE("remove_vertex(vertex) should throw if the id of the given is invalid") { sut_type sut{constants::n_elements}; CHECK_THROWS_AS(sut.remove_vertex(fixture.out_of_range_vertex), std::out_of_range); @@ -361,9 +325,51 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp )); } - // --- edge method tests --- - SUBCASE("edge method tests for default properties type") { + // --- vertex getters --- + + SUBCASE("vertices should return the correct vertex collection view") { + sut_type sut{constants::n_elements}; + + CHECK(std::ranges::equal( + sut.vertices(), constants::vertex_id_view, std::ranges::equal_to{}, get_id + )); + } + + SUBCASE("vertex_ids should return a correct view") { + sut_type sut{constants::n_elements}; + CHECK(std::ranges::equal(sut.vertex_ids(), constants::vertex_id_view)); + } + + SUBCASE("has_vertex(id) should return true when a vertex with the given id is present in " + "the graph") { + sut_type sut{constants::n_elements}; + + CHECK(std::ranges::all_of(constants::vertex_id_view, [&sut](const auto vertex_id) { + return sut.has_vertex(vertex_id); + })); + CHECK_FALSE(sut.has_vertex(constants::out_of_rng_idx)); + } + + SUBCASE("get_vertex should throw if the given id is invalid") { + sut_type sut{constants::n_elements}; + CHECK_THROWS_AS( + static_cast(sut.get_vertex(static_cast(sut.order()))), + std::out_of_range + ); + } + + SUBCASE("get_vertex should return a vertex with the given id") { + sut_type sut; + const auto added_vertex = sut.add_vertex(); + CHECK_EQ(sut.get_vertex(added_vertex.id()), added_vertex); + } + + // --- degree getters --- + + // --- edge modifiers --- + + SUBCASE("edge modifiers tests for default properties type") { sut_type sut{constants::n_elements}; const auto vertices = sut.vertices(); @@ -549,7 +555,7 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp } } - SUBCASE("edge method tests for non-default properties type") { + SUBCASE("edge modifiers tests for non-default properties type") { using properties_traits_type = add_edge_property; using property_edge_type = typename properties_traits_type::edge_type; gl::graph sut{constants::n_elements}; @@ -682,17 +688,49 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp } } + // --- edge getters --- + + SUBCASE("incident_edges(id) should throw if the vertex_id is invalid") { + sut_type sut{constants::n_elements}; + CHECK_THROWS_AS( + discard_result(sut.incident_edges(constants::out_of_rng_idx)), std::out_of_range + ); + } + + SUBCASE("incident_edges(id) should return a proper iterator range for a valid vertex") { + sut_type sut{1uz}; + + CHECK_NOTHROW([&sut]() { CHECK_EQ(gl::util::range_size(sut.incident_edges(0uz)), 0uz); }()); + } + + SUBCASE("incident_edges(vertex) should throw if the vertex is invalid") { + sut_type sut{constants::n_elements}; + + CHECK_THROWS_AS( + discard_result(sut.incident_edges(fixture.out_of_range_vertex)), std::out_of_range + ); + } + + SUBCASE("incident_edges(vertex) should return a proper iterator range for a valid vertex") { + sut_type sut{1uz}; + const auto vertex = sut.get_vertex(0uz); + + CHECK_NOTHROW([&sut, &vertex]() { + CHECK_EQ(gl::util::range_size(sut.incident_edges(vertex)), 0uz); + }()); + } + SUBCASE("has_edge(vertex, vertex) should throw if one of the vertices is invalid") { sut_type sut{constants::n_elements}; - const auto vd_1 = sut.get_vertex(constants::v1_id); - const auto vd_2 = sut.get_vertex(constants::v2_id); + const auto v1 = sut.get_vertex(constants::v1_id); + const auto v2 = sut.get_vertex(constants::v2_id); CHECK_THROWS_AS( - discard_result(sut.has_edge(fixture.out_of_range_vertex, vd_2)), std::out_of_range + discard_result(sut.has_edge(fixture.out_of_range_vertex, v2)), std::out_of_range ); CHECK_THROWS_AS( - discard_result(sut.has_edge(vd_1, fixture.out_of_range_vertex)), std::out_of_range + discard_result(sut.has_edge(v1, fixture.out_of_range_vertex)), std::out_of_range ); } @@ -700,15 +738,15 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp "vertices in the graph") { sut_type sut{constants::n_elements}; - const auto vd_1 = sut.get_vertex(constants::v1_id); - const auto vd_2 = sut.get_vertex(constants::v2_id); - const auto vd_3 = sut.get_vertex(constants::v3_id); + const auto v1 = sut.get_vertex(constants::v1_id); + const auto v2 = sut.get_vertex(constants::v2_id); + const auto v3 = sut.get_vertex(constants::v3_id); - sut.add_edge(vd_1, vd_2); + sut.add_edge(v1, v2); - CHECK(sut.has_edge(vd_1, vd_2)); - CHECK_FALSE(sut.has_edge(vd_1, vd_3)); - CHECK_FALSE(sut.has_edge(vd_2, vd_3)); + CHECK(sut.has_edge(v1, v2)); + CHECK_FALSE(sut.has_edge(v1, v3)); + CHECK_FALSE(sut.has_edge(v2, v3)); } SUBCASE("get_edge(vertex, vertex) should throw if either vertex is invalid") { @@ -738,22 +776,22 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp SUBCASE("get_edge(vertex, vertex) should return a valid edge if the given vetices are adjacent" ) { sut_type sut{constants::n_elements}; - const auto vd_1 = sut.get_vertex(constants::v1_id); - const auto vd_2 = sut.get_vertex(constants::v2_id); + const auto v1 = sut.get_vertex(constants::v1_id); + const auto v2 = sut.get_vertex(constants::v2_id); - const auto edge = sut.add_edge(vd_1, vd_2); + const auto edge = sut.add_edge(v1, v2); - const auto edge_opt_1 = sut.get_edge(vd_1, vd_2); + const auto edge_opt_1 = sut.get_edge(v1, v2); REQUIRE(edge_opt_1.has_value()); CHECK_EQ(*edge_opt_1, edge); if constexpr (gl::traits::c_undirected_edge) { - const auto edge_opt_2 = sut.get_edge(vd_2, vd_1); + const auto edge_opt_2 = sut.get_edge(v2, v1); REQUIRE(edge_opt_2.has_value()); CHECK_EQ(*edge_opt_2, edge); } else { - CHECK_FALSE(sut.get_edge(vd_2, vd_1).has_value()); + CHECK_FALSE(sut.get_edge(v2, v1).has_value()); } } @@ -805,45 +843,162 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp SUBCASE("get_edges(vertex, vertex) should throw if either vertex is invalid") { sut_type sut{constants::n_elements}; - const auto vd_1 = sut.get_vertex(constants::v1_id); - const auto vd_2 = sut.get_vertex(constants::v2_id); + const auto v1 = sut.get_vertex(constants::v1_id); + const auto v2 = sut.get_vertex(constants::v2_id); CHECK_THROWS_AS( - discard_result(sut.get_edges(fixture.out_of_range_vertex, vd_2)), std::out_of_range + discard_result(sut.get_edges(fixture.out_of_range_vertex, v2)), std::out_of_range ); CHECK_THROWS_AS( - discard_result(sut.get_edges(vd_1, fixture.out_of_range_vertex)), std::out_of_range + discard_result(sut.get_edges(v1, fixture.out_of_range_vertex)), std::out_of_range ); } - SUBCASE("incident_edges(id) should throw if the vertex_id is invalid") { + // --- adjacency and incidence methods --- + + SUBCASE("adjacency and incidence method tests") { sut_type sut{constants::n_elements}; - CHECK_THROWS_AS( - discard_result(sut.incident_edges(constants::out_of_rng_idx)), std::out_of_range - ); - } + const auto v1 = sut.get_vertex(constants::v1_id); + const auto v2 = sut.get_vertex(constants::v2_id); + const auto v3 = sut.get_vertex(constants::v3_id); - SUBCASE("incident_edges(id) should return a proper iterator range for a valid vertex") { - sut_type sut{1uz}; + SUBCASE("are_incident(vertex_id, vertex_id) should throw for out of range vertex ids") { + CHECK_THROWS_AS( + discard_result(sut.are_incident(constants::out_of_rng_idx, constants::v2_id)), + std::out_of_range + ); + CHECK_THROWS_AS( + discard_result(sut.are_incident(constants::v1_id, constants::out_of_rng_idx)), + std::out_of_range + ); + } - CHECK_NOTHROW([&sut]() { CHECK_EQ(gl::util::range_size(sut.incident_edges(0uz)), 0uz); }()); - } + SUBCASE("are_incident(vertex_id, vertex_id) should return true the ids are the same and " + "valid") { + CHECK(std::ranges::all_of(constants::vertex_id_view, [&sut](const auto vertex_id) { + return sut.are_incident(vertex_id, vertex_id); + })); + } - SUBCASE("incident_edges(vertex) should throw if the vertex is invalid") { - sut_type sut{constants::n_elements}; + SUBCASE("are_incident(vertex_id, vertex_id) should return true if there is an edge " + "connecting the given vertices") { + sut.add_edge(v1, v2); - CHECK_THROWS_AS( - discard_result(sut.incident_edges(fixture.out_of_range_vertex)), std::out_of_range - ); - } + CHECK(sut.are_incident(constants::v1_id, constants::v2_id)); + CHECK(sut.are_incident(constants::v2_id, constants::v1_id)); - SUBCASE("incident_edges(vertex) should return a proper iterator range for a valid vertex") { - sut_type sut{1uz}; - const auto vertex = sut.get_vertex(0uz); + CHECK_FALSE(sut.are_incident(constants::v1_id, constants::v3_id)); + CHECK_FALSE(sut.are_incident(constants::v2_id, constants::v3_id)); + } - CHECK_NOTHROW([&sut, &vertex]() { - CHECK_EQ(gl::util::range_size(sut.incident_edges(vertex)), 0uz); - }()); + SUBCASE("are_incident(vertex, vertex) should throw if at least one of the vertices is " + "invalid") { + CHECK_THROWS_AS( + discard_result( + sut.are_incident(fixture.out_of_range_vertex, fixture.out_of_range_vertex) + ), + std::out_of_range + ); + CHECK_THROWS_AS( + discard_result(sut.are_incident(fixture.out_of_range_vertex, v2)), std::out_of_range + ); + CHECK_THROWS_AS( + discard_result(sut.are_incident(v1, fixture.out_of_range_vertex)), std::out_of_range + ); + } + + SUBCASE("are_incident(vertex, vertex) should return true if there is an edge connecting " + "the given vertices") { + sut.add_edge(v1, v2); + + CHECK(sut.are_incident(v1, v2)); + CHECK(sut.are_incident(v2, v1)); + + CHECK_FALSE(sut.are_incident(v1, v3)); + CHECK_FALSE(sut.are_incident(v2, v3)); + } + + SUBCASE("are_incident(vertex, vertex) should return true the vertices are the same and " + "valid") { + CHECK(std::ranges::all_of(sut.vertices(), [&sut](const auto& vertex) { + return sut.are_incident(vertex, vertex); + })); + } + + SUBCASE("are_incident(edge, edge) should throw if either edge is invalid") { + const auto edge = sut.add_edge(v1, v2); + const edge_type invalid_edge{gl::invalid_id, v1.id(), v2.id()}; + + CHECK_THROWS_AS( + discard_result(sut.are_incident(edge, invalid_edge)), std::invalid_argument + ); + CHECK_THROWS_AS( + discard_result(sut.are_incident(invalid_edge, edge)), std::invalid_argument + ); + } + + SUBCASE("are_incident(edge, edge) should return true only when the edges share a common " + "vertex") { + const auto edge_1 = sut.add_edge(v1, v2); + const auto edge_2 = sut.add_edge(v2, v3); + const auto loop_3 = sut.add_edge(v3, v3); + + CHECK(sut.are_incident(edge_1, edge_2)); + CHECK(sut.are_incident(edge_2, edge_1)); + CHECK_FALSE(sut.are_incident(edge_1, loop_3)); + CHECK_FALSE(sut.are_incident(loop_3, edge_1)); + } + + SUBCASE("are_incident(vertex and edge pair) should throw if the vertex is invalid") { + const auto edge = sut.add_edge(v1, v2); + + CHECK_THROWS_AS( + discard_result(sut.are_incident(fixture.out_of_range_vertex, edge)), + std::out_of_range + ); + CHECK_THROWS_AS( + discard_result(sut.are_incident(fixture.out_of_range_vertex, edge)), + std::out_of_range + ); + + CHECK_THROWS_AS( + discard_result(sut.are_incident(edge, fixture.out_of_range_vertex)), + std::out_of_range + ); + CHECK_THROWS_AS( + discard_result(sut.are_incident(edge, fixture.out_of_range_vertex)), + std::out_of_range + ); + } + + SUBCASE("are_incident(vertex and edge pair) should throw if the edge is invalid") { + const edge_type invalid_edge{gl::invalid_id, v1.id(), v2.id()}; + + CHECK_THROWS_AS( + discard_result(sut.are_incident(v1, invalid_edge)), std::invalid_argument + ); + CHECK_THROWS_AS( + discard_result(sut.are_incident(v1, invalid_edge)), std::invalid_argument + ); + + CHECK_THROWS_AS( + discard_result(sut.are_incident(invalid_edge, v2)), std::invalid_argument + ); + CHECK_THROWS_AS( + discard_result(sut.are_incident(invalid_edge, v2)), std::invalid_argument + ); + } + + SUBCASE("are_incident(vertex and edge pair) should return true only when the edge and the " + "vertex are incident with each other") { + const auto edge = sut.add_edge(v1, v2); + + CHECK(sut.are_incident(v1, edge)); + CHECK(sut.are_incident(v2, edge)); + + CHECK(sut.are_incident(edge, v1)); + CHECK(sut.are_incident(edge, v2)); + } } // --- comparison and cloning --- @@ -904,18 +1059,178 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp } TEST_CASE_TEMPLATE_INSTANTIATE( - graph_traits_template, + common_graph_traits_template, gl::list_graph_traits, // directed adjacency list gl::list_graph_traits, // undirected adjacency list gl::flat_list_graph_traits, // directed flat adjacency list gl::flat_list_graph_traits, // undirected flat adjacency list gl::matrix_graph_traits, // directed adjacency matrix gl::matrix_graph_traits, // undirected adjacency matrix - gl::matrix_graph_traits, // directed adj matrix - gl::matrix_graph_traits // undirected adj matrix + gl::flat_matrix_graph_traits, // directed flat adjacency matrix + gl::flat_matrix_graph_traits // undirected flat adjacency matrix +); + +TEST_CASE_TEMPLATE_DEFINE( + "directed graph structure tests", TraitsType, directed_graph_traits_template +) { + using sut_type = gl::graph; + + const auto n_vertices = constants::n_elements_top; + + sut_type sut; + std::deque expected_in_deg_list, expected_out_deg_list; + + SUBCASE("clique") { + sut = gl::topology::clique(n_vertices); + expected_in_deg_list = std::deque(n_vertices, n_vertices - 1uz); + expected_out_deg_list = expected_in_deg_list; + } + + SUBCASE("clique with an additional loop") { + sut = gl::topology::clique(n_vertices); + sut.add_edge(0uz, 0uz); + + expected_in_deg_list = std::deque(n_vertices, n_vertices - 1uz); + expected_in_deg_list.front()++; + + expected_out_deg_list = expected_in_deg_list; + } + + SUBCASE("cycle") { + sut = gl::topology::cycle(n_vertices); + expected_in_deg_list = std::deque(n_vertices, 1uz); + expected_out_deg_list = expected_in_deg_list; + } + + SUBCASE("path") { + sut = gl::topology::path(n_vertices); + + expected_in_deg_list = std::deque(n_vertices - 1uz, 1uz); + expected_in_deg_list.push_front(0uz); + + expected_out_deg_list = std::deque(n_vertices - 1uz, 1uz); + expected_out_deg_list.push_back(0uz); + } + + CAPTURE(sut); + CAPTURE(expected_in_deg_list); + CAPTURE(expected_out_deg_list); + + std::deque expected_deg_list(n_vertices); + std::ranges::transform( + expected_in_deg_list, + expected_out_deg_list, + expected_deg_list.begin(), + std::plus{} + ); + + gl::size_type i = 0uz; + CHECK(std::ranges::all_of(sut.vertices(), [&](const auto& vertex) { + const bool result = + sut.in_degree(vertex) == expected_in_deg_list[i] + and sut.out_degree(vertex) == expected_out_deg_list[i] + and sut.degree(vertex) == expected_deg_list[i]; + ++i; + return result; + })); + + i = 0uz; + CHECK(std::ranges::all_of( + sut.vertices(), + [&](const gl::default_id_type vertex_id) { + const bool result = + sut.in_degree(vertex_id) == expected_in_deg_list[i] + and sut.out_degree(vertex_id) == expected_out_deg_list[i] + and sut.degree(vertex_id) == expected_deg_list[i]; + ++i; + return result; + }, + get_id + )); +} + +TEST_CASE_TEMPLATE_INSTANTIATE( + directed_graph_traits_template, + gl::list_graph_traits, // directed adjacency list graph + gl::flat_list_graph_traits, // directed flat adjacency list graph + gl::matrix_graph_traits, // directed adjacency matrix graph + gl::flat_matrix_graph_traits // directed flat adjacency matrix graph +); + +TEST_CASE_TEMPLATE_DEFINE( + "undirected graph structure tests", TraitsType, undirected_graph_traits_template +) { + using sut_type = gl::graph; + + const auto n_vertices = constants::n_elements_top; + + sut_type sut; + std::deque expected_deg_list; + + SUBCASE("clique") { + sut = gl::topology::clique(n_vertices); + expected_deg_list = std::deque(n_vertices, n_vertices - 1uz); + } + + SUBCASE("clique with an additional loop") { + sut = gl::topology::clique(n_vertices); + sut.add_edge(0uz, 0uz); + + expected_deg_list = std::deque(n_vertices, n_vertices - 1uz); + expected_deg_list.front() += 2uz; // loops counted twice + } + + SUBCASE("cycle") { + sut = gl::topology::cycle(n_vertices); + expected_deg_list = std::deque(n_vertices, 2uz); + } + + SUBCASE("path") { + sut = gl::topology::path(n_vertices); + + expected_deg_list = std::deque(n_vertices - 2uz, 2uz); + expected_deg_list.push_front(1uz); + expected_deg_list.push_back(1uz); + } + + CAPTURE(sut); + CAPTURE(expected_deg_list); + + gl::size_type i = 0uz; + CHECK(std::ranges::all_of(sut.vertices(), [&](const auto& vertex) { + const auto expected_deg = expected_deg_list[i]; + const bool result = + sut.in_degree(vertex) == expected_deg and sut.out_degree(vertex) == expected_deg + and sut.degree(vertex) == expected_deg; + ++i; + return result; + })); + + i = 0uz; + CHECK(std::ranges::all_of( + sut.vertices(), + [&](const gl::default_id_type vertex_id) { + const auto expected_deg = expected_deg_list[i]; + const bool result = + sut.in_degree(vertex_id) == expected_deg + and sut.out_degree(vertex_id) == expected_deg + and sut.degree(vertex_id) == expected_deg; + ++i; + return result; + }, + get_id + )); +} + +TEST_CASE_TEMPLATE_INSTANTIATE( + undirected_graph_traits_template, + gl::list_graph_traits, // undirected adjacency list graph + gl::flat_list_graph_traits, // undirected flat adjacency list graph + gl::matrix_graph_traits, // undirected adjacency matrix graph + gl::flat_matrix_graph_traits // undirected flat adjacency matrix graph ); -TEST_CASE_TEMPLATE_DEFINE("properties getter tests", TraitsType, property_graph_traits_template) { +TEST_CASE_TEMPLATE_DEFINE("property getter tests", TraitsType, property_graph_traits_template) { using sut_type = gl::graph; sut_type sut{constants::n_elements}; diff --git a/tests/source/gl/test_graph_file_io.cpp b/tests/source/gl/test_graph_file_io.cpp index 1390e71..b30230b 100644 --- a/tests/source/gl/test_graph_file_io.cpp +++ b/tests/source/gl/test_graph_file_io.cpp @@ -4,7 +4,7 @@ #include #include -#include +#include #include diff --git a/tests/source/gl/test_graph_incidence.cpp b/tests/source/gl/test_graph_incidence.cpp deleted file mode 100644 index e0f78d1..0000000 --- a/tests/source/gl/test_graph_incidence.cpp +++ /dev/null @@ -1,170 +0,0 @@ -#include "testing/gl/constants.hpp" -#include "testing/gl/functional.hpp" - -#include - -#include - -namespace gl_testing { - -TEST_SUITE_BEGIN("test_graph_incidence"); - -TEST_CASE_TEMPLATE_DEFINE("incidence functions tests", SutType, graph_type_template) { - using vertex_type = gl::vertex_descriptor<>; - - SutType sut{constants::n_elements}; - - const auto vd_1 = sut.get_vertex(constants::v1_id); - const auto vd_2 = sut.get_vertex(constants::v2_id); - const auto vd_3 = sut.get_vertex(constants::v3_id); - vertex_type out_of_range_vertex{constants::out_of_rng_idx}; - - SUBCASE("are_incident(vertex_id, vertex_id) should throw for out of range vertex ids") { - CHECK_THROWS_AS( - discard_result(sut.are_incident(constants::out_of_rng_idx, constants::v2_id)), - std::out_of_range - ); - CHECK_THROWS_AS( - discard_result(sut.are_incident(constants::v1_id, constants::out_of_rng_idx)), - std::out_of_range - ); - } - - SUBCASE("are_incident(vertex_id, vertex_id) should return true the ids are the same and valid" - ) { - CHECK(std::ranges::all_of(constants::vertex_id_view, [&sut](const auto vertex_id) { - return sut.are_incident(vertex_id, vertex_id); - })); - } - - SUBCASE("are_incident(vertex_id, vertex_id) should return true if there is an edge connecting " - "the given vertices") { - sut.add_edge(vd_1, vd_2); - - CHECK(sut.are_incident(constants::v1_id, constants::v2_id)); - CHECK(sut.are_incident(constants::v2_id, constants::v1_id)); - - CHECK_FALSE(sut.are_incident(constants::v1_id, constants::v3_id)); - CHECK_FALSE(sut.are_incident(constants::v2_id, constants::v3_id)); - } - - SUBCASE("are_incident(vertex, vertex) should throw if at least one of the vertices is invalid" - ) { - CHECK_THROWS_AS( - discard_result(sut.are_incident(out_of_range_vertex, out_of_range_vertex)), - std::out_of_range - ); - CHECK_THROWS_AS( - discard_result(sut.are_incident(out_of_range_vertex, vd_2)), std::out_of_range - ); - CHECK_THROWS_AS( - discard_result(sut.are_incident(vd_1, out_of_range_vertex)), std::out_of_range - ); - } - - SUBCASE("are_incident(vertex, vertex) should return true if there is an edge connecting the " - "given vertices") { - sut.add_edge(vd_1, vd_2); - - CHECK(sut.are_incident(vd_1, vd_2)); - CHECK(sut.are_incident(vd_2, vd_1)); - - CHECK_FALSE(sut.are_incident(vd_1, vd_3)); - CHECK_FALSE(sut.are_incident(vd_2, vd_3)); - } - - SUBCASE("are_incident(vertex, vertex) should return true the vertices are the same and valid") { - CHECK(std::ranges::all_of(sut.vertices(), [&sut](const auto& vertex) { - return sut.are_incident(vertex, vertex); - })); - } - - SUBCASE("are_incident(vertex and edge pair) should throw if the vertex is invalid") { - const auto edge = sut.add_edge(vd_1, vd_2); - - CHECK_THROWS_AS( - discard_result(sut.are_incident(out_of_range_vertex, edge)), std::out_of_range - ); - CHECK_THROWS_AS( - discard_result(sut.are_incident(out_of_range_vertex, edge)), std::out_of_range - ); - - CHECK_THROWS_AS( - discard_result(sut.are_incident(edge, out_of_range_vertex)), std::out_of_range - ); - CHECK_THROWS_AS( - discard_result(sut.are_incident(edge, out_of_range_vertex)), std::out_of_range - ); - } - - SUBCASE("are_incident(vertex and edge pair) should throw if the edge is invalid") { - const typename SutType::edge_type invalid_edge{gl::invalid_id, vd_1.id(), vd_2.id()}; - - CHECK_THROWS_AS( - discard_result(sut.are_incident(vd_1, invalid_edge)), std::invalid_argument - ); - CHECK_THROWS_AS( - discard_result(sut.are_incident(vd_1, invalid_edge)), std::invalid_argument - ); - - CHECK_THROWS_AS( - discard_result(sut.are_incident(invalid_edge, vd_2)), std::invalid_argument - ); - CHECK_THROWS_AS( - discard_result(sut.are_incident(invalid_edge, vd_2)), std::invalid_argument - ); - } - - SUBCASE("are_incident(vertex and edge pair) should return true only when the edge and the " - "vertex are incident with each other") { - const auto edge = sut.add_edge(vd_1, vd_2); - - CHECK(sut.are_incident(vd_1, edge)); - CHECK(sut.are_incident(vd_2, edge)); - - CHECK(sut.are_incident(edge, vd_1)); - CHECK(sut.are_incident(edge, vd_2)); - } - - SUBCASE("are_incident(edge, edge) should throw if either edge is invalid") { - const auto edge = sut.add_edge(vd_1, vd_2); - const typename SutType::edge_type invalid_edge{gl::invalid_id, vd_1.id(), vd_2.id()}; - - CHECK_THROWS_AS( - discard_result(sut.are_incident(edge, invalid_edge)), std::invalid_argument - ); - CHECK_THROWS_AS( - discard_result(sut.are_incident(invalid_edge, edge)), std::invalid_argument - ); - } - - SUBCASE("are_incident(edge, edge) should return true only when the edges share a common vertex" - ) { - const auto edge_1 = sut.add_edge(vd_1, vd_2); - const auto edge_2 = sut.add_edge(vd_2, vd_3); - const auto loop_3 = sut.add_edge(vd_3, vd_3); - - CHECK(sut.are_incident(edge_1, edge_2)); - CHECK(sut.are_incident(edge_2, edge_1)); - CHECK_FALSE(sut.are_incident(edge_1, loop_3)); - CHECK_FALSE(sut.are_incident(loop_3, edge_1)); - } -} - -TEST_CASE_TEMPLATE_INSTANTIATE( - graph_type_template, - gl::graph>, // directed graph - gl::graph>, // undirected graph - gl::graph>, // directed adj list - gl::graph>, // undirected adj list - gl::graph>, // directed flat adj list - gl::graph>, // undirected flat adj list - gl::graph>, // directed adj matrix - gl::graph>, // undirected adj matrix - gl::graph>, // directed flat adj matrix - gl::graph> // undirected flat adj matrix -); - -TEST_SUITE_END(); // test_graph_incidence - -} // namespace gl_testing diff --git a/tests/source/gl/test_graph_io.cpp b/tests/source/gl/test_graph_io.cpp index 876cace..94dc822 100644 --- a/tests/source/gl/test_graph_io.cpp +++ b/tests/source/gl/test_graph_io.cpp @@ -3,7 +3,7 @@ #include #include -#include +#include #include diff --git a/tests/source/gl/test_graph_topology_builders.cpp b/tests/source/gl/test_graph_topology_builders.cpp index 691f0e2..7354c66 100644 --- a/tests/source/gl/test_graph_topology_builders.cpp +++ b/tests/source/gl/test_graph_topology_builders.cpp @@ -1,6 +1,6 @@ #include "testing/gl/constants.hpp" -#include +#include #include diff --git a/tests/source/gl/test_vertex_degree_getters.cpp b/tests/source/gl/test_vertex_degree_getters.cpp deleted file mode 100644 index b6dadcc..0000000 --- a/tests/source/gl/test_vertex_degree_getters.cpp +++ /dev/null @@ -1,178 +0,0 @@ -#include "testing/gl/constants.hpp" - -#include -#include - -#include - -#include - -namespace gl_testing { - -TEST_SUITE_BEGIN("test_vertex_degree_getters"); - -inline constexpr auto get_id = [](auto&& element) -> gl::default_id_type { return element.id(); }; - -TEST_CASE_TEMPLATE_DEFINE( - "vertex degree getter tests for directed graphs", TraitsType, directed_graph_traits_template -) { - using sut_type = gl::graph; - - const auto n_vertices = constants::n_elements_top; - - sut_type sut; - std::deque expected_in_deg_list, expected_out_deg_list; - - SUBCASE("clique") { - sut = gl::topology::clique(n_vertices); - expected_in_deg_list = std::deque(n_vertices, n_vertices - 1uz); - expected_out_deg_list = expected_in_deg_list; - } - - SUBCASE("clique with an additional loop") { - sut = gl::topology::clique(n_vertices); - sut.add_edge(0uz, 0uz); - - expected_in_deg_list = std::deque(n_vertices, n_vertices - 1uz); - expected_in_deg_list.front()++; - - expected_out_deg_list = expected_in_deg_list; - } - - SUBCASE("cycle") { - sut = gl::topology::cycle(n_vertices); - expected_in_deg_list = std::deque(n_vertices, 1uz); - expected_out_deg_list = expected_in_deg_list; - } - - SUBCASE("path") { - sut = gl::topology::path(n_vertices); - - expected_in_deg_list = std::deque(n_vertices - 1uz, 1uz); - expected_in_deg_list.push_front(0uz); - - expected_out_deg_list = std::deque(n_vertices - 1uz, 1uz); - expected_out_deg_list.push_back(0uz); - } - - CAPTURE(sut); - CAPTURE(expected_in_deg_list); - CAPTURE(expected_out_deg_list); - - std::deque expected_deg_list(n_vertices); - std::ranges::transform( - expected_in_deg_list, - expected_out_deg_list, - expected_deg_list.begin(), - std::plus{} - ); - - gl::size_type i = 0uz; - CHECK(std::ranges::all_of(sut.vertices(), [&](const auto& vertex) { - const bool result = - sut.in_degree(vertex) == expected_in_deg_list[i] - and sut.out_degree(vertex) == expected_out_deg_list[i] - and sut.degree(vertex) == expected_deg_list[i]; - ++i; - return result; - })); - - i = 0uz; - CHECK(std::ranges::all_of( - sut.vertices(), - [&](const gl::default_id_type vertex_id) { - const bool result = - sut.in_degree(vertex_id) == expected_in_deg_list[i] - and sut.out_degree(vertex_id) == expected_out_deg_list[i] - and sut.degree(vertex_id) == expected_deg_list[i]; - ++i; - return result; - }, - get_id - )); -} - -TEST_CASE_TEMPLATE_INSTANTIATE( - directed_graph_traits_template, - gl::list_graph_traits, // directed adjacency list graph - gl::flat_list_graph_traits, // directed flat adjacency list graph - gl::matrix_graph_traits, // directed adjacency matrix graph - gl::flat_matrix_graph_traits // directed flat adjacency matrix graph -); - -TEST_CASE_TEMPLATE_DEFINE( - "vertex degree getter tests for undirected graphs", TraitsType, undirected_graph_traits_template -) { - using sut_type = gl::graph; - - const auto n_vertices = constants::n_elements_top; - - sut_type sut; - std::deque expected_deg_list; - - SUBCASE("clique") { - sut = gl::topology::clique(n_vertices); - expected_deg_list = std::deque(n_vertices, n_vertices - 1uz); - } - - SUBCASE("clique with an additional loop") { - sut = gl::topology::clique(n_vertices); - sut.add_edge(0uz, 0uz); - - expected_deg_list = std::deque(n_vertices, n_vertices - 1uz); - expected_deg_list.front() += 2uz; // loops counted twice - } - - SUBCASE("cycle") { - sut = gl::topology::cycle(n_vertices); - expected_deg_list = std::deque(n_vertices, 2uz); - } - - SUBCASE("path") { - sut = gl::topology::path(n_vertices); - - expected_deg_list = std::deque(n_vertices - 2uz, 2uz); - expected_deg_list.push_front(1uz); - expected_deg_list.push_back(1uz); - } - - CAPTURE(sut); - CAPTURE(expected_deg_list); - - gl::size_type i = 0uz; - CHECK(std::ranges::all_of(sut.vertices(), [&](const auto& vertex) { - const auto expected_deg = expected_deg_list[i]; - const bool result = - sut.in_degree(vertex) == expected_deg and sut.out_degree(vertex) == expected_deg - and sut.degree(vertex) == expected_deg; - ++i; - return result; - })); - - i = 0uz; - CHECK(std::ranges::all_of( - sut.vertices(), - [&](const gl::default_id_type vertex_id) { - const auto expected_deg = expected_deg_list[i]; - const bool result = - sut.in_degree(vertex_id) == expected_deg - and sut.out_degree(vertex_id) == expected_deg - and sut.degree(vertex_id) == expected_deg; - ++i; - return result; - }, - get_id - )); -} - -TEST_CASE_TEMPLATE_INSTANTIATE( - undirected_graph_traits_template, - gl::list_graph_traits, // undirected adjacency list graph - gl::flat_list_graph_traits, // undirected flat adjacency list graph - gl::matrix_graph_traits, // undirected adjacency matrix graph - gl::flat_matrix_graph_traits // undirected flat adjacency matrix graph -); - -TEST_SUITE_END(); // test_vertex_degree_getters - -} // namespace gl_testing diff --git a/tests/source/gl/test_vertex_descriptor.cpp b/tests/source/gl/test_vertex_descriptor.cpp index 68bd467..4981c21 100644 --- a/tests/source/gl/test_vertex_descriptor.cpp +++ b/tests/source/gl/test_vertex_descriptor.cpp @@ -16,20 +16,20 @@ TEST_CASE("id() should return the correct vertex id") { } TEST_CASE("vertex_descriptor objects should be compared by id") { - const gl::vertex_descriptor vd_1{constants::v1_id}; - const gl::vertex_descriptor vd_2{constants::v2_id}; + const gl::vertex_descriptor v1{constants::v1_id}; + const gl::vertex_descriptor v2{constants::v2_id}; - REQUIRE_NE(vd_1, vd_2); - CHECK_EQ(vd_1, vd_1); - CHECK_EQ(vd_2, vd_2); + REQUIRE_NE(v1, v2); + CHECK_EQ(v1, v1); + CHECK_EQ(v2, v2); - CHECK_LE(vd_1, vd_1); - CHECK_LE(vd_1, vd_2); - CHECK_LT(vd_1, vd_2); + CHECK_LE(v1, v1); + CHECK_LE(v1, v2); + CHECK_LT(v1, v2); - CHECK_GE(vd_2, vd_2); - CHECK_GE(vd_2, vd_1); - CHECK_GT(vd_2, vd_1); + CHECK_GE(v2, v2); + CHECK_GE(v2, v1); + CHECK_GT(v2, v1); } TEST_CASE("vertex_descriptor should be valid only if it has a valid id") { From 5539241ce32a638aa6b255b34f8c61de2696e56b Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Fri, 10 Apr 2026 22:13:58 +0200 Subject: [PATCH 4/9] incident_edges directed logic error fix --- .../gl/algorithm/spanning_tree/prim_mst.hpp | 2 +- include/gl/algorithm/templates/bfs.hpp | 2 +- include/gl/algorithm/templates/dfs.hpp | 4 +- include/gl/algorithm/templates/pfs.hpp | 2 +- include/gl/graph.hpp | 16 +- include/gl/impl/adjacency_list.hpp | 20 +-- include/gl/impl/adjacency_matrix.hpp | 4 +- .../gl/impl/specialized/adjacency_list.hpp | 31 +++- .../gl/impl/specialized/adjacency_matrix.hpp | 31 ++++ .../impl/specialized/flat_adjacency_list.hpp | 27 ++++ .../specialized/flat_adjacency_matrix.hpp | 31 ++++ tests/include/testing/gl/io_common.hpp | 4 +- tests/source/gl/test_adjacency_list.cpp | 54 ++++--- tests/source/gl/test_adjacency_matrix.cpp | 89 +++++------ tests/source/gl/test_graph.cpp | 146 ++++++++---------- tests/source/gl/test_graph_file_io.cpp | 2 +- tests/source/gl/test_graph_io.cpp | 4 +- .../gl/test_graph_topology_builders.cpp | 8 +- 18 files changed, 295 insertions(+), 182 deletions(-) diff --git a/include/gl/algorithm/spanning_tree/prim_mst.hpp b/include/gl/algorithm/spanning_tree/prim_mst.hpp index da27b41..fcac41c 100644 --- a/include/gl/algorithm/spanning_tree/prim_mst.hpp +++ b/include/gl/algorithm/spanning_tree/prim_mst.hpp @@ -51,7 +51,7 @@ template if (root_id == invalid_id) root_id = initial_id; - for (const auto& edge : graph.incident_edges(root_id)) + for (const auto& edge : graph.out_edges(root_id)) edge_queue.emplace(edge); // mark the root vertex as visited diff --git a/include/gl/algorithm/templates/bfs.hpp b/include/gl/algorithm/templates/bfs.hpp index 20de8c1..92d1f84 100644 --- a/include/gl/algorithm/templates/bfs.hpp +++ b/include/gl/algorithm/templates/bfs.hpp @@ -55,7 +55,7 @@ bool bfs( if (not visit(node.vertex_id, node.pred_id)) return false; - for (const auto& edge : graph.incident_edges(node.vertex_id)) { + for (const auto& edge : graph.out_edges(node.vertex_id)) { const auto target_vertex_id = edge.other(node.vertex_id); const auto enqueue = enqueue_vertex_pred(target_vertex_id, edge); if (enqueue == decision::abort) diff --git a/include/gl/algorithm/templates/dfs.hpp b/include/gl/algorithm/templates/dfs.hpp index ce150d3..68d81d4 100644 --- a/include/gl/algorithm/templates/dfs.hpp +++ b/include/gl/algorithm/templates/dfs.hpp @@ -55,7 +55,7 @@ bool dfs( if (not visit(node.vertex_id, node.pred_id)) return false; - for (const auto& edge : graph.incident_edges(node.vertex_id)) { + for (const auto& edge : graph.out_edges(node.vertex_id)) { const auto target_vertex_id = edge.other(node.vertex_id); if (enqueue_vertex_pred(target_vertex_id, edge)) s.emplace(target_vertex_id, node.vertex_id); @@ -96,7 +96,7 @@ void r_dfs( visit(vertex_id, pred_id); // recursively search vertices adjacent to the current vertex - for (const auto& edge : graph.incident_edges(vertex_id)) { + for (const auto& edge : graph.out_edges(vertex_id)) { const auto target_vertex_id = edge.other(vertex_id); if (enqueue_vertex_pred(target_vertex_id, edge)) r_dfs( diff --git a/include/gl/algorithm/templates/pfs.hpp b/include/gl/algorithm/templates/pfs.hpp index 9b33b40..6270fbe 100644 --- a/include/gl/algorithm/templates/pfs.hpp +++ b/include/gl/algorithm/templates/pfs.hpp @@ -59,7 +59,7 @@ bool pfs( if (not visit(node.vertex_id, node.pred_id)) return false; - for (const auto& edge : graph.incident_edges(node.vertex_id)) { + for (const auto& edge : graph.out_edges(node.vertex_id)) { const auto target_vertex_id = edge.other(node.vertex_id); const auto enqueue = enqueue_vertex_pred(target_vertex_id, edge); if (enqueue == decision::abort) diff --git a/include/gl/graph.hpp b/include/gl/graph.hpp index 82db400..924d4a4 100644 --- a/include/gl/graph.hpp +++ b/include/gl/graph.hpp @@ -734,7 +734,7 @@ class graph final { for (const auto& vertex : this->vertices()) { os << "- " << vertex << "\n incident edges:\n"; - for (const auto& edge : this->incident_edges(vertex.id())) + for (const auto& edge : this->out_edges(vertex.id())) os << "\t- " << edge << '\n'; } } @@ -744,7 +744,7 @@ class graph final { for (const auto& vertex : this->vertices()) { os << "- " << vertex << " :"; - for (const auto& edge : this->incident_edges(vertex.id())) + for (const auto& edge : this->out_edges(vertex.id())) os << ' ' << edge; os << '\n'; } @@ -773,8 +773,8 @@ class graph final { if constexpr (traits::c_writable) { if (with_edge_properties) { - const auto print_incident_edges = [this, &os](const id_type vertex_id) { - for (const auto& edge : this->incident_edges(vertex_id)) { + const auto print_out_edges = [this, &os](const id_type vertex_id) { + for (const auto& edge : this->out_edges(vertex_id)) { if (edge.source() != vertex_id) continue; // vertex is not the source os << edge.source() << ' ' << edge.target() << ' ' << edge.properties() @@ -783,14 +783,14 @@ class graph final { }; for (const auto vertex_id : this->vertex_ids()) - print_incident_edges(vertex_id); + print_out_edges(vertex_id); return; } } - const auto print_incident_edges = [this, &os](const id_type vertex_id) { - for (const auto& edge : this->incident_edges(vertex_id)) { + const auto print_out_edges = [this, &os](const id_type vertex_id) { + for (const auto& edge : this->out_edges(vertex_id)) { if (edge.source() != vertex_id) continue; // vertex is not the source os << edge.source() << ' ' << edge.target() << '\n'; @@ -798,7 +798,7 @@ class graph final { }; for (const auto vertex_id : this->vertex_ids()) - print_incident_edges(vertex_id); + print_out_edges(vertex_id); } void _gsf_read(std::istream& is) { diff --git a/include/gl/impl/adjacency_list.hpp b/include/gl/impl/adjacency_list.hpp index dee520c..90f2f83 100644 --- a/include/gl/impl/adjacency_list.hpp +++ b/include/gl/impl/adjacency_list.hpp @@ -155,9 +155,9 @@ class adjacency_list final { [[nodiscard]] std::optional get_edge(id_type source_id, id_type target_id) const requires(traits::c_has_empty_properties) { - const auto& incident_edges = this->_list[to_idx(source_id)]; - const auto item_it = std::ranges::find(incident_edges, target_id, &item_type::vertex_id); - if (item_it == incident_edges.cend()) + const auto& out_edges = this->_list[to_idx(source_id)]; + const auto item_it = std::ranges::find(out_edges, target_id, &item_type::vertex_id); + if (item_it == out_edges.cend()) return std::nullopt; return std::make_optional(item_it->edge_id, source_id, target_id); } @@ -167,11 +167,11 @@ class adjacency_list final { ) const requires(traits::c_has_non_empty_properties) { - const auto& incident_edges = this->_list[to_idx(source_id)]; - const auto item_it = std::ranges::find(incident_edges, target_id, [](const auto& item) { + const auto& out_edges = this->_list[to_idx(source_id)]; + const auto item_it = std::ranges::find(out_edges, target_id, [](const auto& item) { return item.vertex_id; }); - if (item_it == incident_edges.cend()) + if (item_it == out_edges.cend()) return std::nullopt; return std::make_optional( item_it->edge_id, source_id, target_id, *edge_properties_map[to_idx(item_it->edge_id)] @@ -209,7 +209,7 @@ class adjacency_list final { [[nodiscard]] gl_attr_force_inline auto incident_edges(id_type vertex_id) const requires(traits::c_has_empty_properties) { - return this->out_edges(vertex_id); + return specialized_impl::incident_edges(*this, vertex_id); } [[nodiscard]] gl_attr_force_inline auto incident_edges( @@ -217,7 +217,7 @@ class adjacency_list final { ) const requires(traits::c_has_non_empty_properties) { - return this->out_edges(vertex_id, edge_properties_map); + return specialized_impl::incident_edges(*this, vertex_id, edge_properties_map); } [[nodiscard]] gl_attr_force_inline auto in_edges(id_type vertex_id) const @@ -275,14 +275,14 @@ class adjacency_list final { [[nodiscard]] gl_attr_force_inline auto at(id_type vertex_id) const requires(traits::c_has_empty_properties) { - return this->incident_edges(vertex_id); + return this->out_edges(vertex_id); } [[nodiscard]] gl_attr_force_inline auto at(id_type vertex_id, const auto& edge_properties_map) const requires(traits::c_has_non_empty_properties) { - return this->incident_edges(vertex_id, edge_properties_map); + return this->out_edges(vertex_id, edge_properties_map); } // --- comparison --- diff --git a/include/gl/impl/adjacency_matrix.hpp b/include/gl/impl/adjacency_matrix.hpp index 78bc73c..832e688 100644 --- a/include/gl/impl/adjacency_matrix.hpp +++ b/include/gl/impl/adjacency_matrix.hpp @@ -200,7 +200,7 @@ class adjacency_matrix final { [[nodiscard]] gl_attr_force_inline auto incident_edges(id_type vertex_id) const requires(traits::c_has_empty_properties) { - return this->out_edges(vertex_id); + return specialized_impl::incident_edges(*this, vertex_id); } [[nodiscard]] gl_attr_force_inline auto incident_edges( @@ -208,7 +208,7 @@ class adjacency_matrix final { ) const requires(traits::c_has_non_empty_properties) { - return this->out_edges(vertex_id, edge_properties_map); + return specialized_impl::incident_edges(*this, vertex_id, edge_properties_map); } [[nodiscard]] gl_attr_force_inline auto in_edges(id_type vertex_id) const diff --git a/include/gl/impl/specialized/adjacency_list.hpp b/include/gl/impl/specialized/adjacency_list.hpp index 09c445c..839c3f4 100644 --- a/include/gl/impl/specialized/adjacency_list.hpp +++ b/include/gl/impl/specialized/adjacency_list.hpp @@ -118,9 +118,9 @@ struct directed_adjacency_list { [[nodiscard]] static size_type in_degree(const impl_type& self, id_type vertex_id) { size_type in_deg = 0uz; - for (const auto& incident_edges : self._list) + for (const auto& out_edges : self._list) in_deg += static_cast( - std::ranges::count(incident_edges, vertex_id, &item_type::vertex_id) + std::ranges::count(out_edges, vertex_id, &item_type::vertex_id) ); return in_deg; @@ -191,6 +191,21 @@ struct directed_adjacency_list { // --- edge getters --- + [[nodiscard]] gl_attr_force_inline static auto incident_edges( + const impl_type& self, id_type vertex_id + ) { + return util::concat(self.in_edges(vertex_id), self.out_edges(vertex_id)); + } + + [[nodiscard]] gl_attr_force_inline static auto incident_edges( + const impl_type& self, id_type vertex_id, const auto& edge_properties_map + ) { + return util::concat( + self.in_edges(vertex_id, edge_properties_map), + self.out_edges(vertex_id, edge_properties_map) + ); + } + [[nodiscard]] static auto in_edges(const impl_type& self, id_type vertex_id) { std::vector in_edges; for (id_type src_id = initial_id; src_id < self._list.size(); ++src_id) { @@ -337,6 +352,18 @@ struct undirected_adjacency_list { // --- edge getters --- + [[nodiscard]] gl_attr_force_inline static auto incident_edges( + const impl_type& self, id_type vertex_id + ) { + return self.out_edges(vertex_id); + } + + [[nodiscard]] gl_attr_force_inline static auto incident_edges( + const impl_type& self, id_type vertex_id, const auto& edge_properties_map + ) { + return self.out_edges(vertex_id, edge_properties_map); + } + [[nodiscard]] gl_attr_force_inline static auto in_edges( const impl_type& self, id_type vertex_id ) { diff --git a/include/gl/impl/specialized/adjacency_matrix.hpp b/include/gl/impl/specialized/adjacency_matrix.hpp index d320a12..2f3e4da 100644 --- a/include/gl/impl/specialized/adjacency_matrix.hpp +++ b/include/gl/impl/specialized/adjacency_matrix.hpp @@ -225,6 +225,23 @@ struct directed_adjacency_matrix { gl_attr_force_inline static void remove_edge(impl_type& self, const edge_type& edge) { detail::strict_get(self._matrix, edge) = invalid_id; } + + // --- edge getters --- + + [[nodiscard]] gl_attr_force_inline static auto incident_edges( + const impl_type& self, id_type vertex_id + ) { + return util::concat(self.in_edges(vertex_id), self.out_edges(vertex_id)); + } + + [[nodiscard]] gl_attr_force_inline static auto incident_edges( + const impl_type& self, id_type vertex_id, const auto& edge_properties_map + ) { + return util::concat( + self.in_edges(vertex_id, edge_properties_map), + self.out_edges(vertex_id, edge_properties_map) + ); + } }; template AdjacencyMatrix> @@ -396,6 +413,20 @@ struct undirected_adjacency_matrix { self._matrix[to_idx(edge.target())][to_idx(edge.source())] = invalid_id; } } + + // --- edge getters --- + + [[nodiscard]] gl_attr_force_inline static auto incident_edges( + const impl_type& self, id_type vertex_id + ) { + return self.out_edges(vertex_id); + } + + [[nodiscard]] gl_attr_force_inline static auto incident_edges( + const impl_type& self, id_type vertex_id, const auto& edge_properties_map + ) { + return self.out_edges(vertex_id, edge_properties_map); + } }; template AdjacencyMatrix> diff --git a/include/gl/impl/specialized/flat_adjacency_list.hpp b/include/gl/impl/specialized/flat_adjacency_list.hpp index 9897166..e16ec36 100644 --- a/include/gl/impl/specialized/flat_adjacency_list.hpp +++ b/include/gl/impl/specialized/flat_adjacency_list.hpp @@ -154,6 +154,21 @@ struct directed_flat_adjacency_list { // --- edge getters --- + [[nodiscard]] gl_attr_force_inline static auto incident_edges( + const impl_type& self, id_type vertex_id + ) { + return util::concat(self.in_edges(vertex_id), self.out_edges(vertex_id)); + } + + [[nodiscard]] gl_attr_force_inline static auto incident_edges( + const impl_type& self, id_type vertex_id, const auto& edge_properties_map + ) { + return util::concat( + self.in_edges(vertex_id, edge_properties_map), + self.out_edges(vertex_id, edge_properties_map) + ); + } + [[nodiscard]] static auto in_edges(const impl_type& self, id_type vertex_id) { std::vector in_edges; for (id_type src_id = initial_id; src_id < self._list.size(); ++src_id) { @@ -318,6 +333,18 @@ struct undirected_flat_adjacency_list { // --- edge getters --- + [[nodiscard]] gl_attr_force_inline static auto incident_edges( + const impl_type& self, id_type vertex_id + ) { + return self.out_edges(vertex_id); + } + + [[nodiscard]] gl_attr_force_inline static auto incident_edges( + const impl_type& self, id_type vertex_id, const auto& edge_properties_map + ) { + return self.out_edges(vertex_id, edge_properties_map); + } + [[nodiscard]] gl_attr_force_inline static auto in_edges( const impl_type& self, id_type vertex_id ) { diff --git a/include/gl/impl/specialized/flat_adjacency_matrix.hpp b/include/gl/impl/specialized/flat_adjacency_matrix.hpp index 73e4184..1ff349c 100644 --- a/include/gl/impl/specialized/flat_adjacency_matrix.hpp +++ b/include/gl/impl/specialized/flat_adjacency_matrix.hpp @@ -230,6 +230,23 @@ struct directed_flat_adjacency_matrix { gl_attr_force_inline static void remove_edge(impl_type& self, const edge_type& edge) { detail::strict_get(self._matrix, edge) = invalid_id; } + + // --- edge getters --- + + [[nodiscard]] gl_attr_force_inline static auto incident_edges( + const impl_type& self, id_type vertex_id + ) { + return util::concat(self.in_edges(vertex_id), self.out_edges(vertex_id)); + } + + [[nodiscard]] gl_attr_force_inline static auto incident_edges( + const impl_type& self, id_type vertex_id, const auto& edge_properties_map + ) { + return util::concat( + self.in_edges(vertex_id, edge_properties_map), + self.out_edges(vertex_id, edge_properties_map) + ); + } }; template AdjacencyMatrix> @@ -396,6 +413,20 @@ struct undirected_flat_adjacency_matrix { self._matrix[to_idx(edge.target()), to_idx(edge.source())] = invalid_id; } } + + // --- edge getters --- + + [[nodiscard]] gl_attr_force_inline static auto incident_edges( + const impl_type& self, id_type vertex_id + ) { + return self.out_edges(vertex_id); + } + + [[nodiscard]] gl_attr_force_inline static auto incident_edges( + const impl_type& self, id_type vertex_id, const auto& edge_properties_map + ) { + return self.out_edges(vertex_id, edge_properties_map); + } }; template AdjacencyMatrix> diff --git a/tests/include/testing/gl/io_common.hpp b/tests/include/testing/gl/io_common.hpp index 45abfff..cf0db99 100644 --- a/tests/include/testing/gl/io_common.hpp +++ b/tests/include/testing/gl/io_common.hpp @@ -13,7 +13,7 @@ void verify_graph_structure(const GraphType& actual, const GraphType& expected) // verify that the edges of the in graph are equivalent to the edges of the out graph CHECK(std::ranges::all_of(actual.vertices(), [&](const auto& v_actual) { - return std::ranges::all_of(actual.incident_edges(v_actual), [&](const auto& edge) { + return std::ranges::all_of(actual.out_edges(v_actual), [&](const auto& edge) { return expected.has_edge(edge.source(), edge.target()); }); })); @@ -35,7 +35,7 @@ void verify_vertex_properties(const GraphType& actual, const GraphType& expected template void verify_edge_properties(const GraphType& actual, const GraphType& expected) { CHECK(std::ranges::all_of(actual.vertices(), [&](const auto& v_actual) { - return std::ranges::all_of(actual.incident_edges(v_actual), [&](const auto& edge) { + return std::ranges::all_of(actual.out_edges(v_actual), [&](const auto& edge) { return edge.properties() == expected.get_edge(edge.source(), edge.target())->properties(); }); diff --git a/tests/source/gl/test_adjacency_list.cpp b/tests/source/gl/test_adjacency_list.cpp index aea8e61..fcdb1a7 100644 --- a/tests/source/gl/test_adjacency_list.cpp +++ b/tests/source/gl/test_adjacency_list.cpp @@ -195,15 +195,15 @@ TEST_CASE_TEMPLATE_DEFINE("directed adjacency list tests", SutType, directed_adj // Check the structure of the graph considering the aligned IDs CHECK_EQ(size(sut), constants::n_elements - 1uz); - const auto inc_edges_1 = sut.incident_edges(constants::v1_id); - CHECK_EQ(inc_edges_1.size(), 1uz); - CHECK_EQ(inc_edges_1.front().id(), edge5.id() - n_removed_edges); - CHECK_EQ(inc_edges_1.front().target(), constants::v2_id); + const auto out_edges_1 = sut.out_edges(constants::v1_id); + CHECK_EQ(out_edges_1.size(), 1uz); + CHECK_EQ(out_edges_1.front().id(), edge5.id() - n_removed_edges); + CHECK_EQ(out_edges_1.front().target(), constants::v2_id); - const auto inc_edges_2 = sut.incident_edges(constants::v2_id); - CHECK_EQ(inc_edges_2.size(), 1uz); - CHECK_EQ(inc_edges_2.front().id(), edge6.id() - n_removed_edges); - CHECK_EQ(inc_edges_2.front().target(), constants::v1_id); + const auto out_edges_2 = sut.out_edges(constants::v2_id); + CHECK_EQ(out_edges_2.size(), 1uz); + CHECK_EQ(out_edges_2.front().id(), edge6.id() - n_removed_edges); + CHECK_EQ(out_edges_2.front().target(), constants::v1_id); } // --- vertex getters --- @@ -290,11 +290,11 @@ TEST_CASE_TEMPLATE_DEFINE("directed adjacency list tests", SutType, directed_adj REQUIRE(new_edge.is_incident_from(constants::v1_id)); REQUIRE(new_edge.is_incident_to(constants::v2_id)); - const auto inc_edged_1 = sut.incident_edges(constants::v1_id); - CHECK_EQ(inc_edged_1.size(), 1uz); - CHECK_EQ(sut.incident_edges(constants::v2_id).size(), 0uz); + const auto out_edges_1 = sut.out_edges(constants::v1_id); + CHECK_EQ(out_edges_1.size(), 1uz); + CHECK_EQ(sut.out_edges(constants::v2_id).size(), 0uz); - const auto& new_edge_extracted = inc_edged_1[0uz]; + const auto& new_edge_extracted = out_edges_1[0uz]; CHECK_EQ(new_edge_extracted, new_edge); } @@ -307,16 +307,16 @@ TEST_CASE_TEMPLATE_DEFINE("directed adjacency list tests", SutType, directed_adj SUBCASE("remove_edge should remove the edge from the source vertex's list") { fully_connect_vertex(constants::v1_id); - auto incident_edges = sut.incident_edges(constants::v1_id); - REQUIRE_EQ(incident_edges.size(), n_inc_edged_for_fully_connected_vertex); + auto out_edges = sut.out_edges(constants::v1_id); + REQUIRE_EQ(out_edges.size(), n_inc_edged_for_fully_connected_vertex); - const auto& edge_to_remove = incident_edges[0uz]; + const auto& edge_to_remove = out_edges[0uz]; sut.remove_edge(edge_to_remove); - incident_edges = sut.incident_edges(constants::v1_id); - REQUIRE_EQ(incident_edges.size(), n_inc_edged_for_fully_connected_vertex - 1uz); + out_edges = sut.out_edges(constants::v1_id); + REQUIRE_EQ(out_edges.size(), n_inc_edged_for_fully_connected_vertex - 1uz); // validate that the incident edges list has been properly aligned - CHECK_EQ(std::ranges::find(incident_edges, edge_to_remove), incident_edges.end()); + CHECK_EQ(std::ranges::find(out_edges, edge_to_remove), out_edges.end()); } // --- edge getters --- @@ -381,6 +381,18 @@ TEST_CASE_TEMPLATE_DEFINE("directed adjacency list tests", SutType, directed_adj CHECK(sut.get_edges(constants::v2_id, constants::v2_id).empty()); } + SUBCASE("incident_edges should return edges incident with the vertex") { + const auto edge1 = add_edge(constants::v1_id, constants::v2_id); + const auto edge2 = add_edge(constants::v3_id, constants::v1_id); + + const auto inc_edges = + sut.incident_edges(constants::v1_id) | std::ranges::to(); + + REQUIRE_EQ(inc_edges.size(), 2uz); + CHECK(std::ranges::contains(inc_edges, edge1)); + CHECK(std::ranges::contains(inc_edges, edge2)); + } + SUBCASE("in_edges should return edges where the vertex is the target") { const auto edge1 = add_edge(constants::v2_id, constants::v1_id); const auto edge2 = add_edge(constants::v3_id, constants::v1_id); @@ -408,7 +420,7 @@ TEST_CASE_TEMPLATE_DEFINE("directed adjacency list tests", SutType, directed_adj SUBCASE("at should return the incident edges of a vertex") { init_complete_graph(); for (const auto vertex_id : std::views::iota(constants::v1_id, constants::n_elements)) - CHECK(std::ranges::equal(sut.at(vertex_id), sut.incident_edges(vertex_id))); + CHECK(std::ranges::equal(sut.at(vertex_id), sut.out_edges(vertex_id))); } } @@ -706,7 +718,9 @@ TEST_CASE_TEMPLATE_DEFINE("undirected adjacency list tests", SutType, undirected )); } - SUBCASE("in_edges and out_edges should return the same edges for undirected graphs") { + // TODO: incident_edges + + SUBCASE("incident/in/out_edges should return the same edge sets for undirected graphs") { add_edge(constants::v1_id, constants::v2_id); add_edge(constants::v1_id, constants::v3_id); diff --git a/tests/source/gl/test_adjacency_matrix.cpp b/tests/source/gl/test_adjacency_matrix.cpp index 5689850..8b21401 100644 --- a/tests/source/gl/test_adjacency_matrix.cpp +++ b/tests/source/gl/test_adjacency_matrix.cpp @@ -152,7 +152,7 @@ TEST_CASE_TEMPLATE_INSTANTIATE( namespace { -constexpr gl::size_type n_incident_edges_for_fully_connected_vertex = constants::n_elements - 1uz; +constexpr gl::size_type n_out_edges_for_fully_connected_vertex = constants::n_elements - 1uz; } // namespace @@ -184,19 +184,19 @@ struct test_directed_adjacency_matrix : public test_adjacency_matrix { if (no_loops) REQUIRE(std::ranges::all_of(get(sut), [&](const auto& matrix_row) { return std::ranges::count_if(matrix_row, is_valid_id) - == n_incident_edges_for_fully_connected_vertex; + == n_out_edges_for_fully_connected_vertex; })); else REQUIRE(std::ranges::all_of(get(sut), [&](const auto& matrix_row) { return std::ranges::count_if(matrix_row, is_valid_id) - == n_incident_edges_for_fully_connected_vertex + 1uz; + == n_out_edges_for_fully_connected_vertex + 1uz; })); } sut_type sut{constants::n_elements}; static constexpr gl::size_type n_unique_edges_in_full_graph = - n_incident_edges_for_fully_connected_vertex * constants::n_elements; + n_out_edges_for_fully_connected_vertex * constants::n_elements; }; TEST_CASE_TEMPLATE_DEFINE("directed adjacency matrix tests", SutType, directed_adj_matrix_template) { @@ -241,15 +241,15 @@ TEST_CASE_TEMPLATE_DEFINE("directed adjacency matrix tests", SutType, directed_a // Check the structure of the graph considering the aligned IDs CHECK_EQ(size(sut), constants::n_elements - 1uz); - auto adj_edges_1 = sut.incident_edges(constants::v1_id); - CHECK_EQ(gl::util::range_size(adj_edges_1), 1uz); - CHECK_EQ((*std::ranges::begin(adj_edges_1)).id(), edge5.id() - n_removed_edges); - CHECK_EQ((*std::ranges::begin(adj_edges_1)).target(), constants::v2_id); + auto out_edges_1 = sut.out_edges(constants::v1_id); + CHECK_EQ(gl::util::range_size(out_edges_1), 1uz); + CHECK_EQ((*std::ranges::begin(out_edges_1)).id(), edge5.id() - n_removed_edges); + CHECK_EQ((*std::ranges::begin(out_edges_1)).target(), constants::v2_id); - auto adj_edges_2 = sut.incident_edges(constants::v2_id); - CHECK_EQ(gl::util::range_size(adj_edges_2), 1uz); - CHECK_EQ((*std::ranges::begin(adj_edges_2)).id(), edge6.id() - n_removed_edges); - CHECK_EQ((*std::ranges::begin(adj_edges_2)).target(), constants::v1_id); + auto out_edges_2 = sut.out_edges(constants::v2_id); + CHECK_EQ(gl::util::range_size(out_edges_2), 1uz); + CHECK_EQ((*std::ranges::begin(out_edges_2)).id(), edge6.id() - n_removed_edges); + CHECK_EQ((*std::ranges::begin(out_edges_2)).target(), constants::v1_id); } // --- vertex getters --- @@ -262,15 +262,13 @@ TEST_CASE_TEMPLATE_DEFINE("directed adjacency matrix tests", SutType, directed_a CHECK(std::ranges::all_of( constants::vertex_id_view, - [](const auto deg) { return deg == 2uz * n_incident_edges_for_fully_connected_vertex; }, + [](const auto deg) { return deg == 2uz * n_out_edges_for_fully_connected_vertex; }, deg_proj )); add_edge(constants::v1_id, constants::v1_id); - CHECK_EQ( - deg_proj(constants::v1_id), 2uz * (n_incident_edges_for_fully_connected_vertex + 1uz) - ); + CHECK_EQ(deg_proj(constants::v1_id), 2uz * (n_out_edges_for_fully_connected_vertex + 1uz)); } SUBCASE("in/out_degree should return the number of edges incident {to/from} the given vertex") { @@ -290,13 +288,13 @@ TEST_CASE_TEMPLATE_DEFINE("directed adjacency matrix tests", SutType, directed_a CHECK(std::ranges::all_of( constants::vertex_id_view, - [](const auto deg) { return deg == n_incident_edges_for_fully_connected_vertex; }, + [](const auto deg) { return deg == n_out_edges_for_fully_connected_vertex; }, deg_proj )); add_edge(constants::v1_id, constants::v1_id); - CHECK_EQ(deg_proj(constants::v1_id), n_incident_edges_for_fully_connected_vertex + 1uz); + CHECK_EQ(deg_proj(constants::v1_id), n_out_edges_for_fully_connected_vertex + 1uz); } SUBCASE("degree_map should return a map of the numbers of edges incident with the " @@ -339,11 +337,11 @@ TEST_CASE_TEMPLATE_DEFINE("directed adjacency matrix tests", SutType, directed_a REQUIRE(new_edge.is_incident_from(constants::v1_id)); REQUIRE(new_edge.is_incident_to(constants::v2_id)); - auto incident_edges_1 = sut.incident_edges(constants::v1_id); - CHECK_EQ(gl::util::range_size(incident_edges_1), 1uz); - CHECK_EQ(gl::util::range_size(sut.incident_edges(constants::v2_id)), 0uz); + auto out_edges_1 = sut.out_edges(constants::v1_id); + CHECK_EQ(gl::util::range_size(out_edges_1), 1uz); + CHECK_EQ(gl::util::range_size(sut.out_edges(constants::v2_id)), 0uz); - const auto& new_edge_extracted = *std::ranges::begin(incident_edges_1); + const auto& new_edge_extracted = *std::ranges::begin(out_edges_1); CHECK_EQ(new_edge_extracted, new_edge); } @@ -358,20 +356,16 @@ TEST_CASE_TEMPLATE_DEFINE("directed adjacency matrix tests", SutType, directed_a SUBCASE("remove_edge should remove the edge from the source vertex's list") { fully_connect_vertex(constants::v1_id); - auto incident_edges = sut.incident_edges(constants::v1_id); - REQUIRE_EQ( - gl::util::range_size(incident_edges), n_incident_edges_for_fully_connected_vertex - ); + auto out_edges = sut.out_edges(constants::v1_id); + REQUIRE_EQ(gl::util::range_size(out_edges), n_out_edges_for_fully_connected_vertex); - const auto edge_to_remove = *std::ranges::begin(incident_edges); + const auto edge_to_remove = *std::ranges::begin(out_edges); sut.remove_edge(edge_to_remove); // validate that the incident edges list has been properly aligned - incident_edges = sut.incident_edges(constants::v1_id); - REQUIRE_EQ( - gl::util::range_size(incident_edges), n_incident_edges_for_fully_connected_vertex - 1uz - ); - CHECK_EQ(std::ranges::find(incident_edges, edge_to_remove), incident_edges.end()); + out_edges = sut.out_edges(constants::v1_id); + REQUIRE_EQ(gl::util::range_size(out_edges), n_out_edges_for_fully_connected_vertex - 1uz); + CHECK_EQ(std::ranges::find(out_edges, edge_to_remove), out_edges.end()); } // --- edge getters --- @@ -413,14 +407,16 @@ TEST_CASE_TEMPLATE_DEFINE("directed adjacency matrix tests", SutType, directed_a CHECK_FALSE(sut.get_edge(constants::v2_id, constants::v2_id)); } - SUBCASE("incident_edges should return a filtered view of edges incident with the given vertex" - ) { - const auto vertex_id = constants::v1_id; - const auto edge = add_edge(vertex_id, constants::v2_id); - auto incident_edges = sut.incident_edges(vertex_id); + SUBCASE("incident_edges should return edges incident with the vertex") { + const auto edge1 = add_edge(constants::v1_id, constants::v2_id); + const auto edge2 = add_edge(constants::v3_id, constants::v1_id); - REQUIRE_EQ(gl::util::range_size(incident_edges), 1uz); - CHECK_EQ(*std::ranges::begin(incident_edges), edge); + const auto inc_edges = + sut.incident_edges(constants::v1_id) | std::ranges::to(); + + REQUIRE_EQ(inc_edges.size(), 2uz); + CHECK(std::ranges::contains(inc_edges, edge1)); + CHECK(std::ranges::contains(inc_edges, edge2)); } SUBCASE("in_edges should return edges where the vertex is the target") { @@ -499,19 +495,19 @@ struct test_undirected_adjacency_matrix : public test_adjacency_matrix { if (no_loops) REQUIRE(std::ranges::all_of(get(sut), [&](const auto& matrix_row) { return std::ranges::count_if(matrix_row, is_valid_id) - == n_incident_edges_for_fully_connected_vertex; + == n_out_edges_for_fully_connected_vertex; })); else REQUIRE(std::ranges::all_of(get(sut), [&](const auto& matrix_row) { return std::ranges::count_if(matrix_row, is_valid_id) - == n_incident_edges_for_fully_connected_vertex + 1uz; + == n_out_edges_for_fully_connected_vertex + 1uz; })); } sut_type sut{constants::n_elements}; static constexpr gl::size_type n_unique_edges_in_full_graph = - (n_incident_edges_for_fully_connected_vertex * constants::n_elements) / 2; + (n_out_edges_for_fully_connected_vertex * constants::n_elements) / 2; }; TEST_CASE_TEMPLATE_DEFINE( @@ -590,7 +586,7 @@ TEST_CASE_TEMPLATE_DEFINE( CHECK(std::ranges::all_of( constants::vertex_id_view, - [](const auto deg) { return deg == n_incident_edges_for_fully_connected_vertex; }, + [](const auto deg) { return deg == n_out_edges_for_fully_connected_vertex; }, deg_proj )); @@ -598,7 +594,7 @@ TEST_CASE_TEMPLATE_DEFINE( CHECK_EQ( deg_proj(constants::v1_id), - n_incident_edges_for_fully_connected_vertex + 2uz // loops counted twice + n_out_edges_for_fully_connected_vertex + 2uz // loops counted twice ); } @@ -672,7 +668,7 @@ TEST_CASE_TEMPLATE_DEFINE( auto incident_edges_first = sut.incident_edges(constants::v1_id); REQUIRE_EQ( - gl::util::range_size(incident_edges_first), n_incident_edges_for_fully_connected_vertex + gl::util::range_size(incident_edges_first), n_out_edges_for_fully_connected_vertex ); const auto edge_to_remove = *std::ranges::begin(incident_edges_first); @@ -685,8 +681,7 @@ TEST_CASE_TEMPLATE_DEFINE( // validate that the first incident edges list has been properly aligned incident_edges_first = sut.incident_edges(constants::v1_id); REQUIRE_EQ( - gl::util::range_size(incident_edges_first), - n_incident_edges_for_fully_connected_vertex - 1uz + gl::util::range_size(incident_edges_first), n_out_edges_for_fully_connected_vertex - 1uz ); CHECK_EQ( std::ranges::find(incident_edges_first, edge_to_remove), incident_edges_first.end() diff --git a/tests/source/gl/test_graph.cpp b/tests/source/gl/test_graph.cpp index a7e000e..8f199a2 100644 --- a/tests/source/gl/test_graph.cpp +++ b/tests/source/gl/test_graph.cpp @@ -62,7 +62,7 @@ struct test_graph { graph.add_edge(first, second); const gl::size_type n_unique_edges_in_full_graph = - n_incident_edges_for_fully_connected_vertex(graph) * graph.order(); + n_out_edges_for_fully_connected_vertex(graph) * graph.order(); REQUIRE_EQ(graph.size(), n_unique_edges_in_full_graph); validate_full_graph_edges(graph); @@ -78,7 +78,7 @@ struct test_graph { graph.add_edge(first, second); const gl::size_type n_unique_edges_in_full_graph = - (n_incident_edges_for_fully_connected_vertex(graph) * graph.order()) / 2; + (n_out_edges_for_fully_connected_vertex(graph) * graph.order()) / 2; REQUIRE_EQ(graph.size(), n_unique_edges_in_full_graph); validate_full_graph_edges(graph); @@ -90,10 +90,10 @@ struct test_graph { void validate_full_graph_edges(const GraphType& graph) { REQUIRE(std::ranges::all_of( graph.vertex_ids(), - [&graph, expected_n_edges = n_incident_edges_for_fully_connected_vertex(graph)]( + [&graph, expected_n_edges = n_out_edges_for_fully_connected_vertex(graph)]( const gl::default_id_type vertex_id ) { - return static_cast(gl::util::range_size(graph.incident_edges(vertex_id))) + return static_cast(gl::util::range_size(graph.out_edges(vertex_id))) == expected_n_edges; } )); @@ -102,7 +102,7 @@ struct test_graph { // clang-format on template GraphType> - gl::size_type n_incident_edges_for_fully_connected_vertex(const GraphType& graph) { + gl::size_type n_out_edges_for_fully_connected_vertex(const GraphType& graph) { return graph.order() - 1uz; } @@ -148,9 +148,7 @@ TEST_CASE_TEMPLATE_DEFINE("common graph structure tests", TraitsType, common_gra CHECK(std::ranges::all_of( constants::vertex_id_view, - [&sut](const gl::default_id_type vertex_id) { - return sut.incident_edges(vertex_id).empty(); - } + [&sut](const gl::default_id_type vertex_id) { return sut.out_edges(vertex_id).empty(); } )); } @@ -164,7 +162,7 @@ TEST_CASE_TEMPLATE_DEFINE("common graph structure tests", TraitsType, common_gra const auto vertex = sut.add_vertex(); CHECK_EQ(vertex.id(), v_id); CHECK_EQ(sut.order(), v_id + 1u); - CHECK(sut.incident_edges(v_id).empty()); + CHECK(sut.out_edges(v_id).empty()); } CHECK_EQ(sut.order(), target_n_vertices); @@ -225,8 +223,7 @@ TEST_CASE_TEMPLATE_DEFINE("common graph structure tests", TraitsType, common_gra sut.remove_vertex(constants::v1_id); constexpr gl::size_type n_vertices_after_remove = constants::n_elements - 1uz; - const auto expected_n_incident_edges = - fixture.n_incident_edges_for_fully_connected_vertex(sut); + const auto expected_n_out_edges = fixture.n_out_edges_for_fully_connected_vertex(sut); const auto vertex_id_view = sut.vertex_ids(); @@ -235,9 +232,9 @@ TEST_CASE_TEMPLATE_DEFINE("common graph structure tests", TraitsType, common_gra )); REQUIRE(std::ranges::all_of( vertex_id_view, - [&sut, expected_n_incident_edges](const gl::default_id_type vertex_id) { - return static_cast(gl::util::range_size(sut.incident_edges(vertex_id))) - == expected_n_incident_edges; + [&sut, expected_n_out_edges](const gl::default_id_type vertex_id) { + return static_cast(gl::util::range_size(sut.out_edges(vertex_id))) + == expected_n_out_edges; } )); @@ -257,8 +254,7 @@ TEST_CASE_TEMPLATE_DEFINE("common graph structure tests", TraitsType, common_gra sut.remove_vertex(constants::v1_id); constexpr gl::size_type n_vertices_after_remove = constants::n_elements - 1uz; - const auto expected_n_incident_edges = - fixture.n_incident_edges_for_fully_connected_vertex(sut); + const auto expected_n_out_edges = fixture.n_out_edges_for_fully_connected_vertex(sut); const auto vertex_id_view = sut.vertex_ids(); @@ -267,9 +263,9 @@ TEST_CASE_TEMPLATE_DEFINE("common graph structure tests", TraitsType, common_gra )); REQUIRE(std::ranges::all_of( vertex_id_view, - [&sut, expected_n_incident_edges](const gl::default_id_type vertex_id) { - return static_cast(gl::util::range_size(sut.incident_edges(vertex_id))) - == expected_n_incident_edges; + [&sut, expected_n_out_edges](const gl::default_id_type vertex_id) { + return static_cast(gl::util::range_size(sut.out_edges(vertex_id))) + == expected_n_out_edges; } )); @@ -290,14 +286,10 @@ TEST_CASE_TEMPLATE_DEFINE("common graph structure tests", TraitsType, common_gra constexpr auto expected_n_vertices = n_vertices - 2uz; REQUIRE_EQ(sut.order(), expected_n_vertices); - constexpr auto expected_n_incident_edges = expected_n_vertices - 1uz; - CHECK(std::ranges::all_of( - sut.vertices(), - [&sut, expected_n_incident_edges](const auto& vertex) { - return gl::util::range_size(sut.incident_edges(vertex)) - == expected_n_incident_edges; - } - )); + constexpr auto expected_n_out_edges = expected_n_vertices - 1uz; + CHECK(std::ranges::all_of(sut.vertices(), [&sut, expected_n_out_edges](const auto& vertex) { + return gl::util::range_size(sut.out_edges(vertex)) == expected_n_out_edges; + })); } SUBCASE("remove_vetices_from(vertices) should properly remove elements at given indices " @@ -315,14 +307,10 @@ TEST_CASE_TEMPLATE_DEFINE("common graph structure tests", TraitsType, common_gra constexpr auto expected_n_vertices = n_vertices - 2uz; REQUIRE_EQ(sut.order(), expected_n_vertices); - constexpr auto expected_n_incident_edges = expected_n_vertices - 1uz; - CHECK(std::ranges::all_of( - sut.vertices(), - [&sut, expected_n_incident_edges](const auto& vertex) { - return gl::util::range_size(sut.incident_edges(vertex)) - == expected_n_incident_edges; - } - )); + constexpr auto expected_n_out_edges = expected_n_vertices - 1uz; + CHECK(std::ranges::all_of(sut.vertices(), [&sut, expected_n_out_edges](const auto& vertex) { + return gl::util::range_size(sut.out_edges(vertex)) == expected_n_out_edges; + })); } @@ -393,19 +381,19 @@ TEST_CASE_TEMPLATE_DEFINE("common graph structure tests", TraitsType, common_gra REQUIRE_EQ(sut.size(), 1uz); - auto incident_edges_1 = sut.incident_edges(constants::v1_id); - CHECK_EQ(gl::util::range_size(incident_edges_1), 1uz); - const auto new_edge_extracted_1 = *std::ranges::begin(incident_edges_1); + auto out_edges_1 = sut.out_edges(constants::v1_id); + CHECK_EQ(gl::util::range_size(out_edges_1), 1uz); + const auto new_edge_extracted_1 = *std::ranges::begin(out_edges_1); CHECK_EQ(new_edge_extracted_1, new_edge); - auto incident_edges_2 = sut.incident_edges(constants::v2_id); + auto out_edges_2 = sut.out_edges(constants::v2_id); if constexpr (gl::traits::c_undirected_edge) { - CHECK_EQ(gl::util::range_size(incident_edges_2), 1uz); - const auto new_edge_extracted_2 = *std::ranges::begin(incident_edges_2); + CHECK_EQ(gl::util::range_size(out_edges_2), 1uz); + const auto new_edge_extracted_2 = *std::ranges::begin(out_edges_2); CHECK_EQ(new_edge_extracted_2, new_edge); } else { - CHECK_EQ(gl::util::range_size(incident_edges_2), 0uz); + CHECK_EQ(gl::util::range_size(out_edges_2), 0uz); } } @@ -421,19 +409,19 @@ TEST_CASE_TEMPLATE_DEFINE("common graph structure tests", TraitsType, common_gra REQUIRE_EQ(sut.size(), 1uz); - auto incident_edges_1 = sut.incident_edges(constants::v1_id); - CHECK_EQ(gl::util::range_size(incident_edges_1), 1uz); - const auto new_edge_extracted_1 = *std::ranges::begin(incident_edges_1); + auto out_edges_1 = sut.out_edges(constants::v1_id); + CHECK_EQ(gl::util::range_size(out_edges_1), 1uz); + const auto new_edge_extracted_1 = *std::ranges::begin(out_edges_1); CHECK_EQ(new_edge_extracted_1, new_edge); - auto incident_edges_2 = sut.incident_edges(constants::v2_id); + auto out_edges_2 = sut.out_edges(constants::v2_id); if constexpr (gl::traits::c_undirected_edge) { - CHECK_EQ(gl::util::range_size(incident_edges_2), 1uz); - const auto new_edge_extracted_2 = *std::ranges::begin(incident_edges_2); + CHECK_EQ(gl::util::range_size(out_edges_2), 1uz); + const auto new_edge_extracted_2 = *std::ranges::begin(out_edges_2); CHECK_EQ(new_edge_extracted_2, new_edge); } else { - CHECK_EQ(gl::util::range_size(incident_edges_2), 0uz); + CHECK_EQ(gl::util::range_size(out_edges_2), 0uz); } } @@ -508,21 +496,21 @@ TEST_CASE_TEMPLATE_DEFINE("common graph structure tests", TraitsType, common_gra REQUIRE_EQ(sut.size(), 1uz); - auto incident_edges_1 = sut.incident_edges(constants::v1_id); - auto incident_edges_2 = sut.incident_edges(constants::v2_id); + auto out_edges_1 = sut.out_edges(constants::v1_id); + auto out_edges_2 = sut.out_edges(constants::v2_id); - REQUIRE_EQ(gl::util::range_size(incident_edges_1), 1uz); + REQUIRE_EQ(gl::util::range_size(out_edges_1), 1uz); if constexpr (gl::traits::c_undirected_edge) - REQUIRE_EQ(gl::util::range_size(incident_edges_2), 1uz); + REQUIRE_EQ(gl::util::range_size(out_edges_2), 1uz); sut.remove_edge(added_edge); CHECK_EQ(sut.size(), 0uz); - incident_edges_1 = sut.incident_edges(constants::v1_id); - incident_edges_2 = sut.incident_edges(constants::v2_id); + out_edges_1 = sut.out_edges(constants::v1_id); + out_edges_2 = sut.out_edges(constants::v2_id); - CHECK_EQ(gl::util::range_size(incident_edges_1), 0uz); - CHECK_EQ(gl::util::range_size(incident_edges_2), 0uz); + CHECK_EQ(gl::util::range_size(out_edges_1), 0uz); + CHECK_EQ(gl::util::range_size(out_edges_2), 0uz); } SUBCASE("remove_edges should properly erase all given edges") { @@ -585,19 +573,19 @@ TEST_CASE_TEMPLATE_DEFINE("common graph structure tests", TraitsType, common_gra REQUIRE_EQ(sut.size(), 1uz); - auto incident_edges_1 = sut.incident_edges(constants::v1_id); - CHECK_EQ(gl::util::range_size(incident_edges_1), 1uz); - const auto new_edge_extracted_1 = *std::ranges::begin(incident_edges_1); + auto out_edges_1 = sut.out_edges(constants::v1_id); + CHECK_EQ(gl::util::range_size(out_edges_1), 1uz); + const auto new_edge_extracted_1 = *std::ranges::begin(out_edges_1); CHECK_EQ(new_edge_extracted_1, new_edge); - auto incident_edges_2 = sut.incident_edges(constants::v2_id); + auto out_edges_2 = sut.out_edges(constants::v2_id); if constexpr (gl::traits::c_undirected_edge) { - CHECK_EQ(gl::util::range_size(incident_edges_2), 1uz); - const auto new_edge_extracted_2 = *std::ranges::begin(incident_edges_2); + CHECK_EQ(gl::util::range_size(out_edges_2), 1uz); + const auto new_edge_extracted_2 = *std::ranges::begin(out_edges_2); CHECK_EQ(new_edge_extracted_2, new_edge); } else { - CHECK_EQ(gl::util::range_size(incident_edges_2), 0uz); + CHECK_EQ(gl::util::range_size(out_edges_2), 0uz); } } @@ -620,19 +608,19 @@ TEST_CASE_TEMPLATE_DEFINE("common graph structure tests", TraitsType, common_gra REQUIRE_EQ(sut.size(), 1uz); - auto incident_edges_1 = sut.incident_edges(constants::v1_id); - CHECK_EQ(gl::util::range_size(incident_edges_1), 1uz); - const auto new_edge_extracted_1 = *std::ranges::begin(incident_edges_1); + auto out_edges_1 = sut.out_edges(constants::v1_id); + CHECK_EQ(gl::util::range_size(out_edges_1), 1uz); + const auto new_edge_extracted_1 = *std::ranges::begin(out_edges_1); CHECK_EQ(new_edge_extracted_1, new_edge); - auto incident_edges_2 = sut.incident_edges(constants::v2_id); + auto out_edges_2 = sut.out_edges(constants::v2_id); if constexpr (gl::traits::c_undirected_edge) { - CHECK_EQ(gl::util::range_size(incident_edges_2), 1uz); - const auto new_edge_extracted_2 = *std::ranges::begin(incident_edges_2); + CHECK_EQ(gl::util::range_size(out_edges_2), 1uz); + const auto new_edge_extracted_2 = *std::ranges::begin(out_edges_2); CHECK_EQ(new_edge_extracted_2, new_edge); } else { - CHECK_EQ(gl::util::range_size(incident_edges_2), 0uz); + CHECK_EQ(gl::util::range_size(out_edges_2), 0uz); } } @@ -641,21 +629,21 @@ TEST_CASE_TEMPLATE_DEFINE("common graph structure tests", TraitsType, common_gra REQUIRE_EQ(sut.size(), 1uz); - auto incident_edges_1 = sut.incident_edges(constants::v1_id); - auto incident_edges_2 = sut.incident_edges(constants::v2_id); + auto out_edges_1 = sut.out_edges(constants::v1_id); + auto out_edges_2 = sut.out_edges(constants::v2_id); - REQUIRE_EQ(gl::util::range_size(incident_edges_1), 1uz); + REQUIRE_EQ(gl::util::range_size(out_edges_1), 1uz); if constexpr (gl::traits::c_undirected_edge) - REQUIRE_EQ(gl::util::range_size(incident_edges_2), 1uz); + REQUIRE_EQ(gl::util::range_size(out_edges_2), 1uz); sut.remove_edge(added_edge); CHECK_EQ(sut.size(), 0uz); - incident_edges_1 = sut.incident_edges(constants::v1_id); - incident_edges_2 = sut.incident_edges(constants::v2_id); + out_edges_1 = sut.out_edges(constants::v1_id); + out_edges_2 = sut.out_edges(constants::v2_id); - CHECK_EQ(gl::util::range_size(incident_edges_1), 0uz); - CHECK_EQ(gl::util::range_size(incident_edges_2), 0uz); + CHECK_EQ(gl::util::range_size(out_edges_1), 0uz); + CHECK_EQ(gl::util::range_size(out_edges_2), 0uz); } SUBCASE("remove_edges should properly erase all given edges") { diff --git a/tests/source/gl/test_graph_file_io.cpp b/tests/source/gl/test_graph_file_io.cpp index b30230b..36a2876 100644 --- a/tests/source/gl/test_graph_file_io.cpp +++ b/tests/source/gl/test_graph_file_io.cpp @@ -46,7 +46,7 @@ struct test_graph_file_io { std::size_t v_idx = 0, e_idx = 0; for (const auto& vertex : sut_out.vertices()) { vertex.properties() = std::format("vertex_{}", v_idx++); - for (const auto& edge : sut_out.incident_edges(vertex)) + for (const auto& edge : sut_out.out_edges(vertex)) edge.properties() = std::format("edge_{}", e_idx++); } } diff --git a/tests/source/gl/test_graph_io.cpp b/tests/source/gl/test_graph_io.cpp index 94dc822..78ca426 100644 --- a/tests/source/gl/test_graph_io.cpp +++ b/tests/source/gl/test_graph_io.cpp @@ -28,7 +28,7 @@ struct test_directed_graph_io { std::size_t v_idx = 0, e_idx = 0; for (const auto& vertex : sut_out.vertices()) { vertex.properties() = std::format("vertex_{}", v_idx++); - for (const auto& edge : sut_out.incident_edges(vertex)) + for (const auto& edge : sut_out.out_edges(vertex)) edge.properties() = std::format("edge_{}", e_idx++); } } @@ -137,7 +137,7 @@ struct test_undirected_graph_io { std::size_t v_idx = 0, e_idx = 0; for (const auto& vertex : sut_out.vertices()) { vertex.properties() = std::format("vertex_{}", v_idx++); - for (const auto& edge : sut_out.incident_edges(vertex)) + for (const auto& edge : sut_out.out_edges(vertex)) if (edge.source() == vertex.id()) edge.properties() = std::format("edge_{}", e_idx++); } diff --git a/tests/source/gl/test_graph_topology_builders.cpp b/tests/source/gl/test_graph_topology_builders.cpp index 7354c66..213c821 100644 --- a/tests/source/gl/test_graph_topology_builders.cpp +++ b/tests/source/gl/test_graph_topology_builders.cpp @@ -133,7 +133,7 @@ template if (target_ids.first >= graph.order()) // no need to check second as second = first + 1 - return gl::util::range_size(graph.incident_edges(source)) == 0uz; + return gl::util::range_size(graph.out_edges(source)) == 0uz; const auto target_1 = graph.get_vertex(target_ids.first); const auto target_2 = graph.get_vertex(target_ids.second); @@ -154,10 +154,10 @@ template if (target_ids.first >= graph.order()) { // no need to check second as second = first + 1 - auto incident_edges = graph.incident_edges(source_id); + auto out_edges = graph.out_edges(source_id); - return gl::util::range_size(incident_edges) == 1uz - and (*std::ranges::begin(incident_edges)).other(source_id) == parent_id; + return gl::util::range_size(out_edges) == 1uz + and (*std::ranges::begin(out_edges)).other(source_id) == parent_id; } const auto target_1 = target_ids.first; From ee36c8a14b3e9ced5a8266429494e2b98510a74f Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Fri, 10 Apr 2026 22:18:41 +0200 Subject: [PATCH 5/9] incident_edges tests for adj models --- tests/source/gl/test_adjacency_list.cpp | 15 ++++++--------- tests/source/gl/test_adjacency_matrix.cpp | 17 ++++++++--------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/tests/source/gl/test_adjacency_list.cpp b/tests/source/gl/test_adjacency_list.cpp index fcdb1a7..c70722d 100644 --- a/tests/source/gl/test_adjacency_list.cpp +++ b/tests/source/gl/test_adjacency_list.cpp @@ -718,17 +718,14 @@ TEST_CASE_TEMPLATE_DEFINE("undirected adjacency list tests", SutType, undirected )); } - // TODO: incident_edges - SUBCASE("incident/in/out_edges should return the same edge sets for undirected graphs") { - add_edge(constants::v1_id, constants::v2_id); - add_edge(constants::v1_id, constants::v3_id); - - const auto in_edges = sut.in_edges(constants::v1_id) | std::ranges::to(); - const auto out_edges = sut.out_edges(constants::v1_id) | std::ranges::to(); + auto edge1 = add_edge(constants::v1_id, constants::v2_id); + auto edge2 = add_edge(constants::v3_id, constants::v1_id); + std::vector expected_edges = {edge1, edge2}; - CHECK(std::ranges::equal(in_edges, sut.incident_edges(constants::v1_id))); - CHECK(std::ranges::equal(out_edges, sut.incident_edges(constants::v1_id))); + CHECK(std::ranges::equal(sut.incident_edges(constants::v1_id), expected_edges)); + CHECK(std::ranges::equal(sut.in_edges(constants::v1_id), expected_edges)); + CHECK(std::ranges::equal(sut.out_edges(constants::v1_id), expected_edges)); } // --- access operators --- diff --git a/tests/source/gl/test_adjacency_matrix.cpp b/tests/source/gl/test_adjacency_matrix.cpp index 8b21401..13129ae 100644 --- a/tests/source/gl/test_adjacency_matrix.cpp +++ b/tests/source/gl/test_adjacency_matrix.cpp @@ -746,15 +746,14 @@ TEST_CASE_TEMPLATE_DEFINE( CHECK_EQ(*std::ranges::begin(incident_edges), edge); } - SUBCASE("in_edges and out_edges should return the same edges for undirected graphs") { - add_edge(constants::v1_id, constants::v2_id); - add_edge(constants::v1_id, constants::v3_id); - - const auto in_edges = sut.in_edges(constants::v1_id) | std::ranges::to(); - const auto out_edges = sut.out_edges(constants::v1_id) | std::ranges::to(); - - CHECK(std::ranges::equal(in_edges, sut.incident_edges(constants::v1_id))); - CHECK(std::ranges::equal(out_edges, sut.incident_edges(constants::v1_id))); + SUBCASE("incident/in/out_edges should return the same edge sets for undirected graphs") { + auto edge1 = add_edge(constants::v1_id, constants::v2_id); + auto edge2 = add_edge(constants::v3_id, constants::v1_id); + std::vector expected_edges = {edge1, edge2}; + + CHECK(std::ranges::equal(sut.incident_edges(constants::v1_id), expected_edges)); + CHECK(std::ranges::equal(sut.in_edges(constants::v1_id), expected_edges)); + CHECK(std::ranges::equal(sut.out_edges(constants::v1_id), expected_edges)); } // --- access operators --- From 59d480d38c56ec8d18b20e350a9cb7b9a9f77bfd Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Fri, 10 Apr 2026 22:21:29 +0200 Subject: [PATCH 6/9] are_incident -> are_adjacent for (v,v) and (e,e) --- include/gl/graph.hpp | 11 +++---- tests/source/gl/test_graph.cpp | 59 +++++++++++++++++----------------- 2 files changed, 33 insertions(+), 37 deletions(-) diff --git a/include/gl/graph.hpp b/include/gl/graph.hpp index 924d4a4..d220987 100644 --- a/include/gl/graph.hpp +++ b/include/gl/graph.hpp @@ -524,8 +524,7 @@ class graph final { // --- adjacency and incidence methods --- - // TODO: rename to are_adjacent - [[nodiscard]] bool are_incident(const id_type source_id, const id_type target_id) const { + [[nodiscard]] bool are_adjacent(const id_type source_id, const id_type target_id) const { this->_verify_vertex_id(source_id); if (source_id == target_id) return true; @@ -538,15 +537,13 @@ class graph final { return this->has_edge(source_id, target_id); } - // TODO: rename to are_adjacent - [[nodiscard]] gl_attr_force_inline bool are_incident( + [[nodiscard]] gl_attr_force_inline bool are_adjacent( const vertex_type& source, const vertex_type& target ) const { - return this->are_incident(source.id(), target.id()); + return this->are_adjacent(source.id(), target.id()); } - // TODO: rename to are_adjacent - [[nodiscard]] bool are_incident(const edge_type& edge_1, const edge_type& edge_2) const { + [[nodiscard]] bool are_adjacent(const edge_type& edge_1, const edge_type& edge_2) const { this->_verify_edge(edge_1); this->_verify_edge(edge_2); return edge_1.is_incident_with(edge_2.source()) or edge_1.is_incident_with(edge_2.target()); diff --git a/tests/source/gl/test_graph.cpp b/tests/source/gl/test_graph.cpp index 8f199a2..9b11e81 100644 --- a/tests/source/gl/test_graph.cpp +++ b/tests/source/gl/test_graph.cpp @@ -850,91 +850,90 @@ TEST_CASE_TEMPLATE_DEFINE("common graph structure tests", TraitsType, common_gra const auto v2 = sut.get_vertex(constants::v2_id); const auto v3 = sut.get_vertex(constants::v3_id); - SUBCASE("are_incident(vertex_id, vertex_id) should throw for out of range vertex ids") { + SUBCASE("are_adjacent(vertex_id, vertex_id) should throw for out of range vertex ids") { CHECK_THROWS_AS( - discard_result(sut.are_incident(constants::out_of_rng_idx, constants::v2_id)), + discard_result(sut.are_adjacent(constants::out_of_rng_idx, constants::v2_id)), std::out_of_range ); CHECK_THROWS_AS( - discard_result(sut.are_incident(constants::v1_id, constants::out_of_rng_idx)), + discard_result(sut.are_adjacent(constants::v1_id, constants::out_of_rng_idx)), std::out_of_range ); } - SUBCASE("are_incident(vertex_id, vertex_id) should return true the ids are the same and " + SUBCASE("are_adjacent(vertex_id, vertex_id) should return true the ids are the same and " "valid") { CHECK(std::ranges::all_of(constants::vertex_id_view, [&sut](const auto vertex_id) { - return sut.are_incident(vertex_id, vertex_id); + return sut.are_adjacent(vertex_id, vertex_id); })); } - SUBCASE("are_incident(vertex_id, vertex_id) should return true if there is an edge " + SUBCASE("are_adjacent(vertex_id, vertex_id) should return true if there is an edge " "connecting the given vertices") { sut.add_edge(v1, v2); - CHECK(sut.are_incident(constants::v1_id, constants::v2_id)); - CHECK(sut.are_incident(constants::v2_id, constants::v1_id)); + CHECK(sut.are_adjacent(constants::v1_id, constants::v2_id)); + CHECK(sut.are_adjacent(constants::v2_id, constants::v1_id)); - CHECK_FALSE(sut.are_incident(constants::v1_id, constants::v3_id)); - CHECK_FALSE(sut.are_incident(constants::v2_id, constants::v3_id)); + CHECK_FALSE(sut.are_adjacent(constants::v1_id, constants::v3_id)); + CHECK_FALSE(sut.are_adjacent(constants::v2_id, constants::v3_id)); } - SUBCASE("are_incident(vertex, vertex) should throw if at least one of the vertices is " + SUBCASE("are_adjacent(vertex, vertex) should throw if at least one of the vertices is " "invalid") { CHECK_THROWS_AS( discard_result( - sut.are_incident(fixture.out_of_range_vertex, fixture.out_of_range_vertex) + sut.are_adjacent(fixture.out_of_range_vertex, fixture.out_of_range_vertex) ), std::out_of_range ); CHECK_THROWS_AS( - discard_result(sut.are_incident(fixture.out_of_range_vertex, v2)), std::out_of_range + discard_result(sut.are_adjacent(fixture.out_of_range_vertex, v2)), std::out_of_range ); CHECK_THROWS_AS( - discard_result(sut.are_incident(v1, fixture.out_of_range_vertex)), std::out_of_range + discard_result(sut.are_adjacent(v1, fixture.out_of_range_vertex)), std::out_of_range ); } - SUBCASE("are_incident(vertex, vertex) should return true if there is an edge connecting " + SUBCASE("are_adjacent(vertex, vertex) should return true if there is an edge connecting " "the given vertices") { sut.add_edge(v1, v2); - CHECK(sut.are_incident(v1, v2)); - CHECK(sut.are_incident(v2, v1)); + CHECK(sut.are_adjacent(v1, v2)); + CHECK(sut.are_adjacent(v2, v1)); - CHECK_FALSE(sut.are_incident(v1, v3)); - CHECK_FALSE(sut.are_incident(v2, v3)); + CHECK_FALSE(sut.are_adjacent(v1, v3)); + CHECK_FALSE(sut.are_adjacent(v2, v3)); } - SUBCASE("are_incident(vertex, vertex) should return true the vertices are the same and " + SUBCASE("are_adjacent(vertex, vertex) should return true the vertices are the same and " "valid") { CHECK(std::ranges::all_of(sut.vertices(), [&sut](const auto& vertex) { - return sut.are_incident(vertex, vertex); + return sut.are_adjacent(vertex, vertex); })); } - SUBCASE("are_incident(edge, edge) should throw if either edge is invalid") { + SUBCASE("are_adjacent(edge, edge) should throw if either edge is invalid") { const auto edge = sut.add_edge(v1, v2); const edge_type invalid_edge{gl::invalid_id, v1.id(), v2.id()}; CHECK_THROWS_AS( - discard_result(sut.are_incident(edge, invalid_edge)), std::invalid_argument + discard_result(sut.are_adjacent(edge, invalid_edge)), std::invalid_argument ); CHECK_THROWS_AS( - discard_result(sut.are_incident(invalid_edge, edge)), std::invalid_argument + discard_result(sut.are_adjacent(invalid_edge, edge)), std::invalid_argument ); } - SUBCASE("are_incident(edge, edge) should return true only when the edges share a common " - "vertex") { + SUBCASE("are_adjacent(edge, edge) should return true only when the edges share a vertex") { const auto edge_1 = sut.add_edge(v1, v2); const auto edge_2 = sut.add_edge(v2, v3); const auto loop_3 = sut.add_edge(v3, v3); - CHECK(sut.are_incident(edge_1, edge_2)); - CHECK(sut.are_incident(edge_2, edge_1)); - CHECK_FALSE(sut.are_incident(edge_1, loop_3)); - CHECK_FALSE(sut.are_incident(loop_3, edge_1)); + CHECK(sut.are_adjacent(edge_1, edge_2)); + CHECK(sut.are_adjacent(edge_2, edge_1)); + CHECK_FALSE(sut.are_adjacent(edge_1, loop_3)); + CHECK_FALSE(sut.are_adjacent(loop_3, edge_1)); } SUBCASE("are_incident(vertex and edge pair) should throw if the vertex is invalid") { From 14b9a1d256bc862adf23b16b16f653761f1261be Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Fri, 10 Apr 2026 22:40:34 +0200 Subject: [PATCH 7/9] new vertex getters tests (impl) --- .../gl/impl/specialized/adjacency_matrix.hpp | 23 +++++-- .../specialized/flat_adjacency_matrix.hpp | 6 +- tests/source/gl/test_adjacency_list.cpp | 64 +++++++++++++++++++ tests/source/gl/test_adjacency_matrix.cpp | 59 +++++++++++++++++ 4 files changed, 143 insertions(+), 9 deletions(-) diff --git a/include/gl/impl/specialized/adjacency_matrix.hpp b/include/gl/impl/specialized/adjacency_matrix.hpp index 2f3e4da..52400a6 100644 --- a/include/gl/impl/specialized/adjacency_matrix.hpp +++ b/include/gl/impl/specialized/adjacency_matrix.hpp @@ -113,14 +113,19 @@ struct directed_adjacency_matrix { [[nodiscard]] static auto successor_ids(const impl_type& self, id_type vertex_id) { return self._matrix[to_idx(vertex_id)] | std::views::enumerate - | std::views::filter([](auto entry) { return entry.second != invalid_id; }) - | std::views::transform([](auto entry) { return static_cast(entry.first); }); + | std::views::filter([](auto entry) { + auto [target_id, edge_id] = entry; + return edge_id != invalid_id; + }) + | std::views::transform([](auto entry) { + auto [target_id, edge_id] = entry; + return static_cast(target_id); + }); } [[nodiscard]] static auto predecessor_ids(const impl_type& self, id_type vertex_id) { - const auto v_idx = to_idx(vertex_id); return std::views::iota(initial_id_v, static_cast(self._matrix.size())) - | std::views::filter([&](const auto src_id) { + | std::views::filter([&self, v_idx = to_idx(vertex_id)](const auto src_id) { return self._matrix[to_idx(src_id)][v_idx] != invalid_id; }); } @@ -302,8 +307,14 @@ struct undirected_adjacency_matrix { [[nodiscard]] static auto neighbor_ids(const impl_type& self, id_type vertex_id) { return self._matrix[to_idx(vertex_id)] | std::views::enumerate - | std::views::filter([](auto entry) { return entry.second != invalid_id; }) - | std::views::transform([](auto entry) { return static_cast(entry.first); }); + | std::views::filter([](auto entry) { + auto [target_id, edge_id] = entry; + return edge_id != invalid_id; + }) + | std::views::transform([](auto entry) { + auto [target_id, edge_id] = entry; + return static_cast(target_id); + }); } [[nodiscard]] gl_attr_force_inline static auto predecessor_ids( diff --git a/include/gl/impl/specialized/flat_adjacency_matrix.hpp b/include/gl/impl/specialized/flat_adjacency_matrix.hpp index 1ff349c..b3c5b18 100644 --- a/include/gl/impl/specialized/flat_adjacency_matrix.hpp +++ b/include/gl/impl/specialized/flat_adjacency_matrix.hpp @@ -124,7 +124,7 @@ struct directed_flat_adjacency_matrix { [[nodiscard]] gl_attr_force_inline static auto predecessor_ids( const impl_type& self, id_type vertex_id ) { - return std::views::iota(initial_id, static_cast(self._matrix.n_rows())) + return std::views::iota(initial_id_v, static_cast(self._matrix.n_rows())) | std::views::filter([&self, v_idx = to_idx(vertex_id)](const auto r_id) { return self._matrix[to_idx(r_id), v_idx] != invalid_id; }); @@ -133,7 +133,7 @@ struct directed_flat_adjacency_matrix { [[nodiscard]] gl_attr_force_inline static auto successor_ids( const impl_type& self, id_type vertex_id ) { - return std::views::iota(initial_id, static_cast(self._matrix.n_cols())) + return std::views::iota(initial_id_v, static_cast(self._matrix.n_cols())) | std::views::filter([&self, v_idx = to_idx(vertex_id)](const auto c_id) { return self._matrix[v_idx, to_idx(c_id)] != invalid_id; }); @@ -302,7 +302,7 @@ struct undirected_flat_adjacency_matrix { [[nodiscard]] gl_attr_force_inline static auto neighbor_ids( const impl_type& self, id_type vertex_id ) { - return std::views::iota(initial_id, static_cast(self._matrix.n_cols())) + return std::views::iota(initial_id_v, static_cast(self._matrix.n_cols())) | std::views::filter([&self, v_idx = to_idx(vertex_id)](const auto c_id) { return self._matrix[v_idx, to_idx(c_id)] != invalid_id; }); diff --git a/tests/source/gl/test_adjacency_list.cpp b/tests/source/gl/test_adjacency_list.cpp index c70722d..8688042 100644 --- a/tests/source/gl/test_adjacency_list.cpp +++ b/tests/source/gl/test_adjacency_list.cpp @@ -208,6 +208,51 @@ TEST_CASE_TEMPLATE_DEFINE("directed adjacency list tests", SutType, directed_adj // --- vertex getters --- + SUBCASE("successor_ids should return the ids of vertices to which the outgoing edges are " + "directed") { + add_edge(constants::v1_id, constants::v2_id); + add_edge(constants::v1_id, constants::v3_id); + + const auto successors = + sut.successor_ids(constants::v1_id) | std::ranges::to(); + + REQUIRE_EQ(successors.size(), 2uz); + CHECK(std::ranges::contains(successors, constants::v2_id)); + CHECK(std::ranges::contains(successors, constants::v3_id)); + + const auto successors_v2 = + sut.successor_ids(constants::v2_id) | std::ranges::to(); + CHECK_EQ(successors_v2.size(), 0uz); + } + + SUBCASE("predecessor_ids should return the ids of vertices from which the incoming edges " + "originate") { + add_edge(constants::v2_id, constants::v1_id); + add_edge(constants::v3_id, constants::v1_id); + + const auto predecessors = + sut.predecessor_ids(constants::v1_id) | std::ranges::to(); + + REQUIRE_EQ(predecessors.size(), 2uz); + CHECK(std::ranges::contains(predecessors, constants::v2_id)); + CHECK(std::ranges::contains(predecessors, constants::v3_id)); + + const auto predecessors_v2 = + sut.predecessor_ids(constants::v2_id) | std::ranges::to(); + CHECK_EQ(predecessors_v2.size(), 0uz); + } + + SUBCASE("neighbor_ids should return the combined ids of predecessors and successors") { + add_edge(constants::v2_id, constants::v1_id); // v2 is a predecessor + add_edge(constants::v1_id, constants::v3_id); // v3 is a successor + + const auto neighbors = sut.neighbor_ids(constants::v1_id) | std::ranges::to(); + + REQUIRE_EQ(neighbors.size(), 2uz); + CHECK(std::ranges::contains(neighbors, constants::v2_id)); + CHECK(std::ranges::contains(neighbors, constants::v3_id)); + } + // --- degree getters --- SUBCASE("degree should return the number of edges incident with the given vertex") { @@ -526,6 +571,25 @@ TEST_CASE_TEMPLATE_DEFINE("undirected adjacency list tests", SutType, undirected // --- vertex getters --- + SUBCASE("neighbor/predecessor/successor_ids should return the same sets of adjacent vertices " + "for undirected graphs") { + add_edge(constants::v1_id, constants::v2_id); + add_edge(constants::v3_id, constants::v1_id); + + const auto neighbors = sut.neighbor_ids(constants::v1_id) | std::ranges::to(); + const auto predecessors = + sut.predecessor_ids(constants::v1_id) | std::ranges::to(); + const auto successors = + sut.successor_ids(constants::v1_id) | std::ranges::to(); + + REQUIRE_EQ(neighbors.size(), 2uz); + CHECK(std::ranges::contains(neighbors, constants::v2_id)); + CHECK(std::ranges::contains(neighbors, constants::v3_id)); + + CHECK(std::ranges::is_permutation(neighbors, predecessors)); + CHECK(std::ranges::is_permutation(neighbors, successors)); + } + // --- degree getters --- SUBCASE("-/in/out__degree should return the number of edges incident with/to/from the given " diff --git a/tests/source/gl/test_adjacency_matrix.cpp b/tests/source/gl/test_adjacency_matrix.cpp index 13129ae..b3da0db 100644 --- a/tests/source/gl/test_adjacency_matrix.cpp +++ b/tests/source/gl/test_adjacency_matrix.cpp @@ -254,6 +254,43 @@ TEST_CASE_TEMPLATE_DEFINE("directed adjacency matrix tests", SutType, directed_a // --- vertex getters --- + SUBCASE("successor_ids should return the ids of vertices to which the outgoing edges are " + "directed") { + add_edge(constants::v1_id, constants::v2_id); + add_edge(constants::v1_id, constants::v3_id); + + auto successors = sut.successor_ids(constants::v1_id); + + REQUIRE_EQ(gl::util::range_size(successors), 2uz); + CHECK(std::ranges::contains(successors, constants::v2_id)); + CHECK(std::ranges::contains(successors, constants::v3_id)); + CHECK_EQ(gl::util::range_size(sut.successor_ids(constants::v2_id)), 0uz); + } + + SUBCASE("predecessor_ids should return the ids of vertices from which the incoming edges " + "originate") { + add_edge(constants::v2_id, constants::v1_id); + add_edge(constants::v3_id, constants::v1_id); + + auto predecessors = sut.predecessor_ids(constants::v1_id); + + REQUIRE_EQ(gl::util::range_size(predecessors), 2uz); + CHECK(std::ranges::contains(predecessors, constants::v2_id)); + CHECK(std::ranges::contains(predecessors, constants::v3_id)); + CHECK_EQ(gl::util::range_size(sut.predecessor_ids(constants::v2_id)), 0uz); + } + + SUBCASE("neighbor_ids should return the combined ids of predecessors and successors") { + add_edge(constants::v2_id, constants::v1_id); + add_edge(constants::v1_id, constants::v3_id); + + auto neighbors = sut.neighbor_ids(constants::v1_id); + + REQUIRE_EQ(gl::util::range_size(neighbors), 2uz); + CHECK(std::ranges::contains(neighbors, constants::v2_id)); + CHECK(std::ranges::contains(neighbors, constants::v3_id)); + } + // --- degree getters --- SUBCASE("degree should return the number of edges incident with the given vertex") { @@ -562,6 +599,28 @@ TEST_CASE_TEMPLATE_DEFINE( // --- vertex getters --- + SUBCASE("neighbor/predecessor/successor_ids should return the same sets of adjacent vertices " + "for undirected graphs") { + add_edge(constants::v1_id, constants::v2_id); + add_edge(constants::v3_id, constants::v1_id); + + auto neighbors_view = sut.neighbor_ids(constants::v1_id); + auto predecessors_view = sut.predecessor_ids(constants::v1_id); + auto successors_view = sut.successor_ids(constants::v1_id); + + REQUIRE_EQ(gl::util::range_size(neighbors_view), 2uz); + CHECK(std::ranges::contains(neighbors_view, constants::v2_id)); + CHECK(std::ranges::contains(neighbors_view, constants::v3_id)); + + // Evaluate views to vectors to ensure robust equivalence checks + const auto neighbors = neighbors_view | std::ranges::to(); + const auto predecessors = predecessors_view | std::ranges::to(); + const auto successors = successors_view | std::ranges::to(); + + CHECK(std::ranges::equal(neighbors, predecessors)); + CHECK(std::ranges::equal(neighbors, successors)); + } + // --- degree getters --- SUBCASE("-/in/out_degree should return the number of edges incident with/to/from the given " From c7053d5927c5d2881bff5244cfd8ea14094650f6 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Fri, 10 Apr 2026 22:55:53 +0200 Subject: [PATCH 8/9] new vertex getters tests (graph) --- tests/source/gl/test_graph.cpp | 65 ++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/tests/source/gl/test_graph.cpp b/tests/source/gl/test_graph.cpp index 9b11e81..d98bb9e 100644 --- a/tests/source/gl/test_graph.cpp +++ b/tests/source/gl/test_graph.cpp @@ -3,6 +3,7 @@ #include "testing/gl/functional.hpp" #include "testing/gl/types.hpp" +#include #include #include #include @@ -329,6 +330,70 @@ TEST_CASE_TEMPLATE_DEFINE("common graph structure tests", TraitsType, common_gra CHECK(std::ranges::equal(sut.vertex_ids(), constants::vertex_id_view)); } + GL_SUPPRESS_WARNING_BEGIN("-Warray-bounds"); + SUBCASE("neighbor/predecessor/successor_ids should throw out_of_range if vertex does not exist" + ) { + sut_type sut{}; + CHECK_THROWS_AS( + discard_result(sut.neighbor_ids(constants::out_of_rng_idx)), std::out_of_range + ); + CHECK_THROWS_AS( + discard_result(sut.predecessor_ids(constants::out_of_rng_idx)), std::out_of_range + ); + CHECK_THROWS_AS( + discard_result(sut.successor_ids(constants::out_of_rng_idx)), std::out_of_range + ); + } + GL_SUPPRESS_WARNING_END; + + SUBCASE("neighbor/predecessor/successor_ids should return valid ranges of ids") { + sut_type sut{constants::n_elements}; + sut.add_edge(constants::v1_id, constants::v2_id); + sut.add_edge(constants::v3_id, constants::v1_id); + + CHECK_FALSE(std::ranges::empty(sut.neighbor_ids(constants::v1_id))); + CHECK_FALSE(std::ranges::empty(sut.predecessor_ids(constants::v1_id))); + CHECK_FALSE(std::ranges::empty(sut.successor_ids(constants::v1_id))); + } + + GL_SUPPRESS_WARNING_BEGIN("-Warray-bounds"); + SUBCASE("neighbors/predecessors/successors should throw out_of_range if vertex does not exist" + ) { + sut_type sut{}; + CHECK_THROWS_AS( + discard_result(sut.neighbors(constants::out_of_rng_idx)), std::out_of_range + ); + CHECK_THROWS_AS( + discard_result(sut.predecessors(constants::out_of_rng_idx)), std::out_of_range + ); + CHECK_THROWS_AS( + discard_result(sut.successors(constants::out_of_rng_idx)), std::out_of_range + ); + } + GL_SUPPRESS_WARNING_END; + + SUBCASE("neighbor/predecessors/successors should return valid ranges of vertex descriptors") { + sut_type sut{constants::n_elements}; + sut.add_edge(constants::v1_id, constants::v2_id); + sut.add_edge(constants::v3_id, constants::v1_id); + + const auto neighbors = sut.neighbors(constants::v1_id) | std::ranges::to(); + const auto predecessors = + sut.predecessors(constants::v1_id) | std::ranges::to(); + const auto successors = sut.successors(constants::v1_id) | std::ranges::to(); + + CHECK_FALSE(neighbors.empty()); + CHECK_FALSE(predecessors.empty()); + CHECK_FALSE(successors.empty()); + + for (const auto& v : neighbors) + CHECK(v.is_valid()); + for (const auto& v : predecessors) + CHECK(v.is_valid()); + for (const auto& v : successors) + CHECK(v.is_valid()); + } + SUBCASE("has_vertex(id) should return true when a vertex with the given id is present in " "the graph") { sut_type sut{constants::n_elements}; From 8b2245c2bc9dcd84cae6f00b194699f03fc49e94 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Fri, 10 Apr 2026 23:29:36 +0200 Subject: [PATCH 9/9] resolved comments by gemini --- include/gl/graph.hpp | 19 +++--- .../impl/specialized/flat_adjacency_list.hpp | 4 +- .../specialized/flat_adjacency_matrix.hpp | 2 +- tests/source/gl/test_adjacency_list.cpp | 58 +++++++++---------- tests/source/gl/test_graph.cpp | 26 ++++----- 5 files changed, 52 insertions(+), 57 deletions(-) diff --git a/include/gl/graph.hpp b/include/gl/graph.hpp index d220987..ad76c0e 100644 --- a/include/gl/graph.hpp +++ b/include/gl/graph.hpp @@ -524,17 +524,13 @@ class graph final { // --- adjacency and incidence methods --- - [[nodiscard]] bool are_adjacent(const id_type source_id, const id_type target_id) const { - this->_verify_vertex_id(source_id); - if (source_id == target_id) - return true; - - this->_verify_vertex_id(target_id); - - if constexpr (traits::c_directed_edge) - return this->has_edge(source_id, target_id) or this->has_edge(target_id, source_id); - else + [[nodiscard]] gl_attr_force_inline bool are_adjacent( + const id_type source_id, const id_type target_id + ) const { + if constexpr (traits::c_undirected_graph) return this->has_edge(source_id, target_id); + else + return this->has_edge(source_id, target_id) or this->has_edge(target_id, source_id); } [[nodiscard]] gl_attr_force_inline bool are_adjacent( @@ -546,6 +542,9 @@ class graph final { [[nodiscard]] bool are_adjacent(const edge_type& edge_1, const edge_type& edge_2) const { this->_verify_edge(edge_1); this->_verify_edge(edge_2); + + if (edge_1.id() == edge_2.id()) + return false; return edge_1.is_incident_with(edge_2.source()) or edge_1.is_incident_with(edge_2.target()); } diff --git a/include/gl/impl/specialized/flat_adjacency_list.hpp b/include/gl/impl/specialized/flat_adjacency_list.hpp index e16ec36..5876be1 100644 --- a/include/gl/impl/specialized/flat_adjacency_list.hpp +++ b/include/gl/impl/specialized/flat_adjacency_list.hpp @@ -207,9 +207,7 @@ struct undirected_flat_adjacency_list { // rebuild the graph (faster then shifting the entire data block for each removed edge) typename impl_type::adjacency_storage_type new_list; new_list.reserve_segments(self._list.size() - 1uz); - const auto estimated_new_size = - self._list.data_size() - (self._list[vertex_idx].size() * 2uz); - new_list.reserve_data(estimated_new_size); + new_list.reserve_data(self._list.data_size() - self._list[vertex_idx].size()); std::vector buffer; for (auto idx = 0uz; idx < self._list.size(); ++idx) { diff --git a/include/gl/impl/specialized/flat_adjacency_matrix.hpp b/include/gl/impl/specialized/flat_adjacency_matrix.hpp index b3c5b18..0bcc3dd 100644 --- a/include/gl/impl/specialized/flat_adjacency_matrix.hpp +++ b/include/gl/impl/specialized/flat_adjacency_matrix.hpp @@ -88,7 +88,7 @@ struct directed_flat_adjacency_matrix { static std::vector remove_vertex(impl_type& self, id_type vertex_id) { const auto vertex_idx = to_idx(vertex_id); std::vector removed_edges; - removed_edges.reserve(self._matrix.size()); + removed_edges.reserve(self._matrix.size() * 2uz); // extract out-edges for (auto edge_id : self._matrix[vertex_idx]) diff --git a/tests/source/gl/test_adjacency_list.cpp b/tests/source/gl/test_adjacency_list.cpp index 8688042..aa8a1ca 100644 --- a/tests/source/gl/test_adjacency_list.cpp +++ b/tests/source/gl/test_adjacency_list.cpp @@ -113,7 +113,7 @@ TEST_CASE_TEMPLATE_INSTANTIATE( namespace { -constexpr gl::size_type n_inc_edged_for_fully_connected_vertex = constants::n_elements - 1uz; +constexpr gl::size_type n_inc_edges_for_fully_connected_vertex = constants::n_elements - 1uz; } // namespace @@ -142,11 +142,11 @@ struct test_directed_adjacency_list : public test_adjacency_list { if (no_loops) REQUIRE(std::ranges::all_of(get(sut), [&](const auto& adj_items) { - return adj_items.size() == n_inc_edged_for_fully_connected_vertex; + return adj_items.size() == n_inc_edges_for_fully_connected_vertex; })); else REQUIRE(std::ranges::all_of(get(sut), [&](const auto& adj_items) { - return adj_items.size() == n_inc_edged_for_fully_connected_vertex + 1uz; + return adj_items.size() == n_inc_edges_for_fully_connected_vertex + 1uz; })); } @@ -261,13 +261,13 @@ TEST_CASE_TEMPLATE_DEFINE("directed adjacency list tests", SutType, directed_adj CHECK(std::ranges::all_of( constants::vertex_id_view, - [](const auto deg) { return deg == 2uz * n_inc_edged_for_fully_connected_vertex; }, + [](const auto deg) { return deg == 2uz * n_inc_edges_for_fully_connected_vertex; }, deg_proj )); add_edge(constants::v1_id, constants::v1_id); - CHECK_EQ(deg_proj(constants::v1_id), 2uz * (n_inc_edged_for_fully_connected_vertex + 1uz)); + CHECK_EQ(deg_proj(constants::v1_id), 2uz * (n_inc_edges_for_fully_connected_vertex + 1uz)); } SUBCASE("in/out_degree should return the number of edges incident {to/from} the given vertex") { @@ -287,13 +287,13 @@ TEST_CASE_TEMPLATE_DEFINE("directed adjacency list tests", SutType, directed_adj CHECK(std::ranges::all_of( constants::vertex_id_view, - [](const auto deg) { return deg == n_inc_edged_for_fully_connected_vertex; }, + [](const auto deg) { return deg == n_inc_edges_for_fully_connected_vertex; }, deg_proj )); add_edge(constants::v1_id, constants::v1_id); - CHECK_EQ(deg_proj(constants::v1_id), n_inc_edged_for_fully_connected_vertex + 1uz); + CHECK_EQ(deg_proj(constants::v1_id), n_inc_edges_for_fully_connected_vertex + 1uz); } SUBCASE("degree_map should return a map of the numbers of edges incident with the " @@ -353,13 +353,13 @@ TEST_CASE_TEMPLATE_DEFINE("directed adjacency list tests", SutType, directed_adj fully_connect_vertex(constants::v1_id); auto out_edges = sut.out_edges(constants::v1_id); - REQUIRE_EQ(out_edges.size(), n_inc_edged_for_fully_connected_vertex); + REQUIRE_EQ(out_edges.size(), n_inc_edges_for_fully_connected_vertex); const auto& edge_to_remove = out_edges[0uz]; sut.remove_edge(edge_to_remove); out_edges = sut.out_edges(constants::v1_id); - REQUIRE_EQ(out_edges.size(), n_inc_edged_for_fully_connected_vertex - 1uz); + REQUIRE_EQ(out_edges.size(), n_inc_edges_for_fully_connected_vertex - 1uz); // validate that the incident edges list has been properly aligned CHECK_EQ(std::ranges::find(out_edges, edge_to_remove), out_edges.end()); } @@ -504,18 +504,18 @@ struct test_undirected_adjacency_list : public test_adjacency_list { if (no_loops) REQUIRE(std::ranges::all_of(get(sut), [&](const auto& adj_items) { - return adj_items.size() == n_inc_edged_for_fully_connected_vertex; + return adj_items.size() == n_inc_edges_for_fully_connected_vertex; })); else REQUIRE(std::ranges::all_of(get(sut), [&](const auto& adj_items) { - return adj_items.size() == n_inc_edged_for_fully_connected_vertex + 1uz; + return adj_items.size() == n_inc_edges_for_fully_connected_vertex + 1uz; })); } sut_type sut{constants::n_elements}; const gl::size_type n_unique_edges_in_full_graph = - (n_inc_edged_for_fully_connected_vertex * constants::n_elements) / 2uz; + (n_inc_edges_for_fully_connected_vertex * constants::n_elements) / 2uz; }; TEST_CASE_TEMPLATE_DEFINE("undirected adjacency list tests", SutType, undirected_adj_list_template) { @@ -614,7 +614,7 @@ TEST_CASE_TEMPLATE_DEFINE("undirected adjacency list tests", SutType, undirected CHECK(std::ranges::all_of( constants::vertex_id_view, - [](const auto deg) { return deg == n_inc_edged_for_fully_connected_vertex; }, + [](const auto deg) { return deg == n_inc_edges_for_fully_connected_vertex; }, deg_proj )); @@ -622,7 +622,7 @@ TEST_CASE_TEMPLATE_DEFINE("undirected adjacency list tests", SutType, undirected CHECK_EQ( deg_proj(constants::v1_id), - n_inc_edged_for_fully_connected_vertex + 2uz // loops counted twice + n_inc_edges_for_fully_connected_vertex + 2uz // loops counted twice ); } @@ -658,16 +658,16 @@ TEST_CASE_TEMPLATE_DEFINE("undirected adjacency list tests", SutType, undirected REQUIRE(new_edge.is_incident_from(constants::v1_id)); REQUIRE(new_edge.is_incident_to(constants::v2_id)); - const auto inc_edged_1 = sut.incident_edges(constants::v1_id); - const auto inc_edged_2 = sut.incident_edges(constants::v2_id); + const auto inc_edges_1 = sut.incident_edges(constants::v1_id); + const auto inc_edges_2 = sut.incident_edges(constants::v2_id); - REQUIRE_EQ(inc_edged_1.size(), 1uz); - REQUIRE_EQ(inc_edged_2.size(), 1uz); + REQUIRE_EQ(inc_edges_1.size(), 1uz); + REQUIRE_EQ(inc_edges_2.size(), 1uz); - const auto& new_edge_extracted_1 = inc_edged_1[0uz]; + const auto& new_edge_extracted_1 = inc_edges_1[0uz]; CHECK_EQ(new_edge_extracted_1, new_edge); - const auto& new_edge_extracted_2 = inc_edged_2[0uz]; + const auto& new_edge_extracted_2 = inc_edges_2[0uz]; CHECK_EQ(new_edge_extracted_2, new_edge); } @@ -692,10 +692,10 @@ TEST_CASE_TEMPLATE_DEFINE("undirected adjacency list tests", SutType, undirected SUBCASE("remove_edge should remove the edge from both the first and second vertices' list") { fully_connect_vertex(constants::v1_id); - auto inc_edged_first = sut.incident_edges(constants::v1_id); - REQUIRE_EQ(inc_edged_first.size(), n_inc_edged_for_fully_connected_vertex); + auto inc_edges_first = sut.incident_edges(constants::v1_id); + REQUIRE_EQ(inc_edges_first.size(), n_inc_edges_for_fully_connected_vertex); - const auto& edge_to_remove = inc_edged_first[0uz]; + const auto& edge_to_remove = inc_edges_first[0uz]; const auto target_id = edge_to_remove.target(); REQUIRE_EQ(sut.incident_edges(target_id).size(), 1uz); @@ -703,14 +703,14 @@ TEST_CASE_TEMPLATE_DEFINE("undirected adjacency list tests", SutType, undirected sut.remove_edge(edge_to_remove); // validate that the first incident edges list has been properly aligned - inc_edged_first = sut.incident_edges(0uz); - REQUIRE_EQ(inc_edged_first.size(), n_inc_edged_for_fully_connected_vertex - 1uz); - CHECK_EQ(std::ranges::find(inc_edged_first, edge_to_remove), inc_edged_first.end()); + inc_edges_first = sut.incident_edges(0uz); + REQUIRE_EQ(inc_edges_first.size(), n_inc_edges_for_fully_connected_vertex - 1uz); + CHECK_EQ(std::ranges::find(inc_edges_first, edge_to_remove), inc_edges_first.end()); // validate that the second adjacent edges list has been properly aligned - const auto inc_edged_second = sut.incident_edges(target_id); - REQUIRE_EQ(inc_edged_second.size(), 0uz); - CHECK_EQ(std::ranges::find(inc_edged_second, edge_to_remove), inc_edged_second.end()); + const auto inc_edges_second = sut.incident_edges(target_id); + REQUIRE_EQ(inc_edges_second.size(), 0uz); + CHECK_EQ(std::ranges::find(inc_edges_second, edge_to_remove), inc_edges_second.end()); } // --- edge getters --- diff --git a/tests/source/gl/test_graph.cpp b/tests/source/gl/test_graph.cpp index d98bb9e..aa959a5 100644 --- a/tests/source/gl/test_graph.cpp +++ b/tests/source/gl/test_graph.cpp @@ -926,13 +926,6 @@ TEST_CASE_TEMPLATE_DEFINE("common graph structure tests", TraitsType, common_gra ); } - SUBCASE("are_adjacent(vertex_id, vertex_id) should return true the ids are the same and " - "valid") { - CHECK(std::ranges::all_of(constants::vertex_id_view, [&sut](const auto vertex_id) { - return sut.are_adjacent(vertex_id, vertex_id); - })); - } - SUBCASE("are_adjacent(vertex_id, vertex_id) should return true if there is an edge " "connecting the given vertices") { sut.add_edge(v1, v2); @@ -942,6 +935,11 @@ TEST_CASE_TEMPLATE_DEFINE("common graph structure tests", TraitsType, common_gra CHECK_FALSE(sut.are_adjacent(constants::v1_id, constants::v3_id)); CHECK_FALSE(sut.are_adjacent(constants::v2_id, constants::v3_id)); + + // self adjacency + CHECK_FALSE(sut.are_adjacent(constants::v1_id, constants::v1_id)); + sut.add_edge(v1, v1); + CHECK(sut.are_adjacent(constants::v1_id, constants::v1_id)); } SUBCASE("are_adjacent(vertex, vertex) should throw if at least one of the vertices is " @@ -969,13 +967,11 @@ TEST_CASE_TEMPLATE_DEFINE("common graph structure tests", TraitsType, common_gra CHECK_FALSE(sut.are_adjacent(v1, v3)); CHECK_FALSE(sut.are_adjacent(v2, v3)); - } - SUBCASE("are_adjacent(vertex, vertex) should return true the vertices are the same and " - "valid") { - CHECK(std::ranges::all_of(sut.vertices(), [&sut](const auto& vertex) { - return sut.are_adjacent(vertex, vertex); - })); + // self adjacency + CHECK_FALSE(sut.are_adjacent(v1, v1)); + sut.add_edge(v1, v1); + CHECK(sut.are_adjacent(v1, v1)); } SUBCASE("are_adjacent(edge, edge) should throw if either edge is invalid") { @@ -990,11 +986,13 @@ TEST_CASE_TEMPLATE_DEFINE("common graph structure tests", TraitsType, common_gra ); } - SUBCASE("are_adjacent(edge, edge) should return true only when the edges share a vertex") { + SUBCASE("are_adjacent(edge, edge) should return true only when the edges are distinct and " + "share a vertex") { const auto edge_1 = sut.add_edge(v1, v2); const auto edge_2 = sut.add_edge(v2, v3); const auto loop_3 = sut.add_edge(v3, v3); + CHECK_FALSE(sut.are_adjacent(edge_1, edge_1)); CHECK(sut.are_adjacent(edge_1, edge_2)); CHECK(sut.are_adjacent(edge_2, edge_1)); CHECK_FALSE(sut.are_adjacent(edge_1, loop_3));