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/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 f5c9175..ad76c0e 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,99 +205,118 @@ 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); + // --- vertex getters --- + + [[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 size_type in_degree(const vertex_type& vertex) const { - return this->in_degree(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 std::vector in_degree_map() const { - return this->_impl.in_degree_map(); + [[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 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 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 size_type out_degree(const vertex_type& vertex) const { - return this->out_degree(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 std::vector out_degree_map() const { - return this->_impl.out_degree_map(); + [[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 size_type degree(const id_type vertex_id) const { + [[nodiscard]] gl_attr_force_inline auto predecessor_ids(const id_type vertex_id) const { this->_verify_vertex_id(vertex_id); - return this->_impl.degree(vertex_id); + return this->_impl.predecessor_ids(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 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 std::vector degree_map() const { - return this->_impl.degree_map(); + [[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 at(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.at(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.at(vertex_id); + return vertex_descriptor{vertex_id}; } - [[nodiscard]] gl_attr_force_inline auto at(const vertex_type& vertex) const { - return this->at(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 incident_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.incident_edges(vertex_id, this->_edge_properties); - else - return this->_impl.incident_edges(vertex_id); + return this->_impl.degree(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 size_type degree(const vertex_type& vertex) const { + return this->degree(vertex.id()); } - [[nodiscard]] inline auto in_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.in_edges(vertex_id, this->_edge_properties); - else - return this->_impl.in_edges(vertex_id); + return this->_impl.in_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 in_degree(const vertex_type& vertex) const { + return this->in_degree(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 std::vector in_degree_map() const { + return this->_impl.in_degree_map(); } - [[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 out_degree(const id_type vertex_id) const { + this->_verify_vertex_id(vertex_id); + return this->_impl.out_degree(vertex_id); } - // --- edge methods --- + [[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 auto edge_ids() const noexcept { - return std::views::iota(initial_id_v, this->_n_edges); + [[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); @@ -371,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()); } @@ -426,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 { @@ -480,44 +522,30 @@ 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--; - } + // --- adjacency and incidence methods --- - 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 --- - - [[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) - 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_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()); + } + + [[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()); } [[nodiscard]] bool are_incident(const vertex_type& vertex, const edge_type& edge) const { @@ -532,13 +560,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) @@ -570,6 +592,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 { @@ -694,7 +730,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'; } } @@ -704,7 +740,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'; } @@ -733,8 +769,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() @@ -743,14 +779,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'; @@ -758,7 +794,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 6f33346..90f2f83 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 @@ -120,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); } @@ -132,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)] @@ -171,27 +206,10 @@ 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) { - return this->out_edges(vertex_id); + return specialized_impl::incident_edges(*this, vertex_id); } [[nodiscard]] gl_attr_force_inline auto incident_edges( @@ -199,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 @@ -257,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 258f1ef..832e688 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,28 +197,10 @@ 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) { - return this->out_edges(vertex_id); + return specialized_impl::incident_edges(*this, vertex_id); } [[nodiscard]] gl_attr_force_inline auto incident_edges( @@ -189,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 6429406..839c3f4 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& out_edges : self._list) + in_deg += static_cast( + std::ranges::count(out_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,39 @@ 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]] 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) { + 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 +231,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 +285,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 +305,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 +349,26 @@ 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 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 + ) { + 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..52400a6 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,52 @@ 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]] 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) { + 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 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 predecessor_ids(const impl_type& self, id_type vertex_id) { + return std::views::iota(initial_id_v, static_cast(self._matrix.size())) + | 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; + }); } + [[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 +148,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 +187,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,9 +227,26 @@ 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; } + + // --- 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> @@ -203,12 +257,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 +287,50 @@ 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) { + 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( 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 +342,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 +369,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); @@ -334,6 +424,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 4552b89..5876be1 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,39 @@ struct directed_flat_adjacency_list { const auto pos = static_cast(std::distance(segment.begin(), it)); self._list.erase(edge_src, pos); } + + // --- 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) { + 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 +194,60 @@ 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); + 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) + 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 +255,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 +275,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 +328,26 @@ struct undirected_flat_adjacency_list { self._list.erase(tgt_idx, pos); } } + + // --- 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 + ) { + 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..0bcc3dd 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() * 2uz); + + // 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_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; + }); } + [[nodiscard]] gl_attr_force_inline static auto successor_ids( + const impl_type& self, id_type vertex_id + ) { + 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; + }); + } + + // --- 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,9 +227,26 @@ 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; } + + // --- 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> @@ -205,10 +259,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 +283,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_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; + }); } - [[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 +332,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 +359,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); @@ -326,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/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/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 9451ad4..aa8a1ca 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_edges_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_edges_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_edges_for_fully_connected_vertex + 1uz; })); } @@ -165,25 +171,201 @@ 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 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 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 --- + + 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") { + 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_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_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_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_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); 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); - 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 = incident_edges_1[0uz]; + const auto& new_edge_extracted = out_edges_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 out_edges = sut.out_edges(constants::v1_id); + 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_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()); + } + + // --- 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,25 +426,16 @@ 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); + 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); - auto incident_edges = sut.incident_edges(constants::v1_id); - REQUIRE_EQ(incident_edges.size(), n_incident_edges_for_fully_connected_vertex); + const auto inc_edges = + sut.incident_edges(constants::v1_id) | std::ranges::to(); - 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()); + 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") { @@ -287,114 +460,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.out_edges(vertex_id))); } } @@ -433,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_incident_edges_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_incident_edges_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_incident_edges_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) { @@ -465,21 +536,138 @@ 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 --- + + 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 " + "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_edges_for_fully_connected_vertex; }, + deg_proj + )); + + add_edge(constants::v1_id, constants::v1_id); + + CHECK_EQ( + deg_proj(constants::v1_id), + n_inc_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)); 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_edges_1 = sut.incident_edges(constants::v1_id); + const auto inc_edges_2 = sut.incident_edges(constants::v2_id); - REQUIRE_EQ(incident_edges_1.size(), 1uz); - REQUIRE_EQ(incident_edges_2.size(), 1uz); + REQUIRE_EQ(inc_edges_1.size(), 1uz); + REQUIRE_EQ(inc_edges_2.size(), 1uz); - const auto& new_edge_extracted_1 = incident_edges_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 = incident_edges_2[0uz]; + const auto& new_edge_extracted_2 = inc_edges_2[0uz]; CHECK_EQ(new_edge_extracted_2, new_edge); } @@ -495,12 +683,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_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_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 + 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_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 --- + 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,140 +782,22 @@ 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); + 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}; - 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() - ); + 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)); } - 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))); - } + // --- access operators --- - SUBCASE("{in_/out_/}degree should return the number of edges incident {to/from} the given " - "vertex") { + SUBCASE("at should return the incident edges of a 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(); - } - - 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); + 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..b3da0db 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); @@ -144,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 @@ -176,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) { @@ -209,133 +217,98 @@ TEST_CASE_TEMPLATE_DEFINE("directed adjacency matrix tests", SutType, directed_a fixture.init_complete_graph(no_loops); }; - 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)); - - 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); - - const auto& new_edge_extracted = *std::ranges::begin(incident_edges_1); - CHECK_EQ(new_edge_extracted, new_edge); - } + // --- vertex modifiers --- - 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_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); - REQUIRE_EQ(std::ranges::count_if(row_view, &edge_type::is_valid), 1uz); + const auto edge5 = add_edge(constants::v2_id, constants::v3_id); + const auto edge6 = add_edge(constants::v3_id, constants::v2_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); - } - } + const auto removed_vertex_id = constants::v1_id; + const auto removed_edge_ids = sut.remove_vertex(removed_vertex_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); + 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)); - REQUIRE_EQ(gl::util::range_size(incident_edges), 1uz); - CHECK_EQ(*std::ranges::begin(incident_edges), edge); - } + // Check the structure of the graph considering the aligned IDs + CHECK_EQ(size(sut), constants::n_elements - 1uz); - 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); + 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); - CHECK(sut.has_edge(constants::v1_id, constants::v2_id)); - CHECK_FALSE(sut.has_edge(constants::v2_id, constants::v1_id)); - CHECK_FALSE(sut.has_edge(constants::v1_id, constants::v3_id)); - CHECK_FALSE(sut.has_edge(constants::v2_id, constants::v3_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); } - SUBCASE("has_edge(edge_ptr) should return true if the given edge is present in the graph") { - const auto valid_edge = add_edge(constants::v1_id, constants::v2_id); - CHECK(sut.has_edge(valid_edge)); + // --- vertex getters --- - const edge_type invalid_edge{gl::invalid_id, constants::v1_id, constants::v2_id}; - CHECK_FALSE(sut.has_edge(invalid_edge)); + 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); - // edge connecting vertices not connected in the actual graph - const edge_type not_present_edge{valid_edge.id(), constants::v2_id, constants::v3_id}; - CHECK_FALSE(sut.has_edge(not_present_edge)); - } + auto successors = sut.successor_ids(constants::v1_id); - SUBCASE("get_edge(id, id) should return nullopt if there is no edge connecting the given " - "vertices") { - CHECK_FALSE(sut.get_edge(constants::v1_id, constants::v2_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("get_edge(id, id) should return a valid edge if the given vertices are connected") { - const auto edge_1 = add_edge(constants::v1_id, constants::v2_id); - - const auto edge_opt = sut.get_edge(constants::v1_id, constants::v2_id); - REQUIRE(edge_opt.has_value()); - CHECK_EQ(*edge_opt, edge_1); + 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); - CHECK_FALSE(sut.get_edge(constants::v2_id, constants::v2_id)); - } + auto predecessors = sut.predecessor_ids(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 - ); + 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("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 - ); + 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); - const auto edge_to_remove = *std::ranges::begin(incident_edges); - sut.remove_edge(edge_to_remove); + auto neighbors = sut.neighbor_ids(constants::v1_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(neighbors), 2uz); + CHECK(std::ranges::contains(neighbors, constants::v2_id)); + CHECK(std::ranges::contains(neighbors, constants::v3_id)); } - 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); - - const auto in_edges = sut.in_edges(constants::v1_id) | std::ranges::to(); + // --- degree getters --- - REQUIRE_EQ(in_edges.size(), 2uz); - CHECK(std::ranges::contains(in_edges, edge1)); - CHECK(std::ranges::contains(in_edges, edge2)); - } + 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); }; - SUBCASE("out_edges should return edges where the vertex is the source") { - const auto edge1 = add_edge(constants::v1_id, constants::v2_id); - const auto edge2 = add_edge(constants::v1_id, constants::v3_id); + CHECK(std::ranges::all_of( + constants::vertex_id_view, + [](const auto deg) { return deg == 2uz * n_out_edges_for_fully_connected_vertex; }, + deg_proj + )); - const auto out_edges = sut.out_edges(constants::v1_id) | std::ranges::to(); + add_edge(constants::v1_id, constants::v1_id); - REQUIRE_EQ(out_edges.size(), 2uz); - CHECK(std::ranges::contains(out_edges, edge1)); - CHECK(std::ranges::contains(out_edges, edge2)); + 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" - ) { + SUBCASE("in/out_degree should return the number of edges incident {to/from} the given vertex") { init_complete_graph(); std::function deg_proj; @@ -352,33 +325,27 @@ 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 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 - )); + 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; - add_edge(constants::v1_id, constants::v1_id); + std::vector degree_map = sut.degree_map(); - CHECK_EQ( - deg_proj(constants::v1_id), 2uz * (n_incident_edges_for_fully_connected_vertex + 1uz) - ); + 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 " + 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; @@ -399,103 +366,188 @@ TEST_CASE_TEMPLATE_DEFINE("directed adjacency matrix tests", SutType, directed_a 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; + // --- edge modifiers --- - std::vector degree_map = sut.degree_map(); + 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_EQ(degree_map.size(), constants::n_elements); - CHECK_EQ(std::ranges::count(degree_map, expected_deg), constants::n_elements); - } + REQUIRE(new_edge.is_incident_from(constants::v1_id)); + REQUIRE(new_edge.is_incident_to(constants::v2_id)); - 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); + 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 edge5 = add_edge(constants::v2_id, constants::v3_id); - const auto edge6 = add_edge(constants::v3_id, constants::v2_id); + const auto& new_edge_extracted = *std::ranges::begin(out_edges_1); + CHECK_EQ(new_edge_extracted, new_edge); + } - const auto removed_vertex_id = constants::v1_id; - const auto removed_edge_ids = sut.remove_vertex(removed_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 + ); + } - 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)); + SUBCASE("remove_edge should remove the edge from the source vertex's list") { + fully_connect_vertex(constants::v1_id); - // Check the structure of the graph considering the aligned IDs - CHECK_EQ(size(sut), constants::n_elements - 1uz); + auto out_edges = sut.out_edges(constants::v1_id); + REQUIRE_EQ(gl::util::range_size(out_edges), n_out_edges_for_fully_connected_vertex); - 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); + const auto edge_to_remove = *std::ranges::begin(out_edges); + sut.remove_edge(edge_to_remove); - 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); + // validate that the incident edges list has been properly aligned + 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()); } -} -TEST_CASE_TEMPLATE_INSTANTIATE( - directed_adj_matrix_template, - gl::impl::adjacency_matrix>, // normal adj matrix - gl::impl::adjacency_matrix> // flat adj matrix -); + // --- edge getters --- -template -struct test_undirected_adjacency_matrix : public test_adjacency_matrix { - using sut_type = SutType; - using edge_type = typename sut_type::edge_type; + 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); - 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}; + CHECK(sut.has_edge(constants::v1_id, constants::v2_id)); + CHECK_FALSE(sut.has_edge(constants::v2_id, constants::v1_id)); + CHECK_FALSE(sut.has_edge(constants::v1_id, constants::v3_id)); + CHECK_FALSE(sut.has_edge(constants::v2_id, constants::v3_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; + SUBCASE("has_edge(edge_ptr) should return true if the given edge is present in the graph") { + const auto valid_edge = add_edge(constants::v1_id, constants::v2_id); + CHECK(sut.has_edge(valid_edge)); - add_edge(source_id, target_id); - } + const edge_type invalid_edge{gl::invalid_id, constants::v1_id, constants::v2_id}; + CHECK_FALSE(sut.has_edge(invalid_edge)); + + // edge connecting vertices not connected in the actual graph + const edge_type not_present_edge{valid_edge.id(), constants::v2_id, constants::v3_id}; + CHECK_FALSE(sut.has_edge(not_present_edge)); } - void init_complete_graph(const bool no_loops = true) { - for (const auto source_id : constants::vertex_id_view) { - const auto bound = no_loops ? source_id : source_id + 1uz; - for (const auto target_id : std::views::iota(constants::v1_id, bound)) - add_edge(source_id, target_id); - } + SUBCASE("get_edge(id, id) should return nullopt if there is no edge connecting the given " + "vertices") { + CHECK_FALSE(sut.get_edge(constants::v1_id, constants::v2_id)); + } - 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; - })); - 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; - })); + SUBCASE("get_edge(id, id) should return a valid edge if the given vertices are connected") { + const auto edge_1 = add_edge(constants::v1_id, constants::v2_id); + + const auto edge_opt = sut.get_edge(constants::v1_id, constants::v2_id); + REQUIRE(edge_opt.has_value()); + CHECK_EQ(*edge_opt, edge_1); + + CHECK_FALSE(sut.get_edge(constants::v2_id, constants::v2_id)); } - sut_type sut{constants::n_elements}; + 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); - static constexpr gl::size_type n_unique_edges_in_full_graph = - (n_incident_edges_for_fully_connected_vertex * constants::n_elements) / 2; -}; + const auto inc_edges = + sut.incident_edges(constants::v1_id) | std::ranges::to(); -TEST_CASE_TEMPLATE_DEFINE( + 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); + + const auto in_edges = sut.in_edges(constants::v1_id) | std::ranges::to(); + + REQUIRE_EQ(in_edges.size(), 2uz); + CHECK(std::ranges::contains(in_edges, edge1)); + CHECK(std::ranges::contains(in_edges, edge2)); + } + + SUBCASE("out_edges should return edges where the vertex is the source") { + const auto edge1 = add_edge(constants::v1_id, constants::v2_id); + const auto edge2 = add_edge(constants::v1_id, constants::v3_id); + + const auto out_edges = sut.out_edges(constants::v1_id) | std::ranges::to(); + + REQUIRE_EQ(out_edges.size(), 2uz); + CHECK(std::ranges::contains(out_edges, edge1)); + CHECK(std::ranges::contains(out_edges, edge2)); + } + + // --- access operators --- + + 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); + + REQUIRE_EQ(std::ranges::count_if(row_view, &edge_type::is_valid), 1uz); + + 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); + } + } +} + +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) { + const auto bound = no_loops ? source_id : source_id + 1uz; + for (const auto target_id : std::views::iota(constants::v1_id, bound)) + add_edge(source_id, target_id); + } + + 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_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_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_out_edges_for_fully_connected_vertex * constants::n_elements) / 2; +}; + +TEST_CASE_TEMPLATE_DEFINE( "undirected adjacency matrix tests", SutType, undirected_adj_matrix_template ) { using fixture_type = test_undirected_adjacency_matrix; @@ -515,6 +567,123 @@ 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 --- + + 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 " + "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_out_edges_for_fully_connected_vertex; }, + deg_proj + )); + + add_edge(constants::v1_id, constants::v1_id); + + CHECK_EQ( + deg_proj(constants::v1_id), + n_out_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,76 +714,6 @@ 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); - - 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 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 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); - } - - 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); - - REQUIRE_EQ(gl::util::range_size(incident_edges), 1uz); - CHECK_EQ(*std::ranges::begin(incident_edges), edge); - } - - 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); - - CHECK(sut.has_edge(constants::v1_id, constants::v2_id)); - CHECK(sut.has_edge(constants::v2_id, constants::v1_id)); - CHECK_FALSE(sut.has_edge(constants::v1_id, constants::v3_id)); - CHECK_FALSE(sut.has_edge(constants::v2_id, constants::v3_id)); - } - - SUBCASE("has_edge(edge_ptr) should return true if the given edge is present in the graph") { - const auto valid_edge = add_edge(constants::v1_id, constants::v2_id); - CHECK(sut.has_edge(valid_edge)); - - const edge_type invalid_edge{gl::invalid_id, constants::v1_id, constants::v2_id}; - CHECK_FALSE(sut.has_edge(invalid_edge)); - - // edge connecting vertices not connected in the actual graph - const edge_type not_present_edge{valid_edge.id(), constants::v2_id, constants::v3_id}; - CHECK_FALSE(sut.has_edge(not_present_edge)); - } - - SUBCASE("get_edge(id, id) should return nullopt if there is no edge connecting the given " - "vertices") { - CHECK_FALSE(sut.get_edge(constants::v1_id, constants::v2_id)); - } - - SUBCASE("get_edge(id, id) should return a valid edge if the given vertices are connected") { - const auto edge_1 = add_edge(constants::v1_id, constants::v2_id); - - const auto edge_opt_1 = sut.get_edge(constants::v1_id, constants::v2_id); - REQUIRE(edge_opt_1.has_value()); - CHECK_EQ(*edge_opt_1, edge_1); - - const auto edge_opt_2 = sut.get_edge(constants::v2_id, constants::v1_id); - REQUIRE(edge_opt_2.has_value()); - 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( @@ -628,7 +727,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); @@ -641,8 +740,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() @@ -656,103 +754,88 @@ TEST_CASE_TEMPLATE_DEFINE( ); } - 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); + // --- edge getters --- - 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(); + 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); - 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(sut.has_edge(constants::v1_id, constants::v2_id)); + CHECK(sut.has_edge(constants::v2_id, constants::v1_id)); + CHECK_FALSE(sut.has_edge(constants::v1_id, constants::v3_id)); + CHECK_FALSE(sut.has_edge(constants::v2_id, constants::v3_id)); } - SUBCASE("{in_/out_/}degree should return the number of edges incident {to/from/with} 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); + SUBCASE("has_edge(edge_ptr) should return true if the given edge is present in the graph") { + const auto valid_edge = add_edge(constants::v1_id, constants::v2_id); + CHECK(sut.has_edge(valid_edge)); - CHECK(std::ranges::all_of( - constants::vertex_id_view, - [](const auto deg) { return deg == n_incident_edges_for_fully_connected_vertex; }, - deg_proj - )); + const edge_type invalid_edge{gl::invalid_id, constants::v1_id, constants::v2_id}; + CHECK_FALSE(sut.has_edge(invalid_edge)); - add_edge(constants::v1_id, constants::v1_id); + // edge connecting vertices not connected in the actual graph + const edge_type not_present_edge{valid_edge.id(), constants::v2_id, constants::v3_id}; + CHECK_FALSE(sut.has_edge(not_present_edge)); + } - CHECK_EQ( - deg_proj(constants::v1_id), - n_incident_edges_for_fully_connected_vertex + 2uz // loops counted twice - ); + SUBCASE("get_edge(id, id) should return nullopt if there is no edge connecting the given " + "vertices") { + CHECK_FALSE(sut.get_edge(constants::v1_id, constants::v2_id)); } - 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; + SUBCASE("get_edge(id, id) should return a valid edge if the given vertices are connected") { + const auto edge_1 = add_edge(constants::v1_id, constants::v2_id); - std::vector degree_map; + const auto edge_opt_1 = sut.get_edge(constants::v1_id, constants::v2_id); + REQUIRE(edge_opt_1.has_value()); + CHECK_EQ(*edge_opt_1, edge_1); - SUBCASE("in_degree") { - degree_map = sut.in_degree_map(); - } + const auto edge_opt_2 = sut.get_edge(constants::v2_id, constants::v1_id); + REQUIRE(edge_opt_2.has_value()); + CHECK_EQ(*edge_opt_2, edge_1); + } - SUBCASE("out_degree") { - degree_map = sut.out_degree_map(); - } + 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("degree") { - degree_map = sut.degree_map(); - } + REQUIRE_EQ(gl::util::range_size(incident_edges), 1uz); + CHECK_EQ(*std::ranges::begin(incident_edges), edge); + } - CAPTURE(degree_map); + 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}; - REQUIRE_EQ(degree_map.size(), constants::n_elements); - CHECK_EQ(std::ranges::count(degree_map, expected_deg), constants::n_elements); + 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)); } - 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); + // --- access operators --- - 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())); + 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); - // 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); } } 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..aa959a5 100644 --- a/tests/source/gl/test_graph.cpp +++ b/tests/source/gl/test_graph.cpp @@ -1,17 +1,19 @@ -#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 #include namespace gl_testing { @@ -61,7 +63,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); @@ -77,7 +79,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); @@ -89,10 +91,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; } )); @@ -101,7 +103,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; } @@ -113,7 +115,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 +124,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; @@ -147,13 +149,11 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp 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(); } )); } - // --- vertex method tests --- + // --- vertex modifiers --- SUBCASE("add_vertex should return a vertex_descriptor with an incremented id and no edges") { sut_type sut; @@ -163,7 +163,7 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp 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); @@ -211,43 +211,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); @@ -261,8 +224,7 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp 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(); @@ -271,9 +233,9 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp )); 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; } )); @@ -293,8 +255,7 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp 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(); @@ -303,9 +264,9 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp )); 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; } )); @@ -326,14 +287,10 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp 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 " @@ -351,19 +308,121 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp 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; + })); + } + + + // --- 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 )); } - // --- edge method tests --- + 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)); + } + + 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("edge method tests for default properties type") { + 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}; + + 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(); @@ -387,19 +446,19 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp 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); } } @@ -415,19 +474,19 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp 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); } } @@ -502,21 +561,21 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp 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") { @@ -549,7 +608,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}; @@ -579,19 +638,19 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp 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); } } @@ -614,19 +673,19 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp 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); } } @@ -635,21 +694,21 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp 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") { @@ -682,17 +741,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 +791,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 +829,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 +896,159 @@ 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_adjacent(vertex_id, vertex_id) should throw for out of range vertex ids") { + CHECK_THROWS_AS( + discard_result(sut.are_adjacent(constants::out_of_rng_idx, constants::v2_id)), + std::out_of_range + ); + CHECK_THROWS_AS( + discard_result(sut.are_adjacent(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_adjacent(vertex_id, vertex_id) should return true if there is an edge " + "connecting the given vertices") { + sut.add_edge(v1, v2); - SUBCASE("incident_edges(vertex) should throw if the vertex is invalid") { - sut_type sut{constants::n_elements}; + CHECK(sut.are_adjacent(constants::v1_id, constants::v2_id)); + CHECK(sut.are_adjacent(constants::v2_id, constants::v1_id)); - CHECK_THROWS_AS( - discard_result(sut.incident_edges(fixture.out_of_range_vertex)), std::out_of_range - ); - } + CHECK_FALSE(sut.are_adjacent(constants::v1_id, constants::v3_id)); + CHECK_FALSE(sut.are_adjacent(constants::v2_id, constants::v3_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); + // 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)); + } - CHECK_NOTHROW([&sut, &vertex]() { - CHECK_EQ(gl::util::range_size(sut.incident_edges(vertex)), 0uz); - }()); + SUBCASE("are_adjacent(vertex, vertex) should throw if at least one of the vertices is " + "invalid") { + CHECK_THROWS_AS( + discard_result( + sut.are_adjacent(fixture.out_of_range_vertex, fixture.out_of_range_vertex) + ), + std::out_of_range + ); + CHECK_THROWS_AS( + discard_result(sut.are_adjacent(fixture.out_of_range_vertex, v2)), std::out_of_range + ); + CHECK_THROWS_AS( + discard_result(sut.are_adjacent(v1, fixture.out_of_range_vertex)), std::out_of_range + ); + } + + 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_adjacent(v1, v2)); + CHECK(sut.are_adjacent(v2, v1)); + + CHECK_FALSE(sut.are_adjacent(v1, v3)); + CHECK_FALSE(sut.are_adjacent(v2, v3)); + + // 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") { + 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_adjacent(edge, invalid_edge)), std::invalid_argument + ); + CHECK_THROWS_AS( + discard_result(sut.are_adjacent(invalid_edge, edge)), std::invalid_argument + ); + } + + 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)); + CHECK_FALSE(sut.are_adjacent(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 +1109,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..36a2876 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 @@ -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_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..78ca426 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 @@ -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 691f0e2..213c821 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 @@ -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; 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") {