From 8b80a7ba79c0c06ffb6fa39df8d0bb6236e6868d Mon Sep 17 00:00:00 2001 From: Phil Ratzloff Date: Fri, 1 Sep 2023 17:22:49 -0400 Subject: [PATCH 01/15] interim checkin Add multi-partite support --- example/CppCon2022/germany_routes_example.cpp | 74 ++ include/graph/container/partite_graph.hpp | 995 ++++++++++++++++++ include/graph/detail/graph_cpo.hpp | 166 ++- 3 files changed, 1217 insertions(+), 18 deletions(-) create mode 100644 include/graph/container/partite_graph.hpp diff --git a/example/CppCon2022/germany_routes_example.cpp b/example/CppCon2022/germany_routes_example.cpp index 087bb03..4da9868 100644 --- a/example/CppCon2022/germany_routes_example.cpp +++ b/example/CppCon2022/germany_routes_example.cpp @@ -110,6 +110,80 @@ TEST_CASE("Germany Routes Presentation", "[presentation][germany][routes][shorte } } + using route_data = copyable_edge_t; // {source_id, target_id, value} + vector routes_doubled = { + {0, 1, 85.0}, {0, 4, 217.0}, {0, 6, 173.0}, // + {1, 0, 85.0}, {1, 2, 80.0}, // + {2, 1, 80.0}, {2, 3, 250.0}, // + {3, 2, 250.0}, {3, 8, 84.0}, // + {4, 0, 217.0}, {4, 5, 103.0}, {4, 7, 186.0}, // + {5, 4, 103.0}, {5, 8, 167.0}, {5, 9, 183.0}, // + {6, 0, 173.0}, {6, 8, 502.0}, // + {7, 4, 186.0}, // + {8, 3, 84.0}, {8, 5, 167.0}, {8, 6, 502.0}, // + {9, 5, 183.0}, + }; + + // Graph definition + struct route { // edge + city_id_type target_id = 0; + double distance = 0.0; // km + }; + using AdjList = vector>; // range of ranges + using G = rr_adaptor; // graph + + G g(city_names, routes_doubled); + + // Useful demo values + city_id_type frankfurt_id = 0; + vertex_reference_t frankfurt = *find_vertex(g, frankfurt_id); + + cout << "Traverse the vertices & outgoing edges" << endl; + for (auto&& [uid, u] : vertexlist(g)) { // [id,vertex&] + cout << city_id(g, uid) << endl; // city name [id] + for (auto&& [vid, uv] : incidence(g, uid)) { // [target_id,edge&] + cout << " --> " << city_id(g, vid) << endl; + // "--> "target city" [target_id] + } + } + + using route_data = copyable_edge_t; // {source_id, target_id, value} + vector routes_doubled = { + {0, 1, 85.0}, {0, 4, 217.0}, {0, 6, 173.0}, // + {1, 0, 85.0}, {1, 2, 80.0}, // + {2, 1, 80.0}, {2, 3, 250.0}, // + {3, 2, 250.0}, {3, 8, 84.0}, // + {4, 0, 217.0}, {4, 5, 103.0}, {4, 7, 186.0}, // + {5, 4, 103.0}, {5, 8, 167.0}, {5, 9, 183.0}, // + {6, 0, 173.0}, {6, 8, 502.0}, // + {7, 4, 186.0}, // + {8, 3, 84.0}, {8, 5, 167.0}, {8, 6, 502.0}, // + {9, 5, 183.0}, + }; + + // Graph definition + struct route { // edge + city_id_type target_id = 0; + double distance = 0.0; // km + }; + using AdjList = vector>; // range of ranges + using G = rr_adaptor; // graph + + G g(city_names, routes_doubled); + + // Useful demo values + city_id_type frankfurt_id = 0; + vertex_reference_t frankfurt = *find_vertex(g, frankfurt_id); + + cout << "Traverse the vertices & outgoing edges" << endl; + for (auto&& [uid, u] : vertexlist(g)) { // [id,vertex&] + cout << city_id(g, uid) << endl; // city name [id] + for (auto&& [vid, uv] : incidence(g, uid)) { // [target_id,edge&] + cout << " --> " << city_id(g, vid) << endl; + // "--> "target city" [target_id] + } + } + // Shortest Paths (segments) { auto weight_1 = [](edge_reference_t uv) -> int { return 1; }; diff --git a/include/graph/container/partite_graph.hpp b/include/graph/container/partite_graph.hpp new file mode 100644 index 0000000..480dfe9 --- /dev/null +++ b/include/graph/container/partite_graph.hpp @@ -0,0 +1,995 @@ +#pragma once + +#include "container_utility.hpp" +#include +#include +#include +#include +#include +#include "graph/graph.hpp" + +// PARTITE NOTES +// Allow n vertex types, where each type also identifies the allowed target types + +// NOTES +// have public load_edges(...), load_vertices(...), and load() +// allow separation of construction and load +// allow multiple calls to load edges as long as subsequent edges have uid >= last vertex (append) +// VId must be large enough for the total edges and the total vertices. + +// load_vertices(vrng, vproj) <- [uid,vval] +// load_edges(erng, eproj) <- [uid, vid, eval] +// load(erng, eproj, vrng, vproj): load_edges(erng,eproj), load_vertices(vrng,vproj) +// +// partite_graph(initializer_list<[uid,vid,eval]>) : load_edges(erng,eproj) +// partite_graph(erng, eproj) : load_edges(erng,eproj) +// partite_graph(erng, eproj, vrng, vproj): load(erng, eproj, vrng, vprog) +// +// [uid,vval] <-- copyable_vertex +// [uid,vid,eval] <-- copyable_edge +// +namespace std::graph::container { + +/** + * @ingroup graph_containers + * @brief Scans a range used for input for loading edges to determine the largest vertex id used. + * + * @tparam VId Vertex Id type. + * @tparam EV Edge value type. It may be void. + * @tparam ERng Edge data range type. + * @tparam EProj Edge Projection function type to convert from an @c ERng value type to a @c copyable_edge_t. + * If the @c ERng value type is already copyable_edge_t identity can be used. + * + * @param erng The edge data range type. + * @param eprojection The edge projection function that converts a @c ERng value type to a @c copyable_edge_t. + * If @c erng value type is already copyable_edge_t, identity() can be used. + * + * @return A @c pair with the max vertex id used and the number of edges scanned. +*/ +template +requires copyable_edge>, VId, EV> +constexpr auto max_vertex_id(const ERng& erng, const EProj& eprojection) { + size_t edge_count = 0; + VId max_id = 0; + for (auto&& edge_data : erng) { + const copyable_edge_t& uv = eprojection(edge_data); + max_id = max(max_id, max(static_cast(uv.source_id), static_cast(uv.target_id))); + ++edge_count; + } + return pair(max_id, edge_count); +} + + +// +// forward declarations +// +template > // for internal containers +class partite_graph; + +/** + * @ingroup graph_containers + * @brief Wrapper struct for the row index to distinguish it from a vertex_id_type (VId). + * + * @tparam EIndex The type for storing an edge index. It must be able to store a value of |E|+1, + * where |E| is the total number of edges in the graph. +*/ +template +struct partite_row { + using edge_index_type = EIndex; + edge_index_type index = 0; +}; + +/** + * @ingroup graph_containers + * @brief Wrapper struct for the col (edge) index to distinguish it from a vertex_id_type (VId). + * + * @tparam VId Vertex id type. The type must be able to store a value of |V|+1, where |V| is the + * number of vertices in the graph. +*/ +template +struct partite_col { + using vertex_id_type = VId; + vertex_id_type index = 0; +}; + + +/** + * @ingroup graph_containers + * @brief Holds vertex values in a vector that is the same size as @c row_index_. + * + * If @c is_void_v then a specialization class is defined that is empty with a single + * constructor that accepts (and ignores) an allocator. + * + * @tparam EV The edge value type. If "void" is used no user value is stored on the edge and + * calls to @c edge_value(g,uv) will generate a compile error. + * @tparam VV The vertex value type. If "void" is used no user value is stored on the vertex + * and calls to @c vertex_value(g,u) will generate a compile error. + * @tparam GV The graph value type. If "void" is used no user value is stored on the graph + * and calls to @c graph_value(g) will generate a compile error. + * @tparam VId Vertex id type. The type must be able to store a value of |V|+1, where |V| is the + * number of vertices in the graph. + * @tparam EIndex The type for storing an edge index. It must be able to store a value of |E|+1, + * where |E| is the total number of edges in the graph. + * @tparam Alloc The allocator type. +*/ +template +class partite_row_values { + using row_type = partite_row; // index into col_index_ + using row_allocator_type = typename allocator_traits::template rebind_alloc; + using row_index_vector = vector; + +public: + using graph_type = partite_graph; + using vertex_type = row_type; + using vertex_value_type = VV; + using allocator_type = typename allocator_traits::template rebind_alloc; + using vector_type = vector; + + using value_type = VV; + using size_type = size_t; //VId; + using difference_type = typename vector_type::difference_type; + using reference = value_type&; + using const_reference = const value_type&; + using pointer = typename vector_type::pointer; + using const_pointer = typename vector_type::const_pointer; + using iterator = typename vector_type::iterator; + using const_iterator = typename vector_type::const_iterator; + + constexpr partite_row_values(const Alloc& alloc) : v_(alloc) {} + + constexpr partite_row_values() = default; + constexpr partite_row_values(const partite_row_values&) = default; + constexpr partite_row_values(partite_row_values&&) = default; + constexpr ~partite_row_values() = default; + + constexpr partite_row_values& operator=(const partite_row_values&) = default; + constexpr partite_row_values& operator=(partite_row_values&&) = default; + + +public: // Properties + [[nodiscard]] constexpr size_type size() const noexcept { return static_cast(v_.size()); } + [[nodiscard]] constexpr bool empty() const noexcept { return v_.empty(); } + [[nodiscard]] constexpr size_type capacity() const noexcept { return static_cast(v_.size()); } + +public: // Operations + constexpr void reserve(size_type new_cap) { v_.reserve(new_cap); } + constexpr void resize(size_type new_size) { v_.resize(new_size); } + + constexpr void clear() noexcept { v_.clear(); } + constexpr void push_back(const value_type& value) { v_.push_back(value); } + constexpr void emplace_back(value_type&& value) { v_.emplace_back(forward(value)); } + + constexpr void swap(partite_row_values& other) noexcept { swap(v_, other.v_); } + + template + //requires views::copyable_vertex>, VId, VV> + constexpr void load_row_values(const VRng& vrng, VProj projection, size_type vertex_count) { + if constexpr (ranges::sized_range) + vertex_count = max(vertex_count, ranges::size(vrng)); + resize(ranges::size(vrng)); + + for (auto&& vtx : vrng) { + const auto& [id, value] = projection(vtx); + + // if an unsized vrng is passed, the caller is responsible to call + // resize_vertices(n) with enough entries for all the values. + assert(static_cast(id) < size()); + + (*this)[static_cast(id)] = value; + } + } + + template + //requires views::copyable_vertex>, VId, VV> + constexpr void load_row_values(VRng&& vrng, VProj projection, size_type vertex_count) { + if constexpr (ranges::sized_range) + vertex_count = max(vertex_count, ranges::size(vrng)); + resize(ranges::size(vrng)); + + for (auto&& vtx : vrng) { + auto&& [id, value] = projection(vtx); + + // if an unsized vrng is passed, the caller is responsible to call + // resize_vertices(n) with enough entries for all the values. + assert(static_cast(id) < size()); + + (*this)[id] = std::move(value); + } + } + +public: + constexpr reference operator[](size_type pos) { return v_[pos]; } + constexpr const_reference operator[](size_type pos) const { return v_[pos]; } + +private: + friend constexpr vertex_value_type& + tag_invoke(::std::graph::tag_invoke::vertex_value_fn_t, graph_type& g, vertex_type& u) { + static_assert(ranges::contiguous_range, "row_index_ must be a contiguous range to evaluate uidx"); + auto uidx = g.index_of(u); + partite_row_values& row_vals = g; + return row_vals.v_[uidx]; + } + friend constexpr const vertex_value_type& + tag_invoke(::std::graph::tag_invoke::vertex_value_fn_t, const graph_type& g, const vertex_type& u) { + static_assert(ranges::contiguous_range, "row_index_ must be a contiguous range to evaluate uidx"); + auto uidx = g.index_of(u); + const partite_row_values& row_vals = g; + return row_vals.v_[uidx]; + } + +private: + vector_type v_; +}; + +template +class partite_row_values { +public: + constexpr partite_row_values(const Alloc& alloc) {} + constexpr partite_row_values() = default; + + using value_type = void; + using size_type = size_t; //VId; + +public: // Properties + [[nodiscard]] constexpr size_type size() const noexcept { return 0; } + [[nodiscard]] constexpr bool empty() const noexcept { return true; } + [[nodiscard]] constexpr size_type capacity() const noexcept { return 0; } + +public: // Operations + constexpr void reserve(size_type new_cap) {} + constexpr void resize(size_type new_size) {} + + constexpr void clear() noexcept {} + constexpr void swap(partite_row_values& other) noexcept {} +}; + + +/** + * @ingroup graph_containers + * @brief Class to hold vertex values in a vector that is the same size as col_index_. + * + * If is_void_v then the class is empty with a single + * constructor that accepts (and ignores) an allocator. + * + * @tparam EV The edge value type. If "void" is used no user value is stored on the edge and + * calls to @c edge_value(g,uv) will generate a compile error. + * @tparam VV The vertex value type. If "void" is used no user value is stored on the vertex + * and calls to @c vertex_value(g,u) will generate a compile error. + * @tparam GV The graph value type. If "void" is used no user value is stored on the graph + * and calls to @c graph_value(g) will generate a compile error. + * @tparam VId Vertex id type. The type must be able to store a value of |V|+1, where |V| is the + * number of vertices in the graph. + * @tparam EIndex The type for storing an edge index. It must be able to store a value of |E|+1, + * where |E| is the total number of edges in the graph. + * @tparam Alloc The allocator type. +*/ +template +class partite_col_values { + using col_type = partite_col; // target_id + using col_allocator_type = typename allocator_traits::template rebind_alloc; + using col_index_vector = vector; + +public: + using graph_type = partite_graph; + using edge_type = col_type; // index into v_ + using edge_value_type = EV; + using allocator_type = typename allocator_traits::template rebind_alloc; + using vector_type = vector; + + using value_type = EV; + using size_type = size_t; //VId; + using difference_type = typename vector_type::difference_type; + using reference = value_type&; + using const_reference = const value_type&; + using pointer = typename vector_type::pointer; + using const_pointer = typename vector_type::const_pointer; + using iterator = typename vector_type::iterator; + using const_iterator = typename vector_type::const_iterator; + + constexpr partite_col_values(const Alloc& alloc) : v_(alloc) {} + + constexpr partite_col_values() = default; + constexpr partite_col_values(const partite_col_values&) = default; + constexpr partite_col_values(partite_col_values&&) = default; + constexpr ~partite_col_values() = default; + + constexpr partite_col_values& operator=(const partite_col_values&) = default; + constexpr partite_col_values& operator=(partite_col_values&&) = default; + + +public: // Properties + [[nodiscard]] constexpr size_type size() const noexcept { return static_cast(v_.size()); } + [[nodiscard]] constexpr bool empty() const noexcept { return v_.empty(); } + [[nodiscard]] constexpr size_type capacity() const noexcept { return static_cast(v_.size()); } + +public: // Operations + constexpr void reserve(size_type new_cap) { v_.reserve(new_cap); } + constexpr void resize(size_type new_size) { v_.resize(new_size); } + + constexpr void clear() noexcept { v_.clear(); } + constexpr void push_back(const value_type& value) { v_.push_back(value); } + constexpr void emplace_back(value_type&& value) { v_.emplace_back(forward(value)); } + + constexpr void swap(partite_col_values& other) noexcept { swap(v_, other.v_); } + +public: + constexpr reference operator[](size_type pos) { return v_[pos]; } + constexpr const_reference operator[](size_type pos) const { return v_[pos]; } + +private: + // edge_value(g,uv) + friend constexpr edge_value_type& + tag_invoke(::std::graph::tag_invoke::edge_value_fn_t, graph_type& g, edge_type& uv) { + auto uv_idx = g.index_of(uv); + partite_col_values& col_vals = g; + return col_vals.v_[uv_idx]; + } + friend constexpr const edge_value_type& + tag_invoke(::std::graph::tag_invoke::edge_value_fn_t, const graph_type& g, const edge_type& uv) { + auto uv_idx = g.index_of(uv); + const partite_col_values& col_vals = g; + return col_vals.v_[uv_idx]; + } + +private: + vector_type v_; +}; + +template +class partite_col_values { +public: + constexpr partite_col_values(const Alloc& alloc) {} + constexpr partite_col_values() = default; + + using value_type = void; + using size_type = size_t; //VId; + +public: // Properties + [[nodiscard]] constexpr size_type size() const noexcept { return 0; } + [[nodiscard]] constexpr bool empty() const noexcept { return true; } + [[nodiscard]] constexpr size_type capacity() const noexcept { return 0; } + +public: // Operations + constexpr void reserve(size_type new_cap) {} + constexpr void resize(size_type new_size) {} + + constexpr void clear() noexcept {} + constexpr void swap(partite_col_values& other) noexcept {} +}; + + +/** + * @ingroup graph_containers + * @brief Base class for compressed sparse row adjacency graph + * + * @tparam EV The edge value type. If "void" is used no user value is stored on the edge and + * calls to @c edge_value(g,uv) will generate a compile error. + * @tparam VV The vertex value type. If "void" is used no user value is stored on the vertex + * and calls to @c vertex_value(g,u) will generate a compile error. + * @tparam GV The graph value type. If "void" is used no user value is stored on the graph + * and calls to @c graph_value(g) will generate a compile error. + * @tparam VId Vertex id type. The type must be able to store a value of |V|+1, where |V| is the + * number of vertices in the graph. + * @tparam EIndex The type for storing an edge index. It must be able to store a value of |E|+1, + * where |E| is the total number of edges in the graph. + * @tparam Alloc The allocator type. +*/ +template +class partite_graph_base + : public partite_row_values + , public partite_col_values { + using row_values_base = partite_row_values; + using col_values_base = partite_col_values; + + using row_type = partite_row; // index into col_index_ + using row_allocator_type = typename allocator_traits::template rebind_alloc; + using row_index_vector = vector; + + using col_type = partite_col; // target_id + using col_allocator_type = typename allocator_traits::template rebind_alloc; + using col_index_vector = vector; + +public: // Types + using graph_type = partite_graph_base; + + using vertex_id_type = VId; + using vertex_type = row_type; + using vertex_value_type = VV; + using vertices_type = ranges::subrange>; + using const_vertices_type = ranges::subrange>; + + using edge_type = col_type; // index into v_ + using edge_value_type = EV; + using edge_index_type = EIndex; + using edges_type = ranges::subrange>; + using const_edges_type = ranges::subrange>; + + using const_iterator = typename row_index_vector::const_iterator; + using iterator = typename row_index_vector::iterator; + + using size_type = ranges::range_size_t; + +public: // Construction/Destruction + constexpr partite_graph_base() = default; + constexpr partite_graph_base(const partite_graph_base&) = default; + constexpr partite_graph_base(partite_graph_base&&) = default; + constexpr ~partite_graph_base() = default; + + constexpr partite_graph_base& operator=(const partite_graph_base&) = default; + constexpr partite_graph_base& operator=(partite_graph_base&&) = default; + + constexpr partite_graph_base(const Alloc& alloc) + : row_values_base(alloc), col_values_base(alloc), row_index_(alloc), col_index_(alloc) {} + + /** + * @brief Constructor that takes a edge range to create the CSR graph. + * + * Edges must be ordered by source_id (enforced by asssertion). + * + * @tparam ERng Edge range type + * @tparam EProj Edge projection function type + * + * @param erng The input range of edges + * @param eprojection Projection function that creates a @c copyable_edge_t from an erng value + * @param alloc Allocator to use for internal containers + */ + template + requires copyable_edge>, VId, EV> + constexpr partite_graph_base(const ERng& erng, EProj eprojection = {}, const Alloc& alloc = Alloc()) + : row_values_base(alloc), col_values_base(alloc), row_index_(alloc), col_index_(alloc) { + + load_edges(erng, eprojection); + } + + /** + * @brief Constructor that takes edge range and vertex range to create the CSR graph. + * + * Edges must be ordered by source_id (enforced by asssertion). + * + * @tparam ERng Edge Range type + * @tparam VRng Vetex range type + * @tparam EProj Edge projection function type + * @tparam VProj Vertex projection function type + * + * @param erng The input range of edges + * @param vrng The input range of vertices + * @param eprojection Projection function that creates a @c copyable_edge_t from an @c erng value + * @param vprojection Projection function that creates a @c copyable_vertex_t from a @c vrng value + * @param alloc Allocator to use for internal containers + */ + template + //requires copyable_edge>, VId, EV> && + // copyable_vertex>, VId, VV> + constexpr partite_graph_base(const ERng& erng, + const VRng& vrng, + EProj eprojection = {}, // eproj(eval) -> {source_id,target_id [,value]} + VProj vprojection = {}, // vproj(vval) -> {target_id [,value]} + const Alloc& alloc = Alloc()) + : row_values_base(alloc), col_values_base(alloc), row_index_(alloc), col_index_(alloc) { + + load(erng, vrng, eprojection, vprojection); + } + + /** + * @brief Constructor for easy creation of a graph that takes an initializer list + * of @c copyable_edge_t -> [source_id, target_id, value]. + * + * @param ilist Initializer list of @c copyable_edge_t -> [source_id, target_id, value] + * @param alloc Allocator to use for internal containers + */ + constexpr partite_graph_base(const initializer_list>& ilist, const Alloc& alloc = Alloc()) + : row_values_base(alloc), col_values_base(alloc), row_index_(alloc), col_index_(alloc) { + load_edges(ilist, identity()); + } + +public: +public: // Operations + void reserve_vertices(size_type count) { + row_index_.reserve(count + 1); // +1 for terminating row + row_values_base::reserve(count); + } + void reserve_edges(size_type count) { + col_index_.reserve(count); + static_cast(*this).reserve(count); + } + + void resize_vertices(size_type count) { + row_index_.resize(count + 1); // +1 for terminating row + row_values_resize(count); + } + void resize_edges(size_type count) { + col_index_.reserve(count); + static_cast(*this).reserve(count); + } + + /** + * @brief Load vertex values, callable either before or after @c load_edges(erng,eproj). + * + * If @c load_edges(vrng,vproj) has been called before this, the @c row_values_ vector will be + * extended to match the number of @c row_index_.size()-1 to avoid out-of-bounds errors when + * accessing vertex values. + * + * @tparam VRng Vertex range type + * @tparam VProj Vertex projection function type + * + * @param vrng Range of values to load for vertices. The order of the values is + * preserved in the internal vector. + * @param vprojection Projection function for @c vrng values + */ + template + //requires views::copyable_vertex>, VId, VV> + constexpr void load_vertices(const VRng& vrng, VProj vprojection, size_type vertex_count = 0) { + row_values_base::load_row_values(vrng, vprojection, max(vertex_count, ranges::size(vrng))); + } + + /** + * Load vertex values, callable either before or after @c load_edges(erng,eproj). + * + * If @c load_edges(vrng,vproj) has been called before this, the @c row_values_ vector will be + * extended to match the number of @c row_index_.size()-1 to avoid out-of-bounds errors when + * accessing vertex values. + * + * @tparam VRng Vertex range type + * @tparam VProj Projection function type + * + * @param vrng Range of values to load for vertices. The order of the values is preserved in the internal vector. + * @param vprojection Projection function for @c vrng values + */ + template + //requires views::copyable_vertex>, VId, VV> + constexpr void load_vertices(VRng& vrng, VProj vprojection = {}, size_type vertex_count = 0) { + row_values_base::load_row_values(vrng, vprojection, max(vertex_count, ranges::size(vrng))); + } + + /** + * @brief Load the edges for the graph, callable either before or after @c load_vertices(erng,eproj). + * + * @c erng must be ordered by source_id (copyable_edge_t) and is enforced by assertion. target_id + * can be unordered within a source_id. + * + * If @c erng is bi-directional, the source_id in the last entry is used to determine the maximum + * number of rows and is used to reserve space in the internal row_index and row_value vectors. + * If @c erng is an input_range or forward_range that evaluation can't be done and the internal + * row_index vector is grown and resized normally as needed (the row_value vector is updated by + * @c load_vertices(vrng,vproj)). If the caller knows the number of rows/vertices, they can call + * @c reserve_vertices(n) to reserve the space. + * + * If @c erng is a sized_range, @c size(erng) is used to reserve space for the internal col_index and + * v vectors. If it isn't a sized range, the vectors will be grown and resized normally as needed + * as new indexes and values are added. If the caller knows the number of columns/edges, they + * can call @c reserve_edges(n) to reserve the space. + * + * If row indexes have been referenced in the edges but there are no edges defined for them + * (with source_id), rows will be added to fill out the row_index vector to avoid out-of-bounds + * references. + * + * If @c load_vertices(vrng,vproj) has been called before this, the row_values_ vector will be + * extended to match the number of @c row_index_.size()-1 to avoid out-of-bounds errors when + * accessing vertex values. + * + * @todo @c ERng not a forward_range because CSV reader doesn't conform to be a forward_range + * + * @tparam ERng Edge range type + * @tparam EProj Edge projection function type + * + * @param erng Input range for edges + * @param eprojection Edge projection function that returns a @ copyable_edge_t for an element in @c erng + */ + template + //requires views::copyable_edge>, VId, EV> + constexpr void load_edges(ERng&& erng, EProj eprojection = {}, size_type vertex_count = 0, size_type edge_count = 0) { + // should only be loading into an empty graph + assert(row_index_.empty() && col_index_.empty() && static_cast(*this).empty()); + + // Nothing to do? + if (ranges::begin(erng) == ranges::end(erng)) { + return; + } + + // We can get the last vertex id from the list because erng is required to be ordered by + // the source id. It's possible a target_id could have a larger id also, which is taken + // care of at the end of this function. + vertex_count = std::max(vertex_count, + static_cast(last_erng_id(erng, eprojection) + 1)); // +1 for zero-based index + reserve_vertices(vertex_count); + + // Eval number of input rows and reserve space for the edges, if possible + if constexpr (ranges::sized_range) + edge_count = max(edge_count, ranges::size(erng)); + reserve_edges(edge_count); + + // Add edges + vertex_id_type last_uid = 0, max_vid = 0; + for (auto&& edge_data : erng) { + auto&& edge = eprojection(edge_data); // partite_graph requires EV!=void + assert(edge.source_id >= last_uid); // ordered by uid? (requirement) + row_index_.resize(static_cast(edge.source_id) + 1, + vertex_type{static_cast(static_cast(*this).size())}); + col_index_.push_back(edge_type{edge.target_id}); + if (!is_void_v) + static_cast(*this).emplace_back(std::move(edge.value)); + last_uid = edge.source_id; + max_vid = max(max_vid, edge.target_id); + } + + // uid and vid may refer to rows that exceed the value evaluated for vertex_count (if any) + vertex_count = max(vertex_count, max(row_index_.size(), static_cast(max_vid + 1))); + + // add any rows that haven't been added yet, and (+1) terminating row + row_index_.resize(vertex_count + 1, + vertex_type{static_cast(static_cast(*this).size())}); + + // If load_vertices(vrng,vproj) has been called but it doesn't have enough values for all + // the vertices then we extend the size to remove possibility of out-of-bounds occuring when + // getting a value for a row. + if (row_values_base::size() > 1 && row_values_base::size() < vertex_count) + row_values_base::resize(vertex_count); + } + + // The only diff with this and ERng&& is v_.push_back vs. v_.emplace_back + template + //requires views::copyable_edge>, VId, EV> + constexpr void + load_edges(const ERng& erng, EProj eprojection = {}, size_type vertex_count = 0, size_type edge_count = 0) { + // should only be loading into an empty graph + assert(row_index_.empty() && col_index_.empty() && static_cast(*this).empty()); + + // Nothing to do? + if (ranges::begin(erng) == ranges::end(erng)) { + return; + } + + // We can get the last vertex id from the list because erng is required to be ordered by + // the source id. It's possible a target_id could have a larger id also, which is taken + // care of at the end of this function. + vertex_count = std::max(vertex_count, + static_cast(last_erng_id(erng, eprojection) + 1)); // +1 for zero-based index + reserve_vertices(vertex_count); + + // Eval number of input rows and reserve space for the edges, if possible + if constexpr (ranges::sized_range) + edge_count = max(edge_count, ranges::size(erng)); + reserve_edges(edge_count); + + // Add edges + vertex_id_type last_uid = 0, max_vid = 0; + for (auto&& edge_data : erng) { + auto&& edge = eprojection(edge_data); // partite_graph requires EV!=void + assert(edge.source_id >= last_uid); // ordered by uid? (requirement) + row_index_.resize(static_cast(edge.source_id) + 1, + vertex_type{static_cast(static_cast(*this).size())}); + col_index_.push_back(edge_type{edge.target_id}); + if constexpr (!is_void_v) + static_cast(*this).push_back(edge.value); + last_uid = edge.source_id; + max_vid = max(max_vid, edge.target_id); + } + + // uid and vid may refer to rows that exceed the value evaluated for vertex_count (if any) + vertex_count = max(vertex_count, max(row_index_.size(), static_cast(max_vid + 1))); + + // add any rows that haven't been added yet, and (+1) terminating row + row_index_.resize(vertex_count + 1, + vertex_type{static_cast(static_cast(*this).size())}); + + // If load_vertices(vrng,vproj) has been called but it doesn't have enough values for all + // the vertices then we extend the size to remove possibility of out-of-bounds occuring when + // getting a value for a row. + if (row_values_base::size() > 0 && row_values_base::size() < vertex_count) + row_values_base::resize(vertex_count); + } + + /** + * @brief Load edges and then vertices for the graph. + * + * See @c load_edges() and @c load_vertices() for more information. + * + * @tparam EProj Edge Projection Function type + * @tparam VProj Vertex Projectiong Function type + * + * @param erng Input edge range + * @param vrng Input vertex range + * @param eprojection Edge projection function object + * @param vprojection Vertex projection function object + */ + template + //requires views::copyable_edge>, VId, EV> && + // views::copyable_vertex>, VId, VV> + constexpr void load(const ERng& erng, const VRng& vrng, EProj eprojection = {}, VProj vprojection = {}) { + load_edges(erng, eprojection); + load_vertices(vrng, vprojection); // load the values + } + +protected: + template + constexpr vertex_id_type last_erng_id(ERng&& erng, EProj eprojection) const { + vertex_id_type last_id = vertex_id_type(); + if constexpr (ranges::bidirectional_range) { + if (ranges::begin(erng) != ranges::end(erng)) { + auto lastIt = ranges::end(erng); + --lastIt; + auto&& e = eprojection(*lastIt); // copyable_edge + last_id = max(e.source_id, e.target_id); + } + } + return last_id; + } + +public: // Operations + constexpr ranges::iterator_t find_vertex(vertex_id_type id) noexcept { + return row_index_.begin() + id; + } + constexpr ranges::iterator_t find_vertex(vertex_id_type id) const noexcept { + return row_index_.begin() + id; + } + + constexpr edge_index_type index_of(const row_type& u) const noexcept { + return static_cast(&u - row_index_.data()); + } + constexpr vertex_id_type index_of(const col_type& v) const noexcept { + return static_cast(&v - col_index_.data()); + } + +public: // Operators + constexpr vertex_type& operator[](vertex_id_type id) noexcept { return row_index_[id]; } + constexpr const vertex_type& operator[](vertex_id_type id) const noexcept { return row_index_[id]; } + +private: // Member variables + row_index_vector row_index_; // starting index into col_index_ and v_; holds +1 extra terminating row + col_index_vector col_index_; // col_index_[n] holds the column index (aka target) + //v_vector_type v_; // v_[n] holds the edge value for col_index_[n] + //row_values_type row_value_; // row_value_[r] holds the value for row_index_[r], for VV!=void + +private: // tag_invoke properties + friend constexpr vertices_type tag_invoke(::std::graph::tag_invoke::vertices_fn_t, partite_graph_base& g) { + if (g.row_index_.empty()) + return vertices_type(g.row_index_); // really empty + else + return vertices_type(g.row_index_.begin(), g.row_index_.end() - 1); // don't include terminating row + } + friend constexpr const_vertices_type tag_invoke(::std::graph::tag_invoke::vertices_fn_t, + const partite_graph_base& g) { + if (g.row_index_.empty()) + return const_vertices_type(g.row_index_); // really empty + else + return const_vertices_type(g.row_index_.begin(), g.row_index_.end() - 1); // don't include terminating row + } + + friend vertex_id_type + tag_invoke(::std::graph::tag_invoke::vertex_id_fn_t, const partite_graph_base& g, const_iterator ui) { + return static_cast(ui - g.row_index_.begin()); + } + + friend constexpr edges_type tag_invoke(::std::graph::tag_invoke::edges_fn_t, graph_type& g, vertex_type& u) { + static_assert(ranges::contiguous_range, "row_index_ must be a contiguous range to get next row"); + vertex_type* u2 = &u + 1; + assert(static_cast(u2 - &u) < g.row_index_.size()); // in row_index_ bounds? + assert(static_cast(u.index) <= g.col_index_.size() && + static_cast(u2->index) <= g.col_index_.size()); // in col_index_ bounds? + return edges_type(g.col_index_.begin() + u.index, g.col_index_.begin() + u2->index); + } + friend constexpr const_edges_type + tag_invoke(::std::graph::tag_invoke::edges_fn_t, const graph_type& g, const vertex_type& u) { + static_assert(ranges::contiguous_range, "row_index_ must be a contiguous range to get next row"); + const vertex_type* u2 = &u + 1; + assert(static_cast(u2 - &u) < g.row_index_.size()); // in row_index_ bounds? + assert(static_cast(u.index) <= g.col_index_.size() && + static_cast(u2->index) <= g.col_index_.size()); // in col_index_ bounds? + return const_edges_type(g.col_index_.begin() + u.index, g.col_index_.begin() + u2->index); + } + + friend constexpr edges_type + tag_invoke(::std::graph::tag_invoke::edges_fn_t, graph_type& g, const vertex_id_type uid) { + assert(static_cast(uid + 1) < g.row_index_.size()); // in row_index_ bounds? + assert(static_cast(g.row_index_[uid + 1].index) <= g.col_index_.size()); // in col_index_ bounds? + return edges_type(g.col_index_.begin() + g.row_index_[uid].index, + g.col_index_.begin() + g.row_index_[uid + 1].index); + } + friend constexpr const_edges_type + tag_invoke(::std::graph::tag_invoke::edges_fn_t, const graph_type& g, const vertex_id_type uid) { + assert(static_cast(uid + 1) < g.row_index_.size()); // in row_index_ bounds? + assert(static_cast(g.row_index_[uid + 1].index) <= g.col_index_.size()); // in col_index_ bounds? + return const_edges_type(g.col_index_.begin() + g.row_index_[uid].index, + g.col_index_.begin() + g.row_index_[uid + 1].index); + } + + + // target_id(g,uv), target(g,uv) + friend constexpr vertex_id_type + tag_invoke(::std::graph::tag_invoke::target_id_fn_t, const graph_type& g, const edge_type& uv) noexcept { + return uv.index; + } + friend constexpr vertex_type& + tag_invoke(::std::graph::tag_invoke::target_fn_t, graph_type& g, edge_type& uv) noexcept { + return g.row_index_[uv.index]; + } + friend constexpr const vertex_type& + tag_invoke(::std::graph::tag_invoke::target_fn_t, const graph_type& g, const edge_type& uv) noexcept { + return g.row_index_[uv.index]; + } + + friend row_values_base; + friend col_values_base; +}; + + +/** + * @ingroup graph_containers + * @brief Compressed Sparse Row adjacency graph container. + * + * @tparam EV Edge value type + * @tparam VV Vertex value type + * @tparam GV Graph value type + * @tparam VI Vertex Id type. This must be large enough for the count of vertices. + * @tparam EIndex Edge Index type. This must be large enough for the count of edges. + * @tparam Alloc Allocator type +*/ +template +class partite_graph : public partite_graph_base { +public: // Types + using graph_type = partite_graph; + using base_type = partite_graph_base; + + using edge_value_type = EV; + using vertex_value_type = VV; + using graph_value_type = GV; + using value_type = GV; + + using vertex_id_type = VId; + +public: // Construction/Destruction + constexpr partite_graph() = default; + constexpr partite_graph(const partite_graph&) = default; + constexpr partite_graph(partite_graph&&) = default; + constexpr ~partite_graph() = default; + + constexpr partite_graph& operator=(const partite_graph&) = default; + constexpr partite_graph& operator=(partite_graph&&) = default; + + // partite_graph( alloc) + // partite_graph(gv&, alloc) + // partite_graph(gv&&, alloc) + + constexpr partite_graph(const Alloc& alloc) : base_type(alloc) {} + constexpr partite_graph(const graph_value_type& value, const Alloc& alloc = Alloc()) + : base_type(alloc), value_(value) {} + constexpr partite_graph(graph_value_type&& value, const Alloc& alloc = Alloc()) + : base_type(alloc), value_(move(value)) {} + + // partite_graph( erng, eprojection, alloc) + // partite_graph(gv&, erng, eprojection, alloc) + // partite_graph(gv&&, erng, eprojection, alloc) + + template + requires copyable_edge>, VId, EV> + constexpr partite_graph(const ERng& erng, EProj eprojection, const Alloc& alloc = Alloc()) + : base_type(erng, eprojection, alloc) {} + + template + requires copyable_edge>, VId, EV> + constexpr partite_graph(const graph_value_type& value, + const ERng& erng, + EProj eprojection, + const Alloc& alloc = Alloc()) + : base_type(erng, eprojection, alloc), value_(value) {} + + template + requires copyable_edge>, VId, EV> + constexpr partite_graph(graph_value_type&& value, const ERng& erng, EProj eprojection, const Alloc& alloc = Alloc()) + : base_type(erng, eprojection, alloc), value_(move(value)) {} + + // partite_graph( erng, vrng, eprojection, vprojection, alloc) + // partite_graph(gv&, erng, vrng, eprojection, vprojection, alloc) + // partite_graph(gv&&, erng, vrng, eprojection, vprojection, alloc) + + template + requires copyable_edge>, VId, EV> && + copyable_vertex>, VId, VV> + constexpr partite_graph(const ERng& erng, + const VRng& vrng, + EProj eprojection = {}, + VProj vprojection = {}, + const Alloc& alloc = Alloc()) + : base_type(erng, vrng, eprojection, vprojection, alloc) {} + + template + requires copyable_edge>, VId, EV> && + copyable_vertex>, VId, VV> + constexpr partite_graph(const graph_value_type& value, + const ERng& erng, + const VRng& vrng, + EProj eprojection = {}, + VProj vprojection = {}, + const Alloc& alloc = Alloc()) + : base_type(erng, vrng, eprojection, vprojection, alloc), value_(value) {} + + template + requires copyable_edge>, VId, EV> && + copyable_vertex>, VId, VV> + constexpr partite_graph(graph_value_type&& value, + const ERng& erng, + const VRng& vrng, + EProj eprojection = {}, + VProj vprojection = {}, + const Alloc& alloc = Alloc()) + : base_type(erng, vrng, eprojection, vprojection, alloc), value_(move(value)) {} + + + constexpr partite_graph(const initializer_list>& ilist, const Alloc& alloc = Alloc()) + : base_type(ilist, alloc) {} + +private: // tag_invoke properties + friend constexpr value_type& tag_invoke(::std::graph::tag_invoke::graph_value_fn_t, graph_type& g) { + return g.value_; + } + friend constexpr const value_type& tag_invoke(::std::graph::tag_invoke::graph_value_fn_t, const graph_type& g) { + return g.value_; + } + +private: // Member variables + graph_value_type value_ = graph_value_type(); +}; + +/** + * @ingroup graph_containers + * @brief Compressed Sparse Row adjacency graph container. + * + * @tparam EV Edge value type + * @tparam VV Vertex value type + * @tparam VI Vertex Id type. This must be large enough for the count of vertices. + * @tparam EIndex Edge Index type. This must be large enough for the count of edges. + * @tparam Alloc Allocator type +*/ +template +class partite_graph : public partite_graph_base { +public: // Types + using graph_type = partite_graph; + using base_type = partite_graph_base; + + using vertex_id_type = VId; + using vertex_value_type = VV; + + using graph_value_type = void; + using value_type = void; + +public: // Construction/Destruction + constexpr partite_graph() = default; + constexpr partite_graph(const partite_graph&) = default; + constexpr partite_graph(partite_graph&&) = default; + constexpr ~partite_graph() = default; + + constexpr partite_graph& operator=(const partite_graph&) = default; + constexpr partite_graph& operator=(partite_graph&&) = default; + + // edge-only construction + template + requires copyable_edge>, VId, EV> + constexpr partite_graph(const ERng& erng, EProj eprojection, const Alloc& alloc = Alloc()) + : base_type(erng, eprojection, alloc) {} + + // edge and vertex value construction + template + requires copyable_edge>, VId, EV> && + copyable_vertex>, VId, VV> + constexpr partite_graph(const ERng& erng, + const VRng& vrng, + EProj eprojection = {}, + VProj vprojection = {}, + const Alloc& alloc = Alloc()) + : base_type(erng, vrng, eprojection, vprojection, alloc) {} + + // initializer list using edge_descriptor + constexpr partite_graph(const initializer_list>& ilist, const Alloc& alloc = Alloc()) + : base_type(ilist, alloc) {} + + +public: // Operations +private: // tag_invoke properties +}; + +} // namespace std::graph::container diff --git a/include/graph/detail/graph_cpo.hpp b/include/graph/detail/graph_cpo.hpp index 485df28..f730691 100644 --- a/include/graph/detail/graph_cpo.hpp +++ b/include/graph/detail/graph_cpo.hpp @@ -781,35 +781,165 @@ namespace edgelist { using edge_value_t = decltype(edge_value(declval(), declval>())); } // namespace edgelist -# if 0 +# if 1 // bipartite idea -template , - class VV = tuple, - class GV = void, - integral VId = uint32_t, - class Alloc = allocator> -class csr_partite_graph; +//template , +// class VV = tuple, +// class GV = void, +// integral VId = uint32_t, +// class Alloc = allocator> +//class csr_partite_graph; -template -using partition_id_t = size_t; +// partition_id(g,uid) -> ? default = vertex_id_t() if not overridden; must be overridden for bipartite or multipartite graphs +// partition_id(g,u) default = partition_id(g,vertex_id(u)) +// +namespace tag_invoke { + TAG_INVOKE_DEF(partition_id); + + template + concept _has_partition_id_uid_adl = requires(G&& g, vertex_id_t uid) { + { partition_id(g, uid) }; + }; + + template + concept _has_partition_id_uref_adl = requires(G&& g, vertex_reference_t u) { + { partition_id(g, u) }; + }; +} // namespace tag_invoke + +/** + * @brief Get's the parition_id of a vertex_id. + * + * Complexity: O(1) + * + * Default implementation: 0; graph container must define when supported + * + * This is a customization point function that may be overriden if graph G supports bi-partite + * or multi-partite graphs. If it doesn't then a value of 0 is returned. + * + * @tparam G The graph type. + * @param g A graph instance. + * @param uid A vertex id for a vertex in graph G. + * @return The partition id of a vertex. 0 if G doesn't support partitioning. +*/ template -partition_id_t partition_id(G&& g, vertex_id_t uid); +requires tag_invoke::_has_partition_id_uid_adl +auto partition_id(G&& g, vertex_id_t uid) { + if constexpr (tag_invoke::_has_partition_id_uid_adl) + return tag_invoke::partition_id(g, uid); + else if constexpr (is_integral_v>) { + return vertex_id_t(); + } else + return size_t(0); +} template -size_t partition_size(G&& g); // number of partitions in the graph +using partition_id_t = decltype(partition(declval(), declval>())); -template -size_t partition_size(G&& g, partition_id_t p); // number of vertices in the partition +/** + * @brief Get's the parition_id of a vertex. + * + * Complexity: O(1) + * + * Default implementation: partition_id(g,vertex_id(g,u)) + * + * This is a customization point function that may be overriden if graph G supports bi-partite + * or multi-partite graphs. If it doesn't then a value of 0 is returned. + * + * @tparam G The graph type. + * @param g A graph instance. + * @param uid A vertex id for a vertex in graph G. + * @return The partition id of a vertex. 0 if G doesn't support partitioning. +*/ template -vertex_range_t vertices(G&& g, partition_id_t p); // overloaded with vertices(g) (all) +requires tag_invoke::_has_partition_id_uref_adl +auto partition_id(G&& g, vertex_reference_t u) { + if constexpr (tag_invoke::_has_partition_id_uref_adl) + return tag_invoke::partition_id(g, u); + else + return partition_id(vertex_id(g, u)); +} + -template -auto vertex_value(G&& g); +// partition_size(g) -> ? default = size_t(0) +// +namespace tag_invoke { + TAG_INVOKE_DEF(partition_size); + + template + concept _has_partition_size_adl = requires(G&& g) { + { partition_size(g) }; + }; +} // namespace tag_invoke + +/** + * @brief Get's the number of partitions in a graph. + * + * Complexity: O(1) + * + * Default implementation: 0; graph container must override if it supports bi-partite + * or multipartite graphs. + * + * This is a customization point function that may be overriden if graph G supports bi-partite + * or multi-partite graphs. If it doesn't then a value of 0 is returned. + * + * @tparam G The graph type. + * @param g A graph instance. + * @return The number of partitions in a graph. 0 if G doesn't support partitioning. +*/ +template +requires tag_invoke::_has_partition_size_adl +auto partition_size(G&& g) { + if constexpr (tag_invoke::_has_partition_size_adl) + return tag_invoke::partition_size(g); + else if constexpr (is_integral_v>) + return vertex_id_t(); + else + return size_t(0); +} + +// vertices(g,pid) -> range of vertices; graph container must override if it supports bi-partite or +// multi-partite graph. +// +namespace tag_invoke { + //TAG_INVOKE_DEF(vertices); // vertices(g) -> [graph vertices] (already defined for vertices(g)) + + template + concept _has_vertices_pid_adl = requires(G&& g, partition_id_t pid) { + { vertices(g, pid) }; + }; +} + +/** + * @brief Get's the range of vertices for a partition in a graph. + * + * Complexity: O(1) + * + * Default implementation: empty range of vertices; the type returned may not be the same + * as vertex_range_t. The graph container must override if it supports bi-partite + * or multipartite graphs. + * + * This is a customization point function that may be overriden if graph G supports bi-partite + * or multi-partite graphs. If it doesn't then an empty range is returned. + * + * @tparam G The graph type. + * @param g A graph instance. + * @return The number of partitions in a graph. 0 if G doesn't support partitioning. +*/ +template +requires tag_invoke::_has_vertices_pid_adl +auto vertices(G&& g, partition_id_t pid) { + if constexpr (tag_invoke::_has_vertices_pid_adl) + return tag_invoke::vertices(g, pid); + else + return subrange(end(vertices(g)), end(vertices(g))); +} + +template +using vertex_partition_range_t = decltype(vertices(declval(), declval())); -template -auto edge_value(G&& g); # endif From 1142ceb016ea7c11d373d958d5ea4c9ee81fdac5 Mon Sep 17 00:00:00 2001 From: Phil Ratzloff Date: Sat, 30 Sep 2023 12:47:47 -0400 Subject: [PATCH 02/15] Remove partite_graph in favor of extending csr_graph Back out addition to germany_routes_example.cpp to remove compile error (I don't remember why I added it) --- example/CppCon2022/germany_routes_example.cpp | 74 -- include/graph/container/partite_graph.hpp | 995 ------------------ include/graph/detail/graph_cpo.hpp | 2 +- 3 files changed, 1 insertion(+), 1070 deletions(-) delete mode 100644 include/graph/container/partite_graph.hpp diff --git a/example/CppCon2022/germany_routes_example.cpp b/example/CppCon2022/germany_routes_example.cpp index 4da9868..087bb03 100644 --- a/example/CppCon2022/germany_routes_example.cpp +++ b/example/CppCon2022/germany_routes_example.cpp @@ -110,80 +110,6 @@ TEST_CASE("Germany Routes Presentation", "[presentation][germany][routes][shorte } } - using route_data = copyable_edge_t; // {source_id, target_id, value} - vector routes_doubled = { - {0, 1, 85.0}, {0, 4, 217.0}, {0, 6, 173.0}, // - {1, 0, 85.0}, {1, 2, 80.0}, // - {2, 1, 80.0}, {2, 3, 250.0}, // - {3, 2, 250.0}, {3, 8, 84.0}, // - {4, 0, 217.0}, {4, 5, 103.0}, {4, 7, 186.0}, // - {5, 4, 103.0}, {5, 8, 167.0}, {5, 9, 183.0}, // - {6, 0, 173.0}, {6, 8, 502.0}, // - {7, 4, 186.0}, // - {8, 3, 84.0}, {8, 5, 167.0}, {8, 6, 502.0}, // - {9, 5, 183.0}, - }; - - // Graph definition - struct route { // edge - city_id_type target_id = 0; - double distance = 0.0; // km - }; - using AdjList = vector>; // range of ranges - using G = rr_adaptor; // graph - - G g(city_names, routes_doubled); - - // Useful demo values - city_id_type frankfurt_id = 0; - vertex_reference_t frankfurt = *find_vertex(g, frankfurt_id); - - cout << "Traverse the vertices & outgoing edges" << endl; - for (auto&& [uid, u] : vertexlist(g)) { // [id,vertex&] - cout << city_id(g, uid) << endl; // city name [id] - for (auto&& [vid, uv] : incidence(g, uid)) { // [target_id,edge&] - cout << " --> " << city_id(g, vid) << endl; - // "--> "target city" [target_id] - } - } - - using route_data = copyable_edge_t; // {source_id, target_id, value} - vector routes_doubled = { - {0, 1, 85.0}, {0, 4, 217.0}, {0, 6, 173.0}, // - {1, 0, 85.0}, {1, 2, 80.0}, // - {2, 1, 80.0}, {2, 3, 250.0}, // - {3, 2, 250.0}, {3, 8, 84.0}, // - {4, 0, 217.0}, {4, 5, 103.0}, {4, 7, 186.0}, // - {5, 4, 103.0}, {5, 8, 167.0}, {5, 9, 183.0}, // - {6, 0, 173.0}, {6, 8, 502.0}, // - {7, 4, 186.0}, // - {8, 3, 84.0}, {8, 5, 167.0}, {8, 6, 502.0}, // - {9, 5, 183.0}, - }; - - // Graph definition - struct route { // edge - city_id_type target_id = 0; - double distance = 0.0; // km - }; - using AdjList = vector>; // range of ranges - using G = rr_adaptor; // graph - - G g(city_names, routes_doubled); - - // Useful demo values - city_id_type frankfurt_id = 0; - vertex_reference_t frankfurt = *find_vertex(g, frankfurt_id); - - cout << "Traverse the vertices & outgoing edges" << endl; - for (auto&& [uid, u] : vertexlist(g)) { // [id,vertex&] - cout << city_id(g, uid) << endl; // city name [id] - for (auto&& [vid, uv] : incidence(g, uid)) { // [target_id,edge&] - cout << " --> " << city_id(g, vid) << endl; - // "--> "target city" [target_id] - } - } - // Shortest Paths (segments) { auto weight_1 = [](edge_reference_t uv) -> int { return 1; }; diff --git a/include/graph/container/partite_graph.hpp b/include/graph/container/partite_graph.hpp deleted file mode 100644 index 480dfe9..0000000 --- a/include/graph/container/partite_graph.hpp +++ /dev/null @@ -1,995 +0,0 @@ -#pragma once - -#include "container_utility.hpp" -#include -#include -#include -#include -#include -#include "graph/graph.hpp" - -// PARTITE NOTES -// Allow n vertex types, where each type also identifies the allowed target types - -// NOTES -// have public load_edges(...), load_vertices(...), and load() -// allow separation of construction and load -// allow multiple calls to load edges as long as subsequent edges have uid >= last vertex (append) -// VId must be large enough for the total edges and the total vertices. - -// load_vertices(vrng, vproj) <- [uid,vval] -// load_edges(erng, eproj) <- [uid, vid, eval] -// load(erng, eproj, vrng, vproj): load_edges(erng,eproj), load_vertices(vrng,vproj) -// -// partite_graph(initializer_list<[uid,vid,eval]>) : load_edges(erng,eproj) -// partite_graph(erng, eproj) : load_edges(erng,eproj) -// partite_graph(erng, eproj, vrng, vproj): load(erng, eproj, vrng, vprog) -// -// [uid,vval] <-- copyable_vertex -// [uid,vid,eval] <-- copyable_edge -// -namespace std::graph::container { - -/** - * @ingroup graph_containers - * @brief Scans a range used for input for loading edges to determine the largest vertex id used. - * - * @tparam VId Vertex Id type. - * @tparam EV Edge value type. It may be void. - * @tparam ERng Edge data range type. - * @tparam EProj Edge Projection function type to convert from an @c ERng value type to a @c copyable_edge_t. - * If the @c ERng value type is already copyable_edge_t identity can be used. - * - * @param erng The edge data range type. - * @param eprojection The edge projection function that converts a @c ERng value type to a @c copyable_edge_t. - * If @c erng value type is already copyable_edge_t, identity() can be used. - * - * @return A @c pair with the max vertex id used and the number of edges scanned. -*/ -template -requires copyable_edge>, VId, EV> -constexpr auto max_vertex_id(const ERng& erng, const EProj& eprojection) { - size_t edge_count = 0; - VId max_id = 0; - for (auto&& edge_data : erng) { - const copyable_edge_t& uv = eprojection(edge_data); - max_id = max(max_id, max(static_cast(uv.source_id), static_cast(uv.target_id))); - ++edge_count; - } - return pair(max_id, edge_count); -} - - -// -// forward declarations -// -template > // for internal containers -class partite_graph; - -/** - * @ingroup graph_containers - * @brief Wrapper struct for the row index to distinguish it from a vertex_id_type (VId). - * - * @tparam EIndex The type for storing an edge index. It must be able to store a value of |E|+1, - * where |E| is the total number of edges in the graph. -*/ -template -struct partite_row { - using edge_index_type = EIndex; - edge_index_type index = 0; -}; - -/** - * @ingroup graph_containers - * @brief Wrapper struct for the col (edge) index to distinguish it from a vertex_id_type (VId). - * - * @tparam VId Vertex id type. The type must be able to store a value of |V|+1, where |V| is the - * number of vertices in the graph. -*/ -template -struct partite_col { - using vertex_id_type = VId; - vertex_id_type index = 0; -}; - - -/** - * @ingroup graph_containers - * @brief Holds vertex values in a vector that is the same size as @c row_index_. - * - * If @c is_void_v then a specialization class is defined that is empty with a single - * constructor that accepts (and ignores) an allocator. - * - * @tparam EV The edge value type. If "void" is used no user value is stored on the edge and - * calls to @c edge_value(g,uv) will generate a compile error. - * @tparam VV The vertex value type. If "void" is used no user value is stored on the vertex - * and calls to @c vertex_value(g,u) will generate a compile error. - * @tparam GV The graph value type. If "void" is used no user value is stored on the graph - * and calls to @c graph_value(g) will generate a compile error. - * @tparam VId Vertex id type. The type must be able to store a value of |V|+1, where |V| is the - * number of vertices in the graph. - * @tparam EIndex The type for storing an edge index. It must be able to store a value of |E|+1, - * where |E| is the total number of edges in the graph. - * @tparam Alloc The allocator type. -*/ -template -class partite_row_values { - using row_type = partite_row; // index into col_index_ - using row_allocator_type = typename allocator_traits::template rebind_alloc; - using row_index_vector = vector; - -public: - using graph_type = partite_graph; - using vertex_type = row_type; - using vertex_value_type = VV; - using allocator_type = typename allocator_traits::template rebind_alloc; - using vector_type = vector; - - using value_type = VV; - using size_type = size_t; //VId; - using difference_type = typename vector_type::difference_type; - using reference = value_type&; - using const_reference = const value_type&; - using pointer = typename vector_type::pointer; - using const_pointer = typename vector_type::const_pointer; - using iterator = typename vector_type::iterator; - using const_iterator = typename vector_type::const_iterator; - - constexpr partite_row_values(const Alloc& alloc) : v_(alloc) {} - - constexpr partite_row_values() = default; - constexpr partite_row_values(const partite_row_values&) = default; - constexpr partite_row_values(partite_row_values&&) = default; - constexpr ~partite_row_values() = default; - - constexpr partite_row_values& operator=(const partite_row_values&) = default; - constexpr partite_row_values& operator=(partite_row_values&&) = default; - - -public: // Properties - [[nodiscard]] constexpr size_type size() const noexcept { return static_cast(v_.size()); } - [[nodiscard]] constexpr bool empty() const noexcept { return v_.empty(); } - [[nodiscard]] constexpr size_type capacity() const noexcept { return static_cast(v_.size()); } - -public: // Operations - constexpr void reserve(size_type new_cap) { v_.reserve(new_cap); } - constexpr void resize(size_type new_size) { v_.resize(new_size); } - - constexpr void clear() noexcept { v_.clear(); } - constexpr void push_back(const value_type& value) { v_.push_back(value); } - constexpr void emplace_back(value_type&& value) { v_.emplace_back(forward(value)); } - - constexpr void swap(partite_row_values& other) noexcept { swap(v_, other.v_); } - - template - //requires views::copyable_vertex>, VId, VV> - constexpr void load_row_values(const VRng& vrng, VProj projection, size_type vertex_count) { - if constexpr (ranges::sized_range) - vertex_count = max(vertex_count, ranges::size(vrng)); - resize(ranges::size(vrng)); - - for (auto&& vtx : vrng) { - const auto& [id, value] = projection(vtx); - - // if an unsized vrng is passed, the caller is responsible to call - // resize_vertices(n) with enough entries for all the values. - assert(static_cast(id) < size()); - - (*this)[static_cast(id)] = value; - } - } - - template - //requires views::copyable_vertex>, VId, VV> - constexpr void load_row_values(VRng&& vrng, VProj projection, size_type vertex_count) { - if constexpr (ranges::sized_range) - vertex_count = max(vertex_count, ranges::size(vrng)); - resize(ranges::size(vrng)); - - for (auto&& vtx : vrng) { - auto&& [id, value] = projection(vtx); - - // if an unsized vrng is passed, the caller is responsible to call - // resize_vertices(n) with enough entries for all the values. - assert(static_cast(id) < size()); - - (*this)[id] = std::move(value); - } - } - -public: - constexpr reference operator[](size_type pos) { return v_[pos]; } - constexpr const_reference operator[](size_type pos) const { return v_[pos]; } - -private: - friend constexpr vertex_value_type& - tag_invoke(::std::graph::tag_invoke::vertex_value_fn_t, graph_type& g, vertex_type& u) { - static_assert(ranges::contiguous_range, "row_index_ must be a contiguous range to evaluate uidx"); - auto uidx = g.index_of(u); - partite_row_values& row_vals = g; - return row_vals.v_[uidx]; - } - friend constexpr const vertex_value_type& - tag_invoke(::std::graph::tag_invoke::vertex_value_fn_t, const graph_type& g, const vertex_type& u) { - static_assert(ranges::contiguous_range, "row_index_ must be a contiguous range to evaluate uidx"); - auto uidx = g.index_of(u); - const partite_row_values& row_vals = g; - return row_vals.v_[uidx]; - } - -private: - vector_type v_; -}; - -template -class partite_row_values { -public: - constexpr partite_row_values(const Alloc& alloc) {} - constexpr partite_row_values() = default; - - using value_type = void; - using size_type = size_t; //VId; - -public: // Properties - [[nodiscard]] constexpr size_type size() const noexcept { return 0; } - [[nodiscard]] constexpr bool empty() const noexcept { return true; } - [[nodiscard]] constexpr size_type capacity() const noexcept { return 0; } - -public: // Operations - constexpr void reserve(size_type new_cap) {} - constexpr void resize(size_type new_size) {} - - constexpr void clear() noexcept {} - constexpr void swap(partite_row_values& other) noexcept {} -}; - - -/** - * @ingroup graph_containers - * @brief Class to hold vertex values in a vector that is the same size as col_index_. - * - * If is_void_v then the class is empty with a single - * constructor that accepts (and ignores) an allocator. - * - * @tparam EV The edge value type. If "void" is used no user value is stored on the edge and - * calls to @c edge_value(g,uv) will generate a compile error. - * @tparam VV The vertex value type. If "void" is used no user value is stored on the vertex - * and calls to @c vertex_value(g,u) will generate a compile error. - * @tparam GV The graph value type. If "void" is used no user value is stored on the graph - * and calls to @c graph_value(g) will generate a compile error. - * @tparam VId Vertex id type. The type must be able to store a value of |V|+1, where |V| is the - * number of vertices in the graph. - * @tparam EIndex The type for storing an edge index. It must be able to store a value of |E|+1, - * where |E| is the total number of edges in the graph. - * @tparam Alloc The allocator type. -*/ -template -class partite_col_values { - using col_type = partite_col; // target_id - using col_allocator_type = typename allocator_traits::template rebind_alloc; - using col_index_vector = vector; - -public: - using graph_type = partite_graph; - using edge_type = col_type; // index into v_ - using edge_value_type = EV; - using allocator_type = typename allocator_traits::template rebind_alloc; - using vector_type = vector; - - using value_type = EV; - using size_type = size_t; //VId; - using difference_type = typename vector_type::difference_type; - using reference = value_type&; - using const_reference = const value_type&; - using pointer = typename vector_type::pointer; - using const_pointer = typename vector_type::const_pointer; - using iterator = typename vector_type::iterator; - using const_iterator = typename vector_type::const_iterator; - - constexpr partite_col_values(const Alloc& alloc) : v_(alloc) {} - - constexpr partite_col_values() = default; - constexpr partite_col_values(const partite_col_values&) = default; - constexpr partite_col_values(partite_col_values&&) = default; - constexpr ~partite_col_values() = default; - - constexpr partite_col_values& operator=(const partite_col_values&) = default; - constexpr partite_col_values& operator=(partite_col_values&&) = default; - - -public: // Properties - [[nodiscard]] constexpr size_type size() const noexcept { return static_cast(v_.size()); } - [[nodiscard]] constexpr bool empty() const noexcept { return v_.empty(); } - [[nodiscard]] constexpr size_type capacity() const noexcept { return static_cast(v_.size()); } - -public: // Operations - constexpr void reserve(size_type new_cap) { v_.reserve(new_cap); } - constexpr void resize(size_type new_size) { v_.resize(new_size); } - - constexpr void clear() noexcept { v_.clear(); } - constexpr void push_back(const value_type& value) { v_.push_back(value); } - constexpr void emplace_back(value_type&& value) { v_.emplace_back(forward(value)); } - - constexpr void swap(partite_col_values& other) noexcept { swap(v_, other.v_); } - -public: - constexpr reference operator[](size_type pos) { return v_[pos]; } - constexpr const_reference operator[](size_type pos) const { return v_[pos]; } - -private: - // edge_value(g,uv) - friend constexpr edge_value_type& - tag_invoke(::std::graph::tag_invoke::edge_value_fn_t, graph_type& g, edge_type& uv) { - auto uv_idx = g.index_of(uv); - partite_col_values& col_vals = g; - return col_vals.v_[uv_idx]; - } - friend constexpr const edge_value_type& - tag_invoke(::std::graph::tag_invoke::edge_value_fn_t, const graph_type& g, const edge_type& uv) { - auto uv_idx = g.index_of(uv); - const partite_col_values& col_vals = g; - return col_vals.v_[uv_idx]; - } - -private: - vector_type v_; -}; - -template -class partite_col_values { -public: - constexpr partite_col_values(const Alloc& alloc) {} - constexpr partite_col_values() = default; - - using value_type = void; - using size_type = size_t; //VId; - -public: // Properties - [[nodiscard]] constexpr size_type size() const noexcept { return 0; } - [[nodiscard]] constexpr bool empty() const noexcept { return true; } - [[nodiscard]] constexpr size_type capacity() const noexcept { return 0; } - -public: // Operations - constexpr void reserve(size_type new_cap) {} - constexpr void resize(size_type new_size) {} - - constexpr void clear() noexcept {} - constexpr void swap(partite_col_values& other) noexcept {} -}; - - -/** - * @ingroup graph_containers - * @brief Base class for compressed sparse row adjacency graph - * - * @tparam EV The edge value type. If "void" is used no user value is stored on the edge and - * calls to @c edge_value(g,uv) will generate a compile error. - * @tparam VV The vertex value type. If "void" is used no user value is stored on the vertex - * and calls to @c vertex_value(g,u) will generate a compile error. - * @tparam GV The graph value type. If "void" is used no user value is stored on the graph - * and calls to @c graph_value(g) will generate a compile error. - * @tparam VId Vertex id type. The type must be able to store a value of |V|+1, where |V| is the - * number of vertices in the graph. - * @tparam EIndex The type for storing an edge index. It must be able to store a value of |E|+1, - * where |E| is the total number of edges in the graph. - * @tparam Alloc The allocator type. -*/ -template -class partite_graph_base - : public partite_row_values - , public partite_col_values { - using row_values_base = partite_row_values; - using col_values_base = partite_col_values; - - using row_type = partite_row; // index into col_index_ - using row_allocator_type = typename allocator_traits::template rebind_alloc; - using row_index_vector = vector; - - using col_type = partite_col; // target_id - using col_allocator_type = typename allocator_traits::template rebind_alloc; - using col_index_vector = vector; - -public: // Types - using graph_type = partite_graph_base; - - using vertex_id_type = VId; - using vertex_type = row_type; - using vertex_value_type = VV; - using vertices_type = ranges::subrange>; - using const_vertices_type = ranges::subrange>; - - using edge_type = col_type; // index into v_ - using edge_value_type = EV; - using edge_index_type = EIndex; - using edges_type = ranges::subrange>; - using const_edges_type = ranges::subrange>; - - using const_iterator = typename row_index_vector::const_iterator; - using iterator = typename row_index_vector::iterator; - - using size_type = ranges::range_size_t; - -public: // Construction/Destruction - constexpr partite_graph_base() = default; - constexpr partite_graph_base(const partite_graph_base&) = default; - constexpr partite_graph_base(partite_graph_base&&) = default; - constexpr ~partite_graph_base() = default; - - constexpr partite_graph_base& operator=(const partite_graph_base&) = default; - constexpr partite_graph_base& operator=(partite_graph_base&&) = default; - - constexpr partite_graph_base(const Alloc& alloc) - : row_values_base(alloc), col_values_base(alloc), row_index_(alloc), col_index_(alloc) {} - - /** - * @brief Constructor that takes a edge range to create the CSR graph. - * - * Edges must be ordered by source_id (enforced by asssertion). - * - * @tparam ERng Edge range type - * @tparam EProj Edge projection function type - * - * @param erng The input range of edges - * @param eprojection Projection function that creates a @c copyable_edge_t from an erng value - * @param alloc Allocator to use for internal containers - */ - template - requires copyable_edge>, VId, EV> - constexpr partite_graph_base(const ERng& erng, EProj eprojection = {}, const Alloc& alloc = Alloc()) - : row_values_base(alloc), col_values_base(alloc), row_index_(alloc), col_index_(alloc) { - - load_edges(erng, eprojection); - } - - /** - * @brief Constructor that takes edge range and vertex range to create the CSR graph. - * - * Edges must be ordered by source_id (enforced by asssertion). - * - * @tparam ERng Edge Range type - * @tparam VRng Vetex range type - * @tparam EProj Edge projection function type - * @tparam VProj Vertex projection function type - * - * @param erng The input range of edges - * @param vrng The input range of vertices - * @param eprojection Projection function that creates a @c copyable_edge_t from an @c erng value - * @param vprojection Projection function that creates a @c copyable_vertex_t from a @c vrng value - * @param alloc Allocator to use for internal containers - */ - template - //requires copyable_edge>, VId, EV> && - // copyable_vertex>, VId, VV> - constexpr partite_graph_base(const ERng& erng, - const VRng& vrng, - EProj eprojection = {}, // eproj(eval) -> {source_id,target_id [,value]} - VProj vprojection = {}, // vproj(vval) -> {target_id [,value]} - const Alloc& alloc = Alloc()) - : row_values_base(alloc), col_values_base(alloc), row_index_(alloc), col_index_(alloc) { - - load(erng, vrng, eprojection, vprojection); - } - - /** - * @brief Constructor for easy creation of a graph that takes an initializer list - * of @c copyable_edge_t -> [source_id, target_id, value]. - * - * @param ilist Initializer list of @c copyable_edge_t -> [source_id, target_id, value] - * @param alloc Allocator to use for internal containers - */ - constexpr partite_graph_base(const initializer_list>& ilist, const Alloc& alloc = Alloc()) - : row_values_base(alloc), col_values_base(alloc), row_index_(alloc), col_index_(alloc) { - load_edges(ilist, identity()); - } - -public: -public: // Operations - void reserve_vertices(size_type count) { - row_index_.reserve(count + 1); // +1 for terminating row - row_values_base::reserve(count); - } - void reserve_edges(size_type count) { - col_index_.reserve(count); - static_cast(*this).reserve(count); - } - - void resize_vertices(size_type count) { - row_index_.resize(count + 1); // +1 for terminating row - row_values_resize(count); - } - void resize_edges(size_type count) { - col_index_.reserve(count); - static_cast(*this).reserve(count); - } - - /** - * @brief Load vertex values, callable either before or after @c load_edges(erng,eproj). - * - * If @c load_edges(vrng,vproj) has been called before this, the @c row_values_ vector will be - * extended to match the number of @c row_index_.size()-1 to avoid out-of-bounds errors when - * accessing vertex values. - * - * @tparam VRng Vertex range type - * @tparam VProj Vertex projection function type - * - * @param vrng Range of values to load for vertices. The order of the values is - * preserved in the internal vector. - * @param vprojection Projection function for @c vrng values - */ - template - //requires views::copyable_vertex>, VId, VV> - constexpr void load_vertices(const VRng& vrng, VProj vprojection, size_type vertex_count = 0) { - row_values_base::load_row_values(vrng, vprojection, max(vertex_count, ranges::size(vrng))); - } - - /** - * Load vertex values, callable either before or after @c load_edges(erng,eproj). - * - * If @c load_edges(vrng,vproj) has been called before this, the @c row_values_ vector will be - * extended to match the number of @c row_index_.size()-1 to avoid out-of-bounds errors when - * accessing vertex values. - * - * @tparam VRng Vertex range type - * @tparam VProj Projection function type - * - * @param vrng Range of values to load for vertices. The order of the values is preserved in the internal vector. - * @param vprojection Projection function for @c vrng values - */ - template - //requires views::copyable_vertex>, VId, VV> - constexpr void load_vertices(VRng& vrng, VProj vprojection = {}, size_type vertex_count = 0) { - row_values_base::load_row_values(vrng, vprojection, max(vertex_count, ranges::size(vrng))); - } - - /** - * @brief Load the edges for the graph, callable either before or after @c load_vertices(erng,eproj). - * - * @c erng must be ordered by source_id (copyable_edge_t) and is enforced by assertion. target_id - * can be unordered within a source_id. - * - * If @c erng is bi-directional, the source_id in the last entry is used to determine the maximum - * number of rows and is used to reserve space in the internal row_index and row_value vectors. - * If @c erng is an input_range or forward_range that evaluation can't be done and the internal - * row_index vector is grown and resized normally as needed (the row_value vector is updated by - * @c load_vertices(vrng,vproj)). If the caller knows the number of rows/vertices, they can call - * @c reserve_vertices(n) to reserve the space. - * - * If @c erng is a sized_range, @c size(erng) is used to reserve space for the internal col_index and - * v vectors. If it isn't a sized range, the vectors will be grown and resized normally as needed - * as new indexes and values are added. If the caller knows the number of columns/edges, they - * can call @c reserve_edges(n) to reserve the space. - * - * If row indexes have been referenced in the edges but there are no edges defined for them - * (with source_id), rows will be added to fill out the row_index vector to avoid out-of-bounds - * references. - * - * If @c load_vertices(vrng,vproj) has been called before this, the row_values_ vector will be - * extended to match the number of @c row_index_.size()-1 to avoid out-of-bounds errors when - * accessing vertex values. - * - * @todo @c ERng not a forward_range because CSV reader doesn't conform to be a forward_range - * - * @tparam ERng Edge range type - * @tparam EProj Edge projection function type - * - * @param erng Input range for edges - * @param eprojection Edge projection function that returns a @ copyable_edge_t for an element in @c erng - */ - template - //requires views::copyable_edge>, VId, EV> - constexpr void load_edges(ERng&& erng, EProj eprojection = {}, size_type vertex_count = 0, size_type edge_count = 0) { - // should only be loading into an empty graph - assert(row_index_.empty() && col_index_.empty() && static_cast(*this).empty()); - - // Nothing to do? - if (ranges::begin(erng) == ranges::end(erng)) { - return; - } - - // We can get the last vertex id from the list because erng is required to be ordered by - // the source id. It's possible a target_id could have a larger id also, which is taken - // care of at the end of this function. - vertex_count = std::max(vertex_count, - static_cast(last_erng_id(erng, eprojection) + 1)); // +1 for zero-based index - reserve_vertices(vertex_count); - - // Eval number of input rows and reserve space for the edges, if possible - if constexpr (ranges::sized_range) - edge_count = max(edge_count, ranges::size(erng)); - reserve_edges(edge_count); - - // Add edges - vertex_id_type last_uid = 0, max_vid = 0; - for (auto&& edge_data : erng) { - auto&& edge = eprojection(edge_data); // partite_graph requires EV!=void - assert(edge.source_id >= last_uid); // ordered by uid? (requirement) - row_index_.resize(static_cast(edge.source_id) + 1, - vertex_type{static_cast(static_cast(*this).size())}); - col_index_.push_back(edge_type{edge.target_id}); - if (!is_void_v) - static_cast(*this).emplace_back(std::move(edge.value)); - last_uid = edge.source_id; - max_vid = max(max_vid, edge.target_id); - } - - // uid and vid may refer to rows that exceed the value evaluated for vertex_count (if any) - vertex_count = max(vertex_count, max(row_index_.size(), static_cast(max_vid + 1))); - - // add any rows that haven't been added yet, and (+1) terminating row - row_index_.resize(vertex_count + 1, - vertex_type{static_cast(static_cast(*this).size())}); - - // If load_vertices(vrng,vproj) has been called but it doesn't have enough values for all - // the vertices then we extend the size to remove possibility of out-of-bounds occuring when - // getting a value for a row. - if (row_values_base::size() > 1 && row_values_base::size() < vertex_count) - row_values_base::resize(vertex_count); - } - - // The only diff with this and ERng&& is v_.push_back vs. v_.emplace_back - template - //requires views::copyable_edge>, VId, EV> - constexpr void - load_edges(const ERng& erng, EProj eprojection = {}, size_type vertex_count = 0, size_type edge_count = 0) { - // should only be loading into an empty graph - assert(row_index_.empty() && col_index_.empty() && static_cast(*this).empty()); - - // Nothing to do? - if (ranges::begin(erng) == ranges::end(erng)) { - return; - } - - // We can get the last vertex id from the list because erng is required to be ordered by - // the source id. It's possible a target_id could have a larger id also, which is taken - // care of at the end of this function. - vertex_count = std::max(vertex_count, - static_cast(last_erng_id(erng, eprojection) + 1)); // +1 for zero-based index - reserve_vertices(vertex_count); - - // Eval number of input rows and reserve space for the edges, if possible - if constexpr (ranges::sized_range) - edge_count = max(edge_count, ranges::size(erng)); - reserve_edges(edge_count); - - // Add edges - vertex_id_type last_uid = 0, max_vid = 0; - for (auto&& edge_data : erng) { - auto&& edge = eprojection(edge_data); // partite_graph requires EV!=void - assert(edge.source_id >= last_uid); // ordered by uid? (requirement) - row_index_.resize(static_cast(edge.source_id) + 1, - vertex_type{static_cast(static_cast(*this).size())}); - col_index_.push_back(edge_type{edge.target_id}); - if constexpr (!is_void_v) - static_cast(*this).push_back(edge.value); - last_uid = edge.source_id; - max_vid = max(max_vid, edge.target_id); - } - - // uid and vid may refer to rows that exceed the value evaluated for vertex_count (if any) - vertex_count = max(vertex_count, max(row_index_.size(), static_cast(max_vid + 1))); - - // add any rows that haven't been added yet, and (+1) terminating row - row_index_.resize(vertex_count + 1, - vertex_type{static_cast(static_cast(*this).size())}); - - // If load_vertices(vrng,vproj) has been called but it doesn't have enough values for all - // the vertices then we extend the size to remove possibility of out-of-bounds occuring when - // getting a value for a row. - if (row_values_base::size() > 0 && row_values_base::size() < vertex_count) - row_values_base::resize(vertex_count); - } - - /** - * @brief Load edges and then vertices for the graph. - * - * See @c load_edges() and @c load_vertices() for more information. - * - * @tparam EProj Edge Projection Function type - * @tparam VProj Vertex Projectiong Function type - * - * @param erng Input edge range - * @param vrng Input vertex range - * @param eprojection Edge projection function object - * @param vprojection Vertex projection function object - */ - template - //requires views::copyable_edge>, VId, EV> && - // views::copyable_vertex>, VId, VV> - constexpr void load(const ERng& erng, const VRng& vrng, EProj eprojection = {}, VProj vprojection = {}) { - load_edges(erng, eprojection); - load_vertices(vrng, vprojection); // load the values - } - -protected: - template - constexpr vertex_id_type last_erng_id(ERng&& erng, EProj eprojection) const { - vertex_id_type last_id = vertex_id_type(); - if constexpr (ranges::bidirectional_range) { - if (ranges::begin(erng) != ranges::end(erng)) { - auto lastIt = ranges::end(erng); - --lastIt; - auto&& e = eprojection(*lastIt); // copyable_edge - last_id = max(e.source_id, e.target_id); - } - } - return last_id; - } - -public: // Operations - constexpr ranges::iterator_t find_vertex(vertex_id_type id) noexcept { - return row_index_.begin() + id; - } - constexpr ranges::iterator_t find_vertex(vertex_id_type id) const noexcept { - return row_index_.begin() + id; - } - - constexpr edge_index_type index_of(const row_type& u) const noexcept { - return static_cast(&u - row_index_.data()); - } - constexpr vertex_id_type index_of(const col_type& v) const noexcept { - return static_cast(&v - col_index_.data()); - } - -public: // Operators - constexpr vertex_type& operator[](vertex_id_type id) noexcept { return row_index_[id]; } - constexpr const vertex_type& operator[](vertex_id_type id) const noexcept { return row_index_[id]; } - -private: // Member variables - row_index_vector row_index_; // starting index into col_index_ and v_; holds +1 extra terminating row - col_index_vector col_index_; // col_index_[n] holds the column index (aka target) - //v_vector_type v_; // v_[n] holds the edge value for col_index_[n] - //row_values_type row_value_; // row_value_[r] holds the value for row_index_[r], for VV!=void - -private: // tag_invoke properties - friend constexpr vertices_type tag_invoke(::std::graph::tag_invoke::vertices_fn_t, partite_graph_base& g) { - if (g.row_index_.empty()) - return vertices_type(g.row_index_); // really empty - else - return vertices_type(g.row_index_.begin(), g.row_index_.end() - 1); // don't include terminating row - } - friend constexpr const_vertices_type tag_invoke(::std::graph::tag_invoke::vertices_fn_t, - const partite_graph_base& g) { - if (g.row_index_.empty()) - return const_vertices_type(g.row_index_); // really empty - else - return const_vertices_type(g.row_index_.begin(), g.row_index_.end() - 1); // don't include terminating row - } - - friend vertex_id_type - tag_invoke(::std::graph::tag_invoke::vertex_id_fn_t, const partite_graph_base& g, const_iterator ui) { - return static_cast(ui - g.row_index_.begin()); - } - - friend constexpr edges_type tag_invoke(::std::graph::tag_invoke::edges_fn_t, graph_type& g, vertex_type& u) { - static_assert(ranges::contiguous_range, "row_index_ must be a contiguous range to get next row"); - vertex_type* u2 = &u + 1; - assert(static_cast(u2 - &u) < g.row_index_.size()); // in row_index_ bounds? - assert(static_cast(u.index) <= g.col_index_.size() && - static_cast(u2->index) <= g.col_index_.size()); // in col_index_ bounds? - return edges_type(g.col_index_.begin() + u.index, g.col_index_.begin() + u2->index); - } - friend constexpr const_edges_type - tag_invoke(::std::graph::tag_invoke::edges_fn_t, const graph_type& g, const vertex_type& u) { - static_assert(ranges::contiguous_range, "row_index_ must be a contiguous range to get next row"); - const vertex_type* u2 = &u + 1; - assert(static_cast(u2 - &u) < g.row_index_.size()); // in row_index_ bounds? - assert(static_cast(u.index) <= g.col_index_.size() && - static_cast(u2->index) <= g.col_index_.size()); // in col_index_ bounds? - return const_edges_type(g.col_index_.begin() + u.index, g.col_index_.begin() + u2->index); - } - - friend constexpr edges_type - tag_invoke(::std::graph::tag_invoke::edges_fn_t, graph_type& g, const vertex_id_type uid) { - assert(static_cast(uid + 1) < g.row_index_.size()); // in row_index_ bounds? - assert(static_cast(g.row_index_[uid + 1].index) <= g.col_index_.size()); // in col_index_ bounds? - return edges_type(g.col_index_.begin() + g.row_index_[uid].index, - g.col_index_.begin() + g.row_index_[uid + 1].index); - } - friend constexpr const_edges_type - tag_invoke(::std::graph::tag_invoke::edges_fn_t, const graph_type& g, const vertex_id_type uid) { - assert(static_cast(uid + 1) < g.row_index_.size()); // in row_index_ bounds? - assert(static_cast(g.row_index_[uid + 1].index) <= g.col_index_.size()); // in col_index_ bounds? - return const_edges_type(g.col_index_.begin() + g.row_index_[uid].index, - g.col_index_.begin() + g.row_index_[uid + 1].index); - } - - - // target_id(g,uv), target(g,uv) - friend constexpr vertex_id_type - tag_invoke(::std::graph::tag_invoke::target_id_fn_t, const graph_type& g, const edge_type& uv) noexcept { - return uv.index; - } - friend constexpr vertex_type& - tag_invoke(::std::graph::tag_invoke::target_fn_t, graph_type& g, edge_type& uv) noexcept { - return g.row_index_[uv.index]; - } - friend constexpr const vertex_type& - tag_invoke(::std::graph::tag_invoke::target_fn_t, const graph_type& g, const edge_type& uv) noexcept { - return g.row_index_[uv.index]; - } - - friend row_values_base; - friend col_values_base; -}; - - -/** - * @ingroup graph_containers - * @brief Compressed Sparse Row adjacency graph container. - * - * @tparam EV Edge value type - * @tparam VV Vertex value type - * @tparam GV Graph value type - * @tparam VI Vertex Id type. This must be large enough for the count of vertices. - * @tparam EIndex Edge Index type. This must be large enough for the count of edges. - * @tparam Alloc Allocator type -*/ -template -class partite_graph : public partite_graph_base { -public: // Types - using graph_type = partite_graph; - using base_type = partite_graph_base; - - using edge_value_type = EV; - using vertex_value_type = VV; - using graph_value_type = GV; - using value_type = GV; - - using vertex_id_type = VId; - -public: // Construction/Destruction - constexpr partite_graph() = default; - constexpr partite_graph(const partite_graph&) = default; - constexpr partite_graph(partite_graph&&) = default; - constexpr ~partite_graph() = default; - - constexpr partite_graph& operator=(const partite_graph&) = default; - constexpr partite_graph& operator=(partite_graph&&) = default; - - // partite_graph( alloc) - // partite_graph(gv&, alloc) - // partite_graph(gv&&, alloc) - - constexpr partite_graph(const Alloc& alloc) : base_type(alloc) {} - constexpr partite_graph(const graph_value_type& value, const Alloc& alloc = Alloc()) - : base_type(alloc), value_(value) {} - constexpr partite_graph(graph_value_type&& value, const Alloc& alloc = Alloc()) - : base_type(alloc), value_(move(value)) {} - - // partite_graph( erng, eprojection, alloc) - // partite_graph(gv&, erng, eprojection, alloc) - // partite_graph(gv&&, erng, eprojection, alloc) - - template - requires copyable_edge>, VId, EV> - constexpr partite_graph(const ERng& erng, EProj eprojection, const Alloc& alloc = Alloc()) - : base_type(erng, eprojection, alloc) {} - - template - requires copyable_edge>, VId, EV> - constexpr partite_graph(const graph_value_type& value, - const ERng& erng, - EProj eprojection, - const Alloc& alloc = Alloc()) - : base_type(erng, eprojection, alloc), value_(value) {} - - template - requires copyable_edge>, VId, EV> - constexpr partite_graph(graph_value_type&& value, const ERng& erng, EProj eprojection, const Alloc& alloc = Alloc()) - : base_type(erng, eprojection, alloc), value_(move(value)) {} - - // partite_graph( erng, vrng, eprojection, vprojection, alloc) - // partite_graph(gv&, erng, vrng, eprojection, vprojection, alloc) - // partite_graph(gv&&, erng, vrng, eprojection, vprojection, alloc) - - template - requires copyable_edge>, VId, EV> && - copyable_vertex>, VId, VV> - constexpr partite_graph(const ERng& erng, - const VRng& vrng, - EProj eprojection = {}, - VProj vprojection = {}, - const Alloc& alloc = Alloc()) - : base_type(erng, vrng, eprojection, vprojection, alloc) {} - - template - requires copyable_edge>, VId, EV> && - copyable_vertex>, VId, VV> - constexpr partite_graph(const graph_value_type& value, - const ERng& erng, - const VRng& vrng, - EProj eprojection = {}, - VProj vprojection = {}, - const Alloc& alloc = Alloc()) - : base_type(erng, vrng, eprojection, vprojection, alloc), value_(value) {} - - template - requires copyable_edge>, VId, EV> && - copyable_vertex>, VId, VV> - constexpr partite_graph(graph_value_type&& value, - const ERng& erng, - const VRng& vrng, - EProj eprojection = {}, - VProj vprojection = {}, - const Alloc& alloc = Alloc()) - : base_type(erng, vrng, eprojection, vprojection, alloc), value_(move(value)) {} - - - constexpr partite_graph(const initializer_list>& ilist, const Alloc& alloc = Alloc()) - : base_type(ilist, alloc) {} - -private: // tag_invoke properties - friend constexpr value_type& tag_invoke(::std::graph::tag_invoke::graph_value_fn_t, graph_type& g) { - return g.value_; - } - friend constexpr const value_type& tag_invoke(::std::graph::tag_invoke::graph_value_fn_t, const graph_type& g) { - return g.value_; - } - -private: // Member variables - graph_value_type value_ = graph_value_type(); -}; - -/** - * @ingroup graph_containers - * @brief Compressed Sparse Row adjacency graph container. - * - * @tparam EV Edge value type - * @tparam VV Vertex value type - * @tparam VI Vertex Id type. This must be large enough for the count of vertices. - * @tparam EIndex Edge Index type. This must be large enough for the count of edges. - * @tparam Alloc Allocator type -*/ -template -class partite_graph : public partite_graph_base { -public: // Types - using graph_type = partite_graph; - using base_type = partite_graph_base; - - using vertex_id_type = VId; - using vertex_value_type = VV; - - using graph_value_type = void; - using value_type = void; - -public: // Construction/Destruction - constexpr partite_graph() = default; - constexpr partite_graph(const partite_graph&) = default; - constexpr partite_graph(partite_graph&&) = default; - constexpr ~partite_graph() = default; - - constexpr partite_graph& operator=(const partite_graph&) = default; - constexpr partite_graph& operator=(partite_graph&&) = default; - - // edge-only construction - template - requires copyable_edge>, VId, EV> - constexpr partite_graph(const ERng& erng, EProj eprojection, const Alloc& alloc = Alloc()) - : base_type(erng, eprojection, alloc) {} - - // edge and vertex value construction - template - requires copyable_edge>, VId, EV> && - copyable_vertex>, VId, VV> - constexpr partite_graph(const ERng& erng, - const VRng& vrng, - EProj eprojection = {}, - VProj vprojection = {}, - const Alloc& alloc = Alloc()) - : base_type(erng, vrng, eprojection, vprojection, alloc) {} - - // initializer list using edge_descriptor - constexpr partite_graph(const initializer_list>& ilist, const Alloc& alloc = Alloc()) - : base_type(ilist, alloc) {} - - -public: // Operations -private: // tag_invoke properties -}; - -} // namespace std::graph::container diff --git a/include/graph/detail/graph_cpo.hpp b/include/graph/detail/graph_cpo.hpp index f730691..b5b582f 100644 --- a/include/graph/detail/graph_cpo.hpp +++ b/include/graph/detail/graph_cpo.hpp @@ -938,7 +938,7 @@ auto vertices(G&& g, partition_id_t pid) { } template -using vertex_partition_range_t = decltype(vertices(declval(), declval())); +using vertex_partition_range_t = decltype(vertices(declval(), declval>())); # endif From 3aadc876e25641f5530a69638f76f3876fdadc39 Mon Sep 17 00:00:00 2001 From: Phil Ratzloff Date: Sun, 1 Oct 2023 16:33:04 -0400 Subject: [PATCH 03/15] Add basic types and CPO functions for csr_graph for partitions --- include/graph/container/csr_graph.hpp | 60 ++++++++++++++++++++------- 1 file changed, 46 insertions(+), 14 deletions(-) diff --git a/include/graph/container/csr_graph.hpp b/include/graph/container/csr_graph.hpp index b5aaa68..c85de44 100644 --- a/include/graph/container/csr_graph.hpp +++ b/include/graph/container/csr_graph.hpp @@ -232,7 +232,7 @@ class csr_row_values { using value_type = void; using size_type = size_t; //VId; -public: // Properties +public: // Properties [[nodiscard]] constexpr size_type size() const noexcept { return 0; } [[nodiscard]] constexpr bool empty() const noexcept { return true; } [[nodiscard]] constexpr size_type capacity() const noexcept { return 0; } @@ -346,7 +346,7 @@ class csr_col_values { using value_type = void; using size_type = size_t; //VId; -public: // Properties +public: // Properties [[nodiscard]] constexpr size_type size() const noexcept { return 0; } [[nodiscard]] constexpr bool empty() const noexcept { return true; } [[nodiscard]] constexpr size_type capacity() const noexcept { return 0; } @@ -387,6 +387,10 @@ class csr_graph_base using row_allocator_type = typename allocator_traits::template rebind_alloc; using row_index_vector = vector; + using partition_type = VId; // index into row_indexes + using partition_allocator_type = typename allocator_traits::template rebind_alloc; + using partition_index_vector = vector; + using col_type = csr_col; // target_id using col_allocator_type = typename allocator_traits::template rebind_alloc; using col_index_vector = vector; @@ -400,6 +404,9 @@ class csr_graph_base using vertices_type = ranges::subrange>; using const_vertices_type = ranges::subrange>; + using partition_id_type = vertex_id_type; + using partition_size_type = vertex_id_type; + using edge_type = col_type; // index into v_ using edge_value_type = EV; using edge_index_type = EIndex; @@ -421,7 +428,9 @@ class csr_graph_base constexpr csr_graph_base& operator=(csr_graph_base&&) = default; constexpr csr_graph_base(const Alloc& alloc) - : row_values_base(alloc), col_values_base(alloc), row_index_(alloc), col_index_(alloc) {} + : row_values_base(alloc), col_values_base(alloc), row_index_(alloc), col_index_(alloc), part_index_(alloc) { + set_default_partition(); + } /** * @brief Constructor that takes a edge range to create the CSR graph. @@ -438,7 +447,7 @@ class csr_graph_base template requires copyable_edge>, VId, EV> constexpr csr_graph_base(const ERng& erng, EProj eprojection = {}, const Alloc& alloc = Alloc()) - : row_values_base(alloc), col_values_base(alloc), row_index_(alloc), col_index_(alloc) { + : row_values_base(alloc), col_values_base(alloc), row_index_(alloc), col_index_(alloc), part_index_(alloc) { load_edges(erng, eprojection); } @@ -467,7 +476,7 @@ class csr_graph_base EProj eprojection = {}, // eproj(eval) -> {source_id,target_id [,value]} VProj vprojection = {}, // vproj(vval) -> {target_id [,value]} const Alloc& alloc = Alloc()) - : row_values_base(alloc), col_values_base(alloc), row_index_(alloc), col_index_(alloc) { + : row_values_base(alloc), col_values_base(alloc), row_index_(alloc), col_index_(alloc), part_index_(alloc) { load(erng, vrng, eprojection, vprojection); } @@ -480,12 +489,11 @@ class csr_graph_base * @param alloc Allocator to use for internal containers */ constexpr csr_graph_base(const initializer_list>& ilist, const Alloc& alloc = Alloc()) - : row_values_base(alloc), col_values_base(alloc), row_index_(alloc), col_index_(alloc) { + : row_values_base(alloc), col_values_base(alloc), row_index_(alloc), col_index_(alloc), part_index_(alloc) { load_edges(ilist, identity()); } -public: -public: // Operations +public: // Operations void reserve_vertices(size_type count) { row_index_.reserve(count + 1); // +1 for terminating row row_values_base::reserve(count); @@ -522,6 +530,7 @@ class csr_graph_base //requires views::copyable_vertex>, VId, VV> constexpr void load_vertices(const VRng& vrng, VProj vprojection, size_type vertex_count = 0) { row_values_base::load_row_values(vrng, vprojection, max(vertex_count, ranges::size(vrng))); + set_default_partition(); } /** @@ -541,6 +550,7 @@ class csr_graph_base //requires views::copyable_vertex>, VId, VV> constexpr void load_vertices(VRng& vrng, VProj vprojection = {}, size_type vertex_count = 0) { row_values_base::load_row_values(vrng, vprojection, max(vertex_count, ranges::size(vrng))); + set_default_partition(); } /** @@ -626,6 +636,8 @@ class csr_graph_base // getting a value for a row. if (row_values_base::size() > 1 && row_values_base::size() < vertex_count) row_values_base::resize(vertex_count); + + set_default_partition(); } // The only diff with this and ERng&& is v_.push_back vs. v_.emplace_back @@ -679,6 +691,8 @@ class csr_graph_base // getting a value for a row. if (row_values_base::size() > 0 && row_values_base::size() < vertex_count) row_values_base::resize(vertex_count); + + set_default_partition(); } /** @@ -717,6 +731,17 @@ class csr_graph_base return last_id; } + void set_default_partition() { + if (size(part_index_) == 0) { + part_index_.push_back(0); + part_index_.push_back(static_cast(size(row_index_))); + } else if (size(part_index_) == 2) { + part_index_.back() = static_cast(size(row_index_)); + } else { + assert(false); // Multiple partitions need different logic + } + } + public: // Operations constexpr ranges::iterator_t find_vertex(vertex_id_type id) noexcept { return row_index_.begin() + id; @@ -739,19 +764,19 @@ class csr_graph_base private: // Member variables row_index_vector row_index_; // starting index into col_index_ and v_; holds +1 extra terminating row col_index_vector col_index_; // col_index_[n] holds the column index (aka target) - //v_vector_type v_; // v_[n] holds the edge value for col_index_[n] - //row_values_type row_value_; // row_value_[r] holds the value for row_index_[r], for VV!=void + partition_index_vector + part_index_; // row_index_[part_index_[p]] is the first row of partition p; holds +1 extra for terminating row (size(row_index_)) private: // tag_invoke properties friend constexpr vertices_type tag_invoke(::std::graph::tag_invoke::vertices_fn_t, csr_graph_base& g) { if (g.row_index_.empty()) - return vertices_type(g.row_index_); // really empty + return vertices_type(g.row_index_); // really empty else return vertices_type(g.row_index_.begin(), g.row_index_.end() - 1); // don't include terminating row } friend constexpr const_vertices_type tag_invoke(::std::graph::tag_invoke::vertices_fn_t, const csr_graph_base& g) { if (g.row_index_.empty()) - return const_vertices_type(g.row_index_); // really empty + return const_vertices_type(g.row_index_); // really empty else return const_vertices_type(g.row_index_.begin(), g.row_index_.end() - 1); // don't include terminating row } @@ -764,7 +789,7 @@ class csr_graph_base friend constexpr edges_type tag_invoke(::std::graph::tag_invoke::edges_fn_t, graph_type& g, vertex_type& u) { static_assert(ranges::contiguous_range, "row_index_ must be a contiguous range to get next row"); vertex_type* u2 = &u + 1; - assert(static_cast(u2 - &u) < g.row_index_.size()); // in row_index_ bounds? + assert(static_cast(u2 - &u) < g.row_index_.size()); // in row_index_ bounds? assert(static_cast(u.index) <= g.col_index_.size() && static_cast(u2->index) <= g.col_index_.size()); // in col_index_ bounds? return edges_type(g.col_index_.begin() + u.index, g.col_index_.begin() + u2->index); @@ -773,7 +798,7 @@ class csr_graph_base tag_invoke(::std::graph::tag_invoke::edges_fn_t, const graph_type& g, const vertex_type& u) { static_assert(ranges::contiguous_range, "row_index_ must be a contiguous range to get next row"); const vertex_type* u2 = &u + 1; - assert(static_cast(u2 - &u) < g.row_index_.size()); // in row_index_ bounds? + assert(static_cast(u2 - &u) < g.row_index_.size()); // in row_index_ bounds? assert(static_cast(u.index) <= g.col_index_.size() && static_cast(u2->index) <= g.col_index_.size()); // in col_index_ bounds? return const_edges_type(g.col_index_.begin() + u.index, g.col_index_.begin() + u2->index); @@ -809,6 +834,13 @@ class csr_graph_base return g.row_index_[uv.index]; } + // partitions + friend constexpr partition_id_type tag_invoke(::std::graph::tag_invoke::partition_count_fn_t, + const graph_type& g) noexcept { + return static_cast(size(g.part_index_) - 1); + } + + friend row_values_base; friend col_values_base; }; From bce9246ca8cf5e469a617b90a5abfc6cc649932f Mon Sep 17 00:00:00 2001 From: Phil Ratzloff Date: Sun, 1 Oct 2023 16:35:57 -0400 Subject: [PATCH 04/15] Add partition types and CPOs partition_vertex_range_t partition_vertex_id_t partition_count(g) vertices(g,p) partition_vertex_id(g,uid) partition_vertex_id(g,ui) partition_vertex_id functions use newer CPO implementation style to work around function overload issues. --- include/graph/detail/graph_cpo.hpp | 232 +++++++++++++++++++++++++++-- 1 file changed, 217 insertions(+), 15 deletions(-) diff --git a/include/graph/detail/graph_cpo.hpp b/include/graph/detail/graph_cpo.hpp index b5b582f..c2ea2a8 100644 --- a/include/graph/detail/graph_cpo.hpp +++ b/include/graph/detail/graph_cpo.hpp @@ -8,6 +8,46 @@ namespace std::graph { +# ifndef _MSC_VER +// Taken from gcc11 definition of __decay_copy(). +// Very similar (identical?) to std::_Fake_copy_init in msvc. +struct _Decay_copy final { + template + constexpr decay_t<_Tp> operator()(_Tp&& __t) const noexcept(is_nothrow_convertible_v<_Tp, decay_t<_Tp>>) { + return std::forward<_Tp>(__t); + } +} inline constexpr _Fake_copy_init{}; + +template +concept _Has_class_or_enum_type = + __is_class(remove_reference_t<_Ty>) || __is_enum(remove_reference_t<_Ty>) || __is_union(remove_reference_t<_Ty>); + +template +// false value attached to a dependent name (for static_assert) +inline constexpr bool _Always_false = false; + +template +inline constexpr bool _Is_nonbool_integral = is_integral_v<_Ty> && !is_same_v, bool>; + +template +inline constexpr bool _Integer_class = requires { + typename _Ty::_Signed_type; + typename _Ty::_Unsigned_type; +}; + +template +concept _Integer_like = _Is_nonbool_integral> || _Integer_class<_Ty>; + +template +concept _Signed_integer_like = _Integer_like<_Ty> && static_cast<_Ty>(-1) < static_cast<_Ty>(0); + +template +struct _Choice_t { + _Ty _Strategy = _Ty{}; + bool _No_throw = false; +}; +# endif + // Tags are defined in tag_invoke namespace to avoid conflicts with function names // in std::graph, allowing customization for default behavior. // @@ -863,14 +903,14 @@ auto partition_id(G&& g, vertex_reference_t u) { } -// partition_size(g) -> ? default = size_t(0) +// partition_count(g) -> ? default = vertex_id_t(1) when vertex_id_t is integral, size_t(0) otherwise // namespace tag_invoke { - TAG_INVOKE_DEF(partition_size); + TAG_INVOKE_DEF(partition_count); template - concept _has_partition_size_adl = requires(G&& g) { - { partition_size(g) }; + concept _has_partition_count_adl = requires(G&& g) { + { partition_count(g) }; }; } // namespace tag_invoke @@ -890,17 +930,17 @@ namespace tag_invoke { * @return The number of partitions in a graph. 0 if G doesn't support partitioning. */ template -requires tag_invoke::_has_partition_size_adl -auto partition_size(G&& g) { - if constexpr (tag_invoke::_has_partition_size_adl) - return tag_invoke::partition_size(g); +requires tag_invoke::_has_partition_count_adl +auto partition_count(G&& g) { + if constexpr (tag_invoke::_has_partition_count_adl) + return tag_invoke::partition_count(g); else if constexpr (is_integral_v>) - return vertex_id_t(); + return vertex_id_t(1); else - return size_t(0); + return size_t(1); } -// vertices(g,pid) -> range of vertices; graph container must override if it supports bi-partite or +// vertices(g,pid) -> range of vertices; graph container must override if it supports bi-partite or // multi-partite graph. // namespace tag_invoke { @@ -910,7 +950,7 @@ namespace tag_invoke { concept _has_vertices_pid_adl = requires(G&& g, partition_id_t pid) { { vertices(g, pid) }; }; -} +} // namespace tag_invoke /** * @brief Get's the range of vertices for a partition in a graph. @@ -934,11 +974,173 @@ auto vertices(G&& g, partition_id_t pid) { if constexpr (tag_invoke::_has_vertices_pid_adl) return tag_invoke::vertices(g, pid); else - return subrange(end(vertices(g)), end(vertices(g))); + return vertices(g); +} + +template +using partition_vertex_range_t = decltype(vertices(declval(), declval>())); + + +template +struct _partition_vertex_id { + partition_id_t partition_id; // + vertex_id_t vertex_id; // vertex id within the partition_id +}; + +template +using partition_vertex_id_t = _partition_vertex_id; + + +// +// partition_vertex_id(g,uid) -> partition_vertex_id_t<_G> +// default = partition_vertex_id(g,vertex_id(g,uid)) +// +// partition_vertex_id(g,ui) -> partition_vertex_id_t<_G> +// default = partition_vertex_id(g,vertex_id(g,ui)) +// +namespace _Partition_vertex_id { +# if defined(__clang__) || defined(__EDG__) // TRANSITION, VSO-1681199 + void vertex_id() = delete; // Block unqualified name lookup +# else // ^^^ no workaround / workaround vvv + void vertex_id(); +# endif // ^^^ workaround ^^^ + + template + concept _Has_UId_member = requires(_G&& __g, vertex_id_t<_G> uid) { + { _Fake_copy_init(__g.partition_vertex_id(uid)) }; + }; + + template + concept _Has_UId_ADL = _Has_class_or_enum_type<_G> // + && requires(_G&& __g, vertex_id_t<_G> uid) { + { _Fake_copy_init(partition_vertex_id(__g, uid)) }; // intentional ADL + }; + + template + concept _Has_UIter_member = requires(_G&& __g, vertex_iterator_t<_G> ui) { + { _Fake_copy_init(__g.partition_vertex_id(ui)) }; + }; + + template + concept _Has_UIter_ADL = _Has_class_or_enum_type<_G> // + && requires(_G&& __g, vertex_iterator_t<_G> ui) { + { _Fake_copy_init(partition_vertex_id(__g, ui)) }; // intentional ADL + }; + + class _Cpo { + private: + enum class _StId { _None, _Member, _Non_member }; + enum class _StIter { _None, _Member, _Non_member }; + + template + [[nodiscard]] static consteval _Choice_t<_StId> _ChooseId() noexcept { + static_assert(is_lvalue_reference_v<_G>); + using _UnCV = remove_cvref_t<_G>; + + if constexpr (_Has_UId_member<_G>) { + return {_StId::_Member, + noexcept(_Fake_copy_init(declval<_G>().partition_vertex_id(declval>())))}; + } else if constexpr (_Has_UId_ADL<_G>) { + return {_StId::_Non_member, noexcept(_Fake_copy_init(partition_vertex_id( + declval<_G>(), declval>())))}; // intentional ADL + } else { + return {_StId::_None}; + } + } + + template + [[nodiscard]] static consteval _Choice_t<_StIter> _ChooseIter() noexcept { + static_assert(is_lvalue_reference_v<_G>); + using _UnCV = remove_cvref_t<_G>; + + if constexpr (_Has_UIter_member<_G>) { + return {_StIter::_Member, + noexcept(_Fake_copy_init(declval<_G>().partition_vertex_id(declval>())))}; + } else if constexpr (_Has_UIter_ADL<_G>) { + return {_StIter::_Non_member, noexcept(_Fake_copy_init(partition_vertex_id( + declval<_G>(), declval>())))}; // intentional ADL + } else { + return {_StIter::_None}; + } + } + + template + static constexpr _Choice_t<_StId> _ChoiceId = _ChooseId<_G>(); + template + static constexpr _Choice_t<_StIter> _ChoiceIter = _ChooseIter<_G>(); + + public: + /** + * @brief Get's the partition_id and relative vertex_id given a vertex_id_t. + * + * Complexity: O(1) + * + * Default implementation: a single partition is assumed and {0, uid} is returned for the + * partition_id and relative vertex_id respectfully. + * + * This is a customization point function that must be overriden if graph G supports bi-partite + * or multi-partite graphs. + * + * @tparam G The graph type. + * @param g A graph instance. + * @param uid The vertex_id. + * + * @return partition_vertex_id_t with the partition_id and relative vertex_id in the partition + * for the vertex_id passed. + */ + template + requires(_ChoiceId<_G&>._Strategy != _StId::_None) + [[nodiscard]] constexpr auto operator()(_G&& __g, vertex_id_t<_G> uid) const noexcept(_ChoiceId<_G&>._No_throw) { + constexpr _StId _Strat = _ChoiceId<_G&>._Strategy; + + if constexpr (_Strat == _StId::_Member) { + return __g.partition_vertex_id(uid); + } else if constexpr (_Strat == _StId::_Non_member) { + return partition_vertex_id(__g, uid); // intentional ADL + } else { + return partition_vertex_id_t<_G>{0, uid}; + } + } + + /** + * @brief Get's the partition_id and relative vertex_id given a vertex_iterator_t. + * + * Complexity: O(1) + * + * Default implementation: a single partition is assumed and {0, vertex_id(g,ui)} is returned + * for the partition_id and relative vertex_id respectfully. + * + * This is a customization point function that must be overriden if graph G supports bi-partite + * or multi-partite graphs. + * + * @tparam G The graph type. + * @param g A graph instance. + * @param ui The vertex_iterator. + * + * @return partition_vertex_id_t with the partition_id and relative vertex_id in the partition + * for the vertex_id passed. + */ + template + requires(_ChoiceIter<_G&>._Strategy != _StIter::_None) + [[nodiscard]] constexpr auto operator()(_G&& __g, vertex_iterator_t<_G> ui) const + noexcept(_ChoiceIter<_G&>._No_throw) { + constexpr _StIter _Strat = _ChoiceIter<_G&>._Strategy; + + if constexpr (_Strat == _StIter::_Member) { + return __g.partition_vertex_id(ui); + } else if constexpr (_Strat == _StIter::_Non_member) { + return partition_vertex_id(__g, ui); // intentional ADL + } else { + return (*this)(__g, vertex_id(__g, ui)); // use partition_vertex_id(g, vertex_id(g,ui)) + } + } + }; +} // namespace _Partition_vertex_id + +inline namespace _Cpos { + inline constexpr _Partition_vertex_id::_Cpo partition_vertex_id; } -template -using vertex_partition_range_t = decltype(vertices(declval(), declval>())); # endif From fd88066dc7558c203c0c49816b39926610486924 Mon Sep 17 00:00:00 2001 From: Phil Ratzloff Date: Sun, 1 Oct 2023 16:51:11 -0400 Subject: [PATCH 05/15] Add find_partition_vertex(g,puid) CPO --- include/graph/detail/graph_cpo.hpp | 88 +++++++++++++++++++++++++++++- 1 file changed, 86 insertions(+), 2 deletions(-) diff --git a/include/graph/detail/graph_cpo.hpp b/include/graph/detail/graph_cpo.hpp index c2ea2a8..2347c0e 100644 --- a/include/graph/detail/graph_cpo.hpp +++ b/include/graph/detail/graph_cpo.hpp @@ -1000,9 +1000,9 @@ using partition_vertex_id_t = _partition_vertex_id; // namespace _Partition_vertex_id { # if defined(__clang__) || defined(__EDG__) // TRANSITION, VSO-1681199 - void vertex_id() = delete; // Block unqualified name lookup + void partition_vertex_id() = delete; // Block unqualified name lookup # else // ^^^ no workaround / workaround vvv - void vertex_id(); + void partition_vertex_id(); # endif // ^^^ workaround ^^^ template @@ -1142,6 +1142,90 @@ inline namespace _Cpos { } +// +// find_partition_vertex(g,puid) -> vertex_t<_G> +// default = find_vertex(g, puid.vertex_id) +// +namespace _Find_partition_vertex { +# if defined(__clang__) || defined(__EDG__) // TRANSITION, VSO-1681199 + void find_partition_vertex() = delete; // Block unqualified name lookup +# else // ^^^ no workaround / workaround vvv + void find_partition_vertex(); +# endif // ^^^ workaround ^^^ + + template + concept _Has_UId_member = requires(_G&& __g, partition_vertex_id_t<_G> puid) { + { _Fake_copy_init(__g.find_partition_vertex(puid)) }; + }; + + template + concept _Has_UId_ADL = _Has_class_or_enum_type<_G> // + && requires(_G&& __g, partition_vertex_id_t<_G> puid) { + { _Fake_copy_init(find_partition_vertex(__g, puid)) }; // intentional ADL + }; + + class _Cpo { + private: + enum class _StId { _None, _Member, _Non_member }; + + template + [[nodiscard]] static consteval _Choice_t<_StId> _ChooseId() noexcept { + static_assert(is_lvalue_reference_v<_G>); + using _UnCV = remove_cvref_t<_G>; + + if constexpr (_Has_UId_member<_G>) { + return {_StId::_Member, + noexcept(_Fake_copy_init(declval<_G>().find_partition_vertex(declval>())))}; + } else if constexpr (_Has_UId_ADL<_G>) { + return {_StId::_Non_member, noexcept(_Fake_copy_init(find_partition_vertex( + declval<_G>(), declval>())))}; // intentional ADL + } else { + return {_StId::_None}; + } + } + + template + static constexpr _Choice_t<_StId> _ChoiceId = _ChooseId<_G>(); + + public: + /** + * @brief Find a vertex given a partition_vertex_id_t + * + * Complexity: O(1) + * + * Default implementation: a single partition is assumed find_vertex(g,puid) is used to find the + * vertex. + * + * This is a customization point function that must be overriden if graph G supports bi-partite + * or multi-partite graphs. + * + * @tparam G The graph type. + * @param g A graph instance. + * @param puid The partition_vertex_id to find. + * + * @return a vertex_iterator_t for the partition_vertex_id_t passed. If it doesn't + * exist end(vertices(g)) will be returned. + */ + template + requires(_ChoiceId<_G&>._Strategy != _StId::_None) + [[nodiscard]] constexpr auto operator()(_G&& __g, partition_vertex_id_t<_G> puid) const + noexcept(_ChoiceId<_G&>._No_throw) { + constexpr _StId _Strat = _ChoiceId<_G&>._Strategy; + + if constexpr (_Strat == _StId::_Member) { + return __g.find_partition_vertex(puid); + } else if constexpr (_Strat == _StId::_Non_member) { + return find_partition_vertex(__g, puid); // intentional ADL + } else { + return find_vertex(__g, puid.vertex_id); // assume 1 partition with all vertices + } + } + }; +} // namespace _Find_partition_vertex + +inline namespace _Cpos { + inline constexpr _Find_partition_vertex::_Cpo find_partition_vertex; +} # endif From 7228de0d1709301c0d3e556e186b032616033e0f Mon Sep 17 00:00:00 2001 From: Phil Ratzloff Date: Sun, 1 Oct 2023 17:02:05 -0400 Subject: [PATCH 06/15] Add edges(g,uv,p) and edges(g,uid,p) CPOs --- include/graph/detail/graph_cpo.hpp | 70 ++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/include/graph/detail/graph_cpo.hpp b/include/graph/detail/graph_cpo.hpp index 2347c0e..52424e9 100644 --- a/include/graph/detail/graph_cpo.hpp +++ b/include/graph/detail/graph_cpo.hpp @@ -1227,6 +1227,76 @@ inline namespace _Cpos { inline constexpr _Find_partition_vertex::_Cpo find_partition_vertex; } + +template +using partition_edge_range_t = vertex_edge_range_t; + + +// +// edges(g,u) -> vertex_edge_range_t +// edges(g,uid) -> vertex_edge_range_t +// default = edges(g,*find_vertex(g,uid)) +// +// vertex_edge_range_t = edges(g,u) +// vertex_edge_iterator_t = ranges::iterator_t> +// edge_t = ranges::range_value_t> +// edge_reference_t = ranges::range_reference_t> +// +namespace tag_invoke { + //TAG_INVOKE_DEF(edges); + + template + concept _has_edges_vtxref_part_adl = requires(G&& g, vertex_reference_t u, partition_id_t p) { + { edges(g, u, p) }; + }; + + template + concept _has_edges_vtxid_part_adl = requires(G&& g, vertex_id_t uid, partition_id_t p) { + { edges(g, uid, p) }; + }; +} // namespace tag_invoke + +/** + * @brief Get the outgoing edges of a vertex. + * + * Complexity: O(1) + * + * Default implementation: n/a. This must be specialized for each graph type. + * + * @tparam G The graph type. + * @param g A graph instance. + * @param u Vertex reference. + * @return A range of the outgoing edges. +*/ +template +requires tag_invoke::_has_edges_vtxref_part_adl +auto edges(G&& g, vertex_reference_t u, partition_id_t p) -> decltype(tag_invoke::edges(g, u, p)) { + return tag_invoke::edges(g, u, p); // graph author must define +} + +/** + * @brief Get the outgoing edges of a vertex id. + * + * Complexity: O(1) + * + * Default implementation: edges(g, *find_vertex(g, uid)) + * + * @tparam G The graph type. + * @param g A graph instance. + * @param uid Vertex id. + * @return A range of the outgoing edges. +*/ +template +requires tag_invoke::_has_edges_vtxid_part_adl +auto edges(G&& g, vertex_id_t uid, partition_id_t p) -> decltype(tag_invoke::edges(g, uid, p)) { + if constexpr (tag_invoke::_has_edges_vtxid_part_adl) + return tag_invoke::edges(g, uid, p); + else + return edges(g, *find_vertex(g, uid), p); +} + + + # endif From 4ddca8b2d487defdc5e9f7588fd5d787df534491 Mon Sep 17 00:00:00 2001 From: Phil Ratzloff Date: Sun, 1 Oct 2023 17:18:28 -0400 Subject: [PATCH 07/15] Add partition_target_id(g,uv) and partition_source_id(g,uv) CPOs --- include/graph/detail/graph_cpo.hpp | 181 ++++++++++++++++++++++++++++- 1 file changed, 177 insertions(+), 4 deletions(-) diff --git a/include/graph/detail/graph_cpo.hpp b/include/graph/detail/graph_cpo.hpp index 52424e9..6ffa10a 100644 --- a/include/graph/detail/graph_cpo.hpp +++ b/include/graph/detail/graph_cpo.hpp @@ -1228,7 +1228,7 @@ inline namespace _Cpos { } -template +template using partition_edge_range_t = vertex_edge_range_t; @@ -1261,7 +1261,7 @@ namespace tag_invoke { * * Complexity: O(1) * - * Default implementation: n/a. This must be specialized for each graph type. + * Default implementation: edges(g,u) * * @tparam G The graph type. * @param g A graph instance. @@ -1271,7 +1271,10 @@ namespace tag_invoke { template requires tag_invoke::_has_edges_vtxref_part_adl auto edges(G&& g, vertex_reference_t u, partition_id_t p) -> decltype(tag_invoke::edges(g, u, p)) { - return tag_invoke::edges(g, u, p); // graph author must define + if constexpr (tag_invoke::_has_edges_vtxref_part_adl) + return tag_invoke::edges(g, u, p); // graph author must define + else + return edges(g, u); } /** @@ -1279,7 +1282,7 @@ auto edges(G&& g, vertex_reference_t u, partition_id_t p) -> decltype(tag_ * * Complexity: O(1) * - * Default implementation: edges(g, *find_vertex(g, uid)) + * Default implementation: edges(g, *find_vertex(g, uid), p) * * @tparam G The graph type. * @param g A graph instance. @@ -1296,6 +1299,176 @@ auto edges(G&& g, vertex_id_t uid, partition_id_t p) -> decltype(tag_invok } +// +// partition_target_id(g,puid) -> partition_vertex_id_t<_G> +// default = partition_vertex_id_t{0, target_id(__g, uv)} +// +namespace _Partition_target_id { +# if defined(__clang__) || defined(__EDG__) // TRANSITION, VSO-1681199 + void partition_target_id() = delete; // Block unqualified name lookup +# else // ^^^ no workaround / workaround vvv + void partition_target_id(); +# endif // ^^^ workaround ^^^ + + template + concept _Has_UVRef__member = requires(_G&& __g, edge_reference_t<_G> uv) { + { _Fake_copy_init(__g.partition_target_id(uv)) }; + }; + + template + concept _Has_UVRef__ADL = _Has_class_or_enum_type<_G> // + && requires(_G&& __g, edge_reference_t<_G> uv) { + { _Fake_copy_init(partition_target_id(__g, uv)) }; // intentional ADL + }; + + class _Cpo { + private: + enum class _StRef { _None, _Member, _Non_member }; + + template + [[nodiscard]] static consteval _Choice_t<_StRef> _ChooseRef() noexcept { + static_assert(is_lvalue_reference_v<_G>); + using _UnCV = remove_cvref_t<_G>; + + if constexpr (_Has_UVRef__member<_G>) { + return {_StRef::_Member, + noexcept(_Fake_copy_init(declval<_G>().partition_target_id(declval>())))}; + } else if constexpr (_Has_UVRef__ADL<_G>) { + return {_StRef::_Non_member, noexcept(_Fake_copy_init(partition_target_id( + declval<_G>(), declval>())))}; // intentional ADL + } else { + return {_StRef::_None}; + } + } + + template + static constexpr _Choice_t<_StRef> _ChoiceId = _ChooseRef<_G>(); + + public: + /** + * @brief Find a vertex given a edge_reference_t + * + * Complexity: O(1) + * + * Default implementation: a single partition is assumed find_vertex(g,uv) is used to find the + * vertex. + * + * This is a customization point function that must be overriden if graph G supports bi-partite + * or multi-partite graphs. + * + * @tparam G The graph type. + * @param g A graph instance. + * @param uv The partition_vertex_id to find. + * + * @return a vertex_iterator_t for the edge_reference_t passed. If it doesn't + * exist end(vertices(g)) will be returned. + */ + template + requires(_ChoiceId<_G&>._Strategy != _StRef::_None) + [[nodiscard]] constexpr auto operator()(_G&& __g, edge_reference_t<_G> uv) const + noexcept(_ChoiceId<_G&>._No_throw) { + constexpr _StRef _Strat = _ChoiceId<_G&>._Strategy; + + if constexpr (_Strat == _StRef::_Member) { + return __g.partition_target_id(uv); + } else if constexpr (_Strat == _StRef::_Non_member) { + return partition_target_id(__g, uv); // intentional ADL + } else { + return partition_vertex_id_t<_G>{0, target_id(__g, uv)}; // assume 1 partition with all vertices + } + } + }; +} // namespace _Partition_target_id + +inline namespace _Cpos { + inline constexpr _Partition_target_id::_Cpo partition_target_id; +} + + +// +// partition_source_id(g,puid) -> partition_vertex_id_t<_G> +// default = partition_vertex_id_t{0, source_id(__g, uv)} +// +namespace _Partition_source_id { +# if defined(__clang__) || defined(__EDG__) // TRANSITION, VSO-1681199 + void partition_source_id() = delete; // Block unqualified name lookup +# else // ^^^ no workaround / workaround vvv + void partition_source_id(); +# endif // ^^^ workaround ^^^ + + template + concept _Has_UVRef__member = requires(_G&& __g, edge_reference_t<_G> uv) { + { _Fake_copy_init(__g.partition_source_id(uv)) }; + }; + + template + concept _Has_UVRef__ADL = _Has_class_or_enum_type<_G> // + && requires(_G&& __g, edge_reference_t<_G> uv) { + { _Fake_copy_init(partition_source_id(__g, uv)) }; // intentional ADL + }; + + class _Cpo { + private: + enum class _StRef { _None, _Member, _Non_member }; + + template + [[nodiscard]] static consteval _Choice_t<_StRef> _ChooseRef() noexcept { + static_assert(is_lvalue_reference_v<_G>); + using _UnCV = remove_cvref_t<_G>; + + if constexpr (_Has_UVRef__member<_G>) { + return {_StRef::_Member, + noexcept(_Fake_copy_init(declval<_G>().partition_source_id(declval>())))}; + } else if constexpr (_Has_UVRef__ADL<_G>) { + return {_StRef::_Non_member, noexcept(_Fake_copy_init(partition_source_id( + declval<_G>(), declval>())))}; // intentional ADL + } else { + return {_StRef::_None}; + } + } + + template + static constexpr _Choice_t<_StRef> _ChoiceId = _ChooseRef<_G>(); + + public: + /** + * @brief Find a vertex given a edge_reference_t + * + * Complexity: O(1) + * + * Default implementation: a single partition is assumed find_vertex(g,uv) is used to find the + * vertex. + * + * This is a customization point function that must be overriden if graph G supports bi-partite + * or multi-partite graphs. + * + * @tparam G The graph type. + * @param g A graph instance. + * @param uv The partition_vertex_id to find. + * + * @return a vertex_iterator_t for the edge_reference_t passed. If it doesn't + * exist end(vertices(g)) will be returned. + */ + template + requires(_ChoiceId<_G&>._Strategy != _StRef::_None) + [[nodiscard]] constexpr auto operator()(_G&& __g, edge_reference_t<_G> uv) const + noexcept(_ChoiceId<_G&>._No_throw) { + constexpr _StRef _Strat = _ChoiceId<_G&>._Strategy; + + if constexpr (_Strat == _StRef::_Member) { + return __g.partition_source_id(uv); + } else if constexpr (_Strat == _StRef::_Non_member) { + return partition_source_id(__g, uv); // intentional ADL + } else { + return partition_vertex_id_t<_G>{0, source_id(__g, uv)}; // assume 1 partition with all vertices + } + } + }; +} // namespace _Partition_source_id + +inline namespace _Cpos { + inline constexpr _Partition_source_id::_Cpo partition_source_id; +} # endif From 8d09e1be20ca1e7f6a2256d708f4b10c38e922a2 Mon Sep 17 00:00:00 2001 From: Phil Ratzloff Date: Thu, 26 Oct 2023 13:49:02 -0400 Subject: [PATCH 08/15] Add graph_error --- include/graph/graph.hpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/include/graph/graph.hpp b/include/graph/graph.hpp index bc9419e..2a06799 100644 --- a/include/graph/graph.hpp +++ b/include/graph/graph.hpp @@ -20,6 +20,7 @@ #include #include #include +#include #include "detail/graph_cpo.hpp" @@ -39,6 +40,7 @@ // VR Vertex Range // VI ui,vi Vertex Iterator // VVF Vertex Value Function: vvf(u) -> value +// VVP vvp Vertex Value Projection function vvp(x) --> vertex_descriptor<> // // E Edge type // uv,vw Edge reference @@ -46,6 +48,7 @@ // ER Edge Range // EI uvi,vwi Edge iterator // EVF evf Edge Value Function: evf(uv) -> value +// EVP evp Edge Value Production function: evp(y) -> edge_descriptor<> // #ifndef GRAPH_HPP @@ -308,6 +311,15 @@ template concept ordered_edge = is_ordered_edge_v; +// +// graph_error +// +class graph_error : public runtime_error { +public: + explicit graph_error(const string& what_arg) : runtime_error(what_arg) {} + explicit graph_error(const char* what_arg) : runtime_error(what_arg) {} +}; + } // namespace std::graph #endif //GRAPH_HPP From 758dec5b0d9d788a765c3eb56c432d1d8b94e5da Mon Sep 17 00:00:00 2001 From: Phil Ratzloff Date: Thu, 26 Oct 2023 14:01:55 -0400 Subject: [PATCH 09/15] Fix merge error --- include/graph/container/compressed_graph.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/graph/container/compressed_graph.hpp b/include/graph/container/compressed_graph.hpp index 4bc6e1e..c1479e2 100644 --- a/include/graph/container/compressed_graph.hpp +++ b/include/graph/container/compressed_graph.hpp @@ -768,13 +768,13 @@ class compressed_graph_base part_index_; // row_index_[part_index_[p]] is the first row of partition p; holds +1 extra for terminating row (size(row_index_)) private: // tag_invoke properties - friend constexpr vertices_type tag_invoke(::std::graph::tag_invoke::vertices_fn_t, csr_graph_base& g) { + friend constexpr vertices_type tag_invoke(::std::graph::tag_invoke::vertices_fn_t, compressed_graph_base& g) { if (g.row_index_.empty()) return vertices_type(g.row_index_); // really empty else return vertices_type(g.row_index_.begin(), g.row_index_.end() - 1); // don't include terminating row } - friend constexpr const_vertices_type tag_invoke(::std::graph::tag_invoke::vertices_fn_t, const csr_graph_base& g) { + friend constexpr const_vertices_type tag_invoke(::std::graph::tag_invoke::vertices_fn_t, const compressed_graph_base& g) { if (g.row_index_.empty()) return const_vertices_type(g.row_index_); // really empty else From 543fa4d1f3438dce42b32d925181d27e985bb73d Mon Sep 17 00:00:00 2001 From: Phil Ratzloff Date: Thu, 26 Oct 2023 16:58:29 -0400 Subject: [PATCH 10/15] Formatting and minor edits for compressed_graph --- include/graph/container/compressed_graph.hpp | 106 +++++++++++-------- 1 file changed, 63 insertions(+), 43 deletions(-) diff --git a/include/graph/container/compressed_graph.hpp b/include/graph/container/compressed_graph.hpp index c1479e2..c2f16d4 100644 --- a/include/graph/container/compressed_graph.hpp +++ b/include/graph/container/compressed_graph.hpp @@ -164,8 +164,10 @@ class csr_row_values { constexpr void swap(csr_row_values& other) noexcept { swap(v_, other.v_); } template - //requires views::copyable_vertex>, VId, VV> + //requires copyable_vertex>, VId, VV> constexpr void load_row_values(const VRng& vrng, VProj projection, size_type vertex_count) { + static_assert(copyable_vertex>, VId, VV>); + if constexpr (ranges::sized_range) vertex_count = max(vertex_count, ranges::size(vrng)); resize(ranges::size(vrng)); @@ -182,8 +184,16 @@ class csr_row_values { } template - //requires views::copyable_vertex>, VId, VV> + //requires copyable_vertex>, VId, VV> constexpr void load_row_values(VRng&& vrng, VProj projection, size_type vertex_count) { + //using result_t = invoke_result_t>; + //using desc_t = vertex_descriptor; + + //result_t result; + //desc_t desc{result}; + //static_assert(convertible_to); + //static_assert(copyable_vertex>, VId, VV>); + if constexpr (ranges::sized_range) vertex_count = max(vertex_count, ranges::size(vrng)); resize(ranges::size(vrng)); @@ -419,18 +429,18 @@ class compressed_graph_base using size_type = ranges::range_size_t; public: // Construction/Destruction - constexpr compressed_graph_base() = default; + constexpr compressed_graph_base() = default; constexpr compressed_graph_base(const compressed_graph_base&) = default; constexpr compressed_graph_base(compressed_graph_base&&) = default; - constexpr ~compressed_graph_base() = default; + constexpr ~compressed_graph_base() = default; constexpr compressed_graph_base& operator=(const compressed_graph_base&) = default; constexpr compressed_graph_base& operator=(compressed_graph_base&&) = default; constexpr compressed_graph_base(const Alloc& alloc) - : row_values_base(alloc), col_values_base(alloc), row_index_(alloc), col_index_(alloc), part_index_(alloc) { + : row_values_base(alloc), col_values_base(alloc), row_index_(alloc), col_index_(alloc), part_index_(alloc) { set_default_partition(); - } + } /** * @brief Constructor that takes a edge range to create the CSR graph. @@ -472,10 +482,10 @@ class compressed_graph_base //requires copyable_edge>, VId, EV> && // copyable_vertex>, VId, VV> constexpr compressed_graph_base(const ERng& erng, - const VRng& vrng, - EProj eprojection = {}, // eproj(eval) -> {source_id,target_id [,value]} - VProj vprojection = {}, // vproj(vval) -> {target_id [,value]} - const Alloc& alloc = Alloc()) + const VRng& vrng, + EProj eprojection = {}, // eproj(eval) -> {source_id,target_id [,value]} + VProj vprojection = {}, // vproj(vval) -> {target_id [,value]} + const Alloc& alloc = Alloc()) : row_values_base(alloc), col_values_base(alloc), row_index_(alloc), col_index_(alloc), part_index_(alloc) { load(erng, vrng, eprojection, vprojection); @@ -527,7 +537,7 @@ class compressed_graph_base * @param vprojection Projection function for @c vrng values */ template - //requires views::copyable_vertex>, VId, VV> + //requires copyable_vertex>, VId, VV> constexpr void load_vertices(const VRng& vrng, VProj vprojection, size_type vertex_count = 0) { row_values_base::load_row_values(vrng, vprojection, max(vertex_count, ranges::size(vrng))); set_default_partition(); @@ -547,7 +557,7 @@ class compressed_graph_base * @param vprojection Projection function for @c vrng values */ template - //requires views::copyable_vertex>, VId, VV> + //requires copyable_vertex>, VId, VV> constexpr void load_vertices(VRng& vrng, VProj vprojection = {}, size_type vertex_count = 0) { row_values_base::load_row_values(vrng, vprojection, max(vertex_count, ranges::size(vrng))); set_default_partition(); @@ -588,7 +598,7 @@ class compressed_graph_base * @param eprojection Edge projection function that returns a @ copyable_edge_t for an element in @c erng */ template - //requires views::copyable_edge>, VId, EV> + //requires copyable_edge>, VId, EV> constexpr void load_edges(ERng&& erng, EProj eprojection = {}, size_type vertex_count = 0, size_type edge_count = 0) { // should only be loading into an empty graph assert(row_index_.empty() && col_index_.empty() && static_cast(*this).empty()); @@ -642,7 +652,7 @@ class compressed_graph_base // The only diff with this and ERng&& is v_.push_back vs. v_.emplace_back template - //requires views::copyable_edge>, VId, EV> + //requires copyable_edge>, VId, EV> constexpr void load_edges(const ERng& erng, EProj eprojection = {}, size_type vertex_count = 0, size_type edge_count = 0) { // should only be loading into an empty graph @@ -709,7 +719,7 @@ class compressed_graph_base * @param vprojection Vertex projection function object */ template - //requires views::copyable_edge>, VId, EV> && + //requires copyable_edge>, VId, EV> && // views::copyable_vertex>, VId, VV> constexpr void load(const ERng& erng, const VRng& vrng, EProj eprojection = {}, VProj vprojection = {}) { load_edges(erng, eprojection); @@ -774,7 +784,8 @@ class compressed_graph_base else return vertices_type(g.row_index_.begin(), g.row_index_.end() - 1); // don't include terminating row } - friend constexpr const_vertices_type tag_invoke(::std::graph::tag_invoke::vertices_fn_t, const compressed_graph_base& g) { + friend constexpr const_vertices_type tag_invoke(::std::graph::tag_invoke::vertices_fn_t, + const compressed_graph_base& g) { if (g.row_index_.empty()) return const_vertices_type(g.row_index_); // really empty else @@ -871,10 +882,10 @@ class compressed_graph : public compressed_graph_base requires copyable_edge>, VId, EV> - constexpr compressed_graph(const graph_value_type& value, const ERng& erng, EProj eprojection, const Alloc& alloc = Alloc()) + constexpr compressed_graph(const graph_value_type& value, + const ERng& erng, + EProj eprojection, + const Alloc& alloc = Alloc()) : base_type(erng, eprojection, alloc), value_(value) {} template requires copyable_edge>, VId, EV> - constexpr compressed_graph(graph_value_type&& value, const ERng& erng, EProj eprojection, const Alloc& alloc = Alloc()) + constexpr compressed_graph(graph_value_type&& value, + const ERng& erng, + EProj eprojection, + const Alloc& alloc = Alloc()) : base_type(erng, eprojection, alloc), value_(move(value)) {} // compressed_graph( erng, vrng, eprojection, vprojection, alloc) @@ -914,32 +933,32 @@ class compressed_graph : public compressed_graph_base>, VId, EV> && copyable_vertex>, VId, VV> constexpr compressed_graph(const ERng& erng, - const VRng& vrng, - EProj eprojection = {}, - VProj vprojection = {}, - const Alloc& alloc = Alloc()) + const VRng& vrng, + EProj eprojection = {}, + VProj vprojection = {}, + const Alloc& alloc = Alloc()) : base_type(erng, vrng, eprojection, vprojection, alloc) {} template requires copyable_edge>, VId, EV> && copyable_vertex>, VId, VV> constexpr compressed_graph(const graph_value_type& value, - const ERng& erng, - const VRng& vrng, - EProj eprojection = {}, - VProj vprojection = {}, - const Alloc& alloc = Alloc()) + const ERng& erng, + const VRng& vrng, + EProj eprojection = {}, + VProj vprojection = {}, + const Alloc& alloc = Alloc()) : base_type(erng, vrng, eprojection, vprojection, alloc), value_(value) {} template requires copyable_edge>, VId, EV> && copyable_vertex>, VId, VV> constexpr compressed_graph(graph_value_type&& value, - const ERng& erng, - const VRng& vrng, - EProj eprojection = {}, - VProj vprojection = {}, - const Alloc& alloc = Alloc()) + const ERng& erng, + const VRng& vrng, + EProj eprojection = {}, + VProj vprojection = {}, + const Alloc& alloc = Alloc()) : base_type(erng, vrng, eprojection, vprojection, alloc), value_(move(value)) {} @@ -969,7 +988,8 @@ class compressed_graph : public compressed_graph_base -class compressed_graph : public compressed_graph_base { +class compressed_graph + : public compressed_graph_base { public: // Types using graph_type = compressed_graph; using base_type = compressed_graph_base; @@ -981,10 +1001,10 @@ class compressed_graph : public compressed_gra using value_type = void; public: // Construction/Destruction - constexpr compressed_graph() = default; + constexpr compressed_graph() = default; constexpr compressed_graph(const compressed_graph&) = default; constexpr compressed_graph(compressed_graph&&) = default; - constexpr ~compressed_graph() = default; + constexpr ~compressed_graph() = default; constexpr compressed_graph& operator=(const compressed_graph&) = default; constexpr compressed_graph& operator=(compressed_graph&&) = default; @@ -1000,10 +1020,10 @@ class compressed_graph : public compressed_gra requires copyable_edge>, VId, EV> && copyable_vertex>, VId, VV> constexpr compressed_graph(const ERng& erng, - const VRng& vrng, - EProj eprojection = {}, - VProj vprojection = {}, - const Alloc& alloc = Alloc()) + const VRng& vrng, + EProj eprojection = {}, + VProj vprojection = {}, + const Alloc& alloc = Alloc()) : base_type(erng, vrng, eprojection, vprojection, alloc) {} // initializer list using edge_descriptor From b1186611659a39dc4e87c862edde6958b032f704 Mon Sep 17 00:00:00 2001 From: Phil Ratzloff Date: Fri, 27 Oct 2023 11:19:57 -0400 Subject: [PATCH 11/15] Fix warnings --- include/graph/algorithm/pagerank.hpp | 6 +++--- include/graph/detail/graph_cpo.hpp | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/include/graph/algorithm/pagerank.hpp b/include/graph/algorithm/pagerank.hpp index 786de30..6af40e1 100644 --- a/include/graph/algorithm/pagerank.hpp +++ b/include/graph/algorithm/pagerank.hpp @@ -72,7 +72,7 @@ void pagerank( std::vector iweights(size(vertices(g))); // Initialize the data, pagerank as 1/n_vertices. - std::ranges::fill(scores, 1.0f / size(vertices(g))); + std::ranges::fill(scores, 1.0 / double(size(vertices(g)))); for (auto&& [uid, u] : views::vertexlist(g)) { // Calculate the degree of each vertex. @@ -80,7 +80,7 @@ void pagerank( for (auto&& uv : edges(g, u)) { ++edge_cnt; } - degrees[uid] = edge_cnt; + degrees[uid] = static_cast(edge_cnt); // Find the sum of outgoing weights. weight_type val = 0; @@ -102,7 +102,7 @@ void pagerank( dsum += (iweights[uid] == 0) ? damping_factor * scores[uid] : 0; } - std::ranges::fill(scores, (1 - damping_factor + dsum) / size(vertices(g))); + std::ranges::fill(scores, (1 - damping_factor + dsum) / double(size(vertices(g)))); double error = 0; for (auto&& [uid, vid, uv, val] : views::edgelist(g, weight_fn)) { diff --git a/include/graph/detail/graph_cpo.hpp b/include/graph/detail/graph_cpo.hpp index 6ffa10a..0fffe1d 100644 --- a/include/graph/detail/graph_cpo.hpp +++ b/include/graph/detail/graph_cpo.hpp @@ -1035,7 +1035,7 @@ namespace _Partition_vertex_id { template [[nodiscard]] static consteval _Choice_t<_StId> _ChooseId() noexcept { static_assert(is_lvalue_reference_v<_G>); - using _UnCV = remove_cvref_t<_G>; + //using _UnCV = remove_cvref_t<_G>; if constexpr (_Has_UId_member<_G>) { return {_StId::_Member, @@ -1051,7 +1051,7 @@ namespace _Partition_vertex_id { template [[nodiscard]] static consteval _Choice_t<_StIter> _ChooseIter() noexcept { static_assert(is_lvalue_reference_v<_G>); - using _UnCV = remove_cvref_t<_G>; + //using _UnCV = remove_cvref_t<_G>; if constexpr (_Has_UIter_member<_G>) { return {_StIter::_Member, @@ -1171,7 +1171,7 @@ namespace _Find_partition_vertex { template [[nodiscard]] static consteval _Choice_t<_StId> _ChooseId() noexcept { static_assert(is_lvalue_reference_v<_G>); - using _UnCV = remove_cvref_t<_G>; + //using _UnCV = remove_cvref_t<_G>; if constexpr (_Has_UId_member<_G>) { return {_StId::_Member, @@ -1328,7 +1328,7 @@ namespace _Partition_target_id { template [[nodiscard]] static consteval _Choice_t<_StRef> _ChooseRef() noexcept { static_assert(is_lvalue_reference_v<_G>); - using _UnCV = remove_cvref_t<_G>; + //using _UnCV = remove_cvref_t<_G>; if constexpr (_Has_UVRef__member<_G>) { return {_StRef::_Member, @@ -1414,7 +1414,7 @@ namespace _Partition_source_id { template [[nodiscard]] static consteval _Choice_t<_StRef> _ChooseRef() noexcept { static_assert(is_lvalue_reference_v<_G>); - using _UnCV = remove_cvref_t<_G>; + //using _UnCV = remove_cvref_t<_G>; if constexpr (_Has_UVRef__member<_G>) { return {_StRef::_Member, From 63367795a2de65a6c29b0ca5b535b0d8c9798616 Mon Sep 17 00:00:00 2001 From: Phil Ratzloff Date: Fri, 27 Oct 2023 11:33:32 -0400 Subject: [PATCH 12/15] Change Ninja to Ninja Multi-Config generator --- CMakePresets.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakePresets.json b/CMakePresets.json index 02c0151..3e5aac4 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -9,7 +9,7 @@ { "name": "linux-common", "hidden": true, - "generator": "Ninja", + "generator": "Ninja Multi-Config", "binaryDir": "${sourceDir}/out/build/${presetName}", "cacheVariables": { "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}" @@ -121,8 +121,8 @@ { "name": "macos-default", "displayName": "macOS Debug", - "description": "Target a remote macOS system with Ninja", - "generator": "Ninja", + "description": "Target a remote macOS system with Ninja Multi-Config", + "generator": "Ninja Multi-Config", "binaryDir": "${sourceDir}/out/build/${presetName}", "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug", @@ -145,7 +145,7 @@ { "name": "windows-common", "hidden": true, - "generator": "Ninja", + "generator": "Ninja Multi-Config", "architecture": { "value": "x64", "strategy": "external" From bd52d50e9a3c89d13b6fb0fb3c615f569ce0292e Mon Sep 17 00:00:00 2001 From: Phil Ratzloff Date: Mon, 30 Oct 2023 09:17:21 -0400 Subject: [PATCH 13/15] Add load function prototypes --- include/graph/container/compressed_graph.hpp | 1496 +++++++++--------- 1 file changed, 762 insertions(+), 734 deletions(-) diff --git a/include/graph/container/compressed_graph.hpp b/include/graph/container/compressed_graph.hpp index c2f16d4..06287e2 100644 --- a/include/graph/container/compressed_graph.hpp +++ b/include/graph/container/compressed_graph.hpp @@ -8,6 +8,28 @@ #include #include "graph/graph.hpp" + +// Example prototypes for P1709 +namespace std::graph::prototypes { +template +requires copyable_vertex>, vertex_id_t, vertex_value_t> +constexpr void load_vertices(G&, const VRng&, VProj); + +template +requires copyable_edge>, vertex_id_t, edge_value_t> +constexpr void load_edges(G&, const ERng&, EProj = {}, ranges::range_size_t vertex_count = 0); + +template +requires copyable_edge>, vertex_id_t, edge_value_t> && + copyable_vertex>, vertex_id_t, edge_value_t> +constexpr void load(G&, const ERng&, const VRng&, EProj = {}, VProj = {}); +} // namespace std::graph + + // NOTES // have public load_edges(...), load_vertices(...), and load() // allow separation of construction and load @@ -27,7 +49,7 @@ // namespace std::graph::container { -/** + /** * @ingroup graph_containers * @brief Scans a range used for input for loading edges to determine the largest vertex id used. * @@ -43,59 +65,59 @@ namespace std::graph::container { * * @return A @c pair with the max vertex id used and the number of edges scanned. */ -template -requires copyable_edge>, VId, EV> -constexpr auto max_vertex_id(const ERng& erng, const EProj& eprojection) { - size_t edge_count = 0; - VId max_id = 0; - for (auto&& edge_data : erng) { - const copyable_edge_t& uv = eprojection(edge_data); - max_id = max(max_id, max(static_cast(uv.source_id), static_cast(uv.target_id))); - ++edge_count; + template + requires copyable_edge>, VId, EV> + constexpr auto max_vertex_id(const ERng& erng, const EProj& eprojection) { + size_t edge_count = 0; + VId max_id = 0; + for (auto&& edge_data : erng) { + const copyable_edge_t& uv = eprojection(edge_data); + max_id = max(max_id, max(static_cast(uv.source_id), static_cast(uv.target_id))); + ++edge_count; + } + return pair(max_id, edge_count); } - return pair(max_id, edge_count); -} -// -// forward declarations -// -template > // for internal containers -class compressed_graph; - -/** + // + // forward declarations + // + template > // for internal containers + class compressed_graph; + + /** * @ingroup graph_containers * @brief Wrapper struct for the row index to distinguish it from a vertex_id_type (VId). * * @tparam EIndex The type for storing an edge index. It must be able to store a value of |E|+1, * where |E| is the total number of edges in the graph. */ -template -struct csr_row { - using edge_index_type = EIndex; - edge_index_type index = 0; -}; + template + struct csr_row { + using edge_index_type = EIndex; + edge_index_type index = 0; + }; -/** + /** * @ingroup graph_containers * @brief Wrapper struct for the col (edge) index to distinguish it from a vertex_id_type (VId). * * @tparam VId Vertex id type. The type must be able to store a value of |V|+1, where |V| is the * number of vertices in the graph. */ -template -struct csr_col { - using vertex_id_type = VId; - vertex_id_type index = 0; -}; + template + struct csr_col { + using vertex_id_type = VId; + vertex_id_type index = 0; + }; -/** + /** * @ingroup graph_containers * @brief Holds vertex values in a vector that is the same size as @c row_index_. * @@ -114,149 +136,151 @@ struct csr_col { * where |E| is the total number of edges in the graph. * @tparam Alloc The allocator type. */ -template -class csr_row_values { - using row_type = csr_row; // index into col_index_ - using row_allocator_type = typename allocator_traits::template rebind_alloc; - using row_index_vector = vector; - -public: - using graph_type = compressed_graph; - using vertex_type = row_type; - using vertex_value_type = VV; - using allocator_type = typename allocator_traits::template rebind_alloc; - using vector_type = vector; - - using value_type = VV; - using size_type = size_t; //VId; - using difference_type = typename vector_type::difference_type; - using reference = value_type&; - using const_reference = const value_type&; - using pointer = typename vector_type::pointer; - using const_pointer = typename vector_type::const_pointer; - using iterator = typename vector_type::iterator; - using const_iterator = typename vector_type::const_iterator; - - constexpr csr_row_values(const Alloc& alloc) : v_(alloc) {} - - constexpr csr_row_values() = default; - constexpr csr_row_values(const csr_row_values&) = default; - constexpr csr_row_values(csr_row_values&&) = default; - constexpr ~csr_row_values() = default; - - constexpr csr_row_values& operator=(const csr_row_values&) = default; - constexpr csr_row_values& operator=(csr_row_values&&) = default; - - -public: // Properties - [[nodiscard]] constexpr size_type size() const noexcept { return static_cast(v_.size()); } - [[nodiscard]] constexpr bool empty() const noexcept { return v_.empty(); } - [[nodiscard]] constexpr size_type capacity() const noexcept { return static_cast(v_.size()); } - -public: // Operations - constexpr void reserve(size_type new_cap) { v_.reserve(new_cap); } - constexpr void resize(size_type new_size) { v_.resize(new_size); } - - constexpr void clear() noexcept { v_.clear(); } - constexpr void push_back(const value_type& value) { v_.push_back(value); } - constexpr void emplace_back(value_type&& value) { v_.emplace_back(forward(value)); } - - constexpr void swap(csr_row_values& other) noexcept { swap(v_, other.v_); } - - template - //requires copyable_vertex>, VId, VV> - constexpr void load_row_values(const VRng& vrng, VProj projection, size_type vertex_count) { - static_assert(copyable_vertex>, VId, VV>); - - if constexpr (ranges::sized_range) - vertex_count = max(vertex_count, ranges::size(vrng)); - resize(ranges::size(vrng)); - - for (auto&& vtx : vrng) { - const auto& [id, value] = projection(vtx); - - // if an unsized vrng is passed, the caller is responsible to call - // resize_vertices(n) with enough entries for all the values. - assert(static_cast(id) < size()); - - (*this)[static_cast(id)] = value; + template + class csr_row_values { + using row_type = csr_row; // index into col_index_ + using row_allocator_type = typename allocator_traits::template rebind_alloc; + using row_index_vector = vector; + + public: + using graph_type = compressed_graph; + using vertex_type = row_type; + using vertex_value_type = VV; + using allocator_type = typename allocator_traits::template rebind_alloc; + using vector_type = vector; + + using value_type = VV; + using size_type = size_t; //VId; + using difference_type = typename vector_type::difference_type; + using reference = value_type&; + using const_reference = const value_type&; + using pointer = typename vector_type::pointer; + using const_pointer = typename vector_type::const_pointer; + using iterator = typename vector_type::iterator; + using const_iterator = typename vector_type::const_iterator; + + constexpr csr_row_values(const Alloc& alloc) : v_(alloc) {} + + constexpr csr_row_values() = default; + constexpr csr_row_values(const csr_row_values&) = default; + constexpr csr_row_values(csr_row_values&&) = default; + constexpr ~csr_row_values() = default; + + constexpr csr_row_values& operator=(const csr_row_values&) = default; + constexpr csr_row_values& operator=(csr_row_values&&) = default; + + + public: // Properties + [[nodiscard]] constexpr size_type size() const noexcept { return static_cast(v_.size()); } + [[nodiscard]] constexpr bool empty() const noexcept { return v_.empty(); } + [[nodiscard]] constexpr size_type capacity() const noexcept { return static_cast(v_.size()); } + + public: // Operations + constexpr void reserve(size_type new_cap) { v_.reserve(new_cap); } + constexpr void resize(size_type new_size) { v_.resize(new_size); } + + constexpr void clear() noexcept { v_.clear(); } + constexpr void push_back(const value_type& value) { v_.push_back(value); } + constexpr void emplace_back(value_type&& value) { v_.emplace_back(forward(value)); } + + constexpr void swap(csr_row_values& other) noexcept { swap(v_, other.v_); } + + template + //requires copyable_vertex>, VId, VV> + constexpr void load_row_values(const VRng& vrng, VProj projection, size_type vertex_count) { + static_assert(copyable_vertex>, VId, VV>); + + if constexpr (ranges::sized_range) + vertex_count = max(vertex_count, ranges::size(vrng)); + resize(ranges::size(vrng)); + + for (auto&& vtx : vrng) { + const auto& [id, value] = projection(vtx); + + // if an unsized vrng is passed, the caller is responsible to call + // resize_vertices(n) with enough entries for all the values. + assert(static_cast(id) < size()); + + (*this)[static_cast(id)] = value; + } } - } - template - //requires copyable_vertex>, VId, VV> - constexpr void load_row_values(VRng&& vrng, VProj projection, size_type vertex_count) { - //using result_t = invoke_result_t>; - //using desc_t = vertex_descriptor; + template + //requires copyable_vertex>, VId, VV> + constexpr void load_row_values(VRng&& vrng, VProj projection, size_type vertex_count) { + //using result_t = invoke_result_t>; + //using desc_t = vertex_descriptor; - //result_t result; - //desc_t desc{result}; - //static_assert(convertible_to); - //static_assert(copyable_vertex>, VId, VV>); + //result_t result; + //desc_t desc{result}; + //static_assert(convertible_to); + //static_assert(copyable_vertex>, VId, VV>); - if constexpr (ranges::sized_range) - vertex_count = max(vertex_count, ranges::size(vrng)); - resize(ranges::size(vrng)); + if constexpr (ranges::sized_range) + vertex_count = max(vertex_count, ranges::size(vrng)); + resize(ranges::size(vrng)); - for (auto&& vtx : vrng) { - auto&& [id, value] = projection(vtx); + for (auto&& vtx : vrng) { + auto&& [id, value] = projection(vtx); - // if an unsized vrng is passed, the caller is responsible to call - // resize_vertices(n) with enough entries for all the values. - assert(static_cast(id) < size()); + // if an unsized vrng is passed, the caller is responsible to call + // resize_vertices(n) with enough entries for all the values. + assert(static_cast(id) < size()); - (*this)[id] = std::move(value); + (*this)[id] = std::move(value); + } } - } -public: - constexpr reference operator[](size_type pos) { return v_[pos]; } - constexpr const_reference operator[](size_type pos) const { return v_[pos]; } - -private: - friend constexpr vertex_value_type& - tag_invoke(::std::graph::tag_invoke::vertex_value_fn_t, graph_type& g, vertex_type& u) { - static_assert(ranges::contiguous_range, "row_index_ must be a contiguous range to evaluate uidx"); - auto uidx = g.index_of(u); - csr_row_values& row_vals = g; - return row_vals.v_[uidx]; - } - friend constexpr const vertex_value_type& - tag_invoke(::std::graph::tag_invoke::vertex_value_fn_t, const graph_type& g, const vertex_type& u) { - static_assert(ranges::contiguous_range, "row_index_ must be a contiguous range to evaluate uidx"); - auto uidx = g.index_of(u); - const csr_row_values& row_vals = g; - return row_vals.v_[uidx]; - } + public: + constexpr reference operator[](size_type pos) { return v_[pos]; } + constexpr const_reference operator[](size_type pos) const { return v_[pos]; } + + private: + friend constexpr vertex_value_type& + tag_invoke(::std::graph::tag_invoke::vertex_value_fn_t, graph_type& g, vertex_type& u) { + static_assert(ranges::contiguous_range, + "row_index_ must be a contiguous range to evaluate uidx"); + auto uidx = g.index_of(u); + csr_row_values& row_vals = g; + return row_vals.v_[uidx]; + } + friend constexpr const vertex_value_type& + tag_invoke(::std::graph::tag_invoke::vertex_value_fn_t, const graph_type& g, const vertex_type& u) { + static_assert(ranges::contiguous_range, + "row_index_ must be a contiguous range to evaluate uidx"); + auto uidx = g.index_of(u); + const csr_row_values& row_vals = g; + return row_vals.v_[uidx]; + } -private: - vector_type v_; -}; + private: + vector_type v_; + }; -template -class csr_row_values { -public: - constexpr csr_row_values(const Alloc& alloc) {} - constexpr csr_row_values() = default; + template + class csr_row_values { + public: + constexpr csr_row_values(const Alloc& alloc) {} + constexpr csr_row_values() = default; - using value_type = void; - using size_type = size_t; //VId; + using value_type = void; + using size_type = size_t; //VId; -public: // Properties - [[nodiscard]] constexpr size_type size() const noexcept { return 0; } - [[nodiscard]] constexpr bool empty() const noexcept { return true; } - [[nodiscard]] constexpr size_type capacity() const noexcept { return 0; } + public: // Properties + [[nodiscard]] constexpr size_type size() const noexcept { return 0; } + [[nodiscard]] constexpr bool empty() const noexcept { return true; } + [[nodiscard]] constexpr size_type capacity() const noexcept { return 0; } -public: // Operations - constexpr void reserve(size_type new_cap) {} - constexpr void resize(size_type new_size) {} + public: // Operations + constexpr void reserve(size_type new_cap) {} + constexpr void resize(size_type new_size) {} - constexpr void clear() noexcept {} - constexpr void swap(csr_row_values& other) noexcept {} -}; + constexpr void clear() noexcept {} + constexpr void swap(csr_row_values& other) noexcept {} + }; -/** + /** * @ingroup graph_containers * @brief Class to hold vertex values in a vector that is the same size as col_index_. * @@ -275,102 +299,102 @@ class csr_row_values { * where |E| is the total number of edges in the graph. * @tparam Alloc The allocator type. */ -template -class csr_col_values { - using col_type = csr_col; // target_id - using col_allocator_type = typename allocator_traits::template rebind_alloc; - using col_index_vector = vector; - -public: - using graph_type = compressed_graph; - using edge_type = col_type; // index into v_ - using edge_value_type = EV; - using allocator_type = typename allocator_traits::template rebind_alloc; - using vector_type = vector; - - using value_type = EV; - using size_type = size_t; //VId; - using difference_type = typename vector_type::difference_type; - using reference = value_type&; - using const_reference = const value_type&; - using pointer = typename vector_type::pointer; - using const_pointer = typename vector_type::const_pointer; - using iterator = typename vector_type::iterator; - using const_iterator = typename vector_type::const_iterator; - - constexpr csr_col_values(const Alloc& alloc) : v_(alloc) {} - - constexpr csr_col_values() = default; - constexpr csr_col_values(const csr_col_values&) = default; - constexpr csr_col_values(csr_col_values&&) = default; - constexpr ~csr_col_values() = default; - - constexpr csr_col_values& operator=(const csr_col_values&) = default; - constexpr csr_col_values& operator=(csr_col_values&&) = default; - - -public: // Properties - [[nodiscard]] constexpr size_type size() const noexcept { return static_cast(v_.size()); } - [[nodiscard]] constexpr bool empty() const noexcept { return v_.empty(); } - [[nodiscard]] constexpr size_type capacity() const noexcept { return static_cast(v_.size()); } - -public: // Operations - constexpr void reserve(size_type new_cap) { v_.reserve(new_cap); } - constexpr void resize(size_type new_size) { v_.resize(new_size); } - - constexpr void clear() noexcept { v_.clear(); } - constexpr void push_back(const value_type& value) { v_.push_back(value); } - constexpr void emplace_back(value_type&& value) { v_.emplace_back(forward(value)); } - - constexpr void swap(csr_col_values& other) noexcept { swap(v_, other.v_); } - -public: - constexpr reference operator[](size_type pos) { return v_[pos]; } - constexpr const_reference operator[](size_type pos) const { return v_[pos]; } - -private: - // edge_value(g,uv) - friend constexpr edge_value_type& - tag_invoke(::std::graph::tag_invoke::edge_value_fn_t, graph_type& g, edge_type& uv) { - auto uv_idx = g.index_of(uv); - csr_col_values& col_vals = g; - return col_vals.v_[uv_idx]; - } - friend constexpr const edge_value_type& - tag_invoke(::std::graph::tag_invoke::edge_value_fn_t, const graph_type& g, const edge_type& uv) { - auto uv_idx = g.index_of(uv); - const csr_col_values& col_vals = g; - return col_vals.v_[uv_idx]; - } + template + class csr_col_values { + using col_type = csr_col; // target_id + using col_allocator_type = typename allocator_traits::template rebind_alloc; + using col_index_vector = vector; + + public: + using graph_type = compressed_graph; + using edge_type = col_type; // index into v_ + using edge_value_type = EV; + using allocator_type = typename allocator_traits::template rebind_alloc; + using vector_type = vector; + + using value_type = EV; + using size_type = size_t; //VId; + using difference_type = typename vector_type::difference_type; + using reference = value_type&; + using const_reference = const value_type&; + using pointer = typename vector_type::pointer; + using const_pointer = typename vector_type::const_pointer; + using iterator = typename vector_type::iterator; + using const_iterator = typename vector_type::const_iterator; + + constexpr csr_col_values(const Alloc& alloc) : v_(alloc) {} + + constexpr csr_col_values() = default; + constexpr csr_col_values(const csr_col_values&) = default; + constexpr csr_col_values(csr_col_values&&) = default; + constexpr ~csr_col_values() = default; + + constexpr csr_col_values& operator=(const csr_col_values&) = default; + constexpr csr_col_values& operator=(csr_col_values&&) = default; + + + public: // Properties + [[nodiscard]] constexpr size_type size() const noexcept { return static_cast(v_.size()); } + [[nodiscard]] constexpr bool empty() const noexcept { return v_.empty(); } + [[nodiscard]] constexpr size_type capacity() const noexcept { return static_cast(v_.size()); } + + public: // Operations + constexpr void reserve(size_type new_cap) { v_.reserve(new_cap); } + constexpr void resize(size_type new_size) { v_.resize(new_size); } + + constexpr void clear() noexcept { v_.clear(); } + constexpr void push_back(const value_type& value) { v_.push_back(value); } + constexpr void emplace_back(value_type&& value) { v_.emplace_back(forward(value)); } + + constexpr void swap(csr_col_values& other) noexcept { swap(v_, other.v_); } + + public: + constexpr reference operator[](size_type pos) { return v_[pos]; } + constexpr const_reference operator[](size_type pos) const { return v_[pos]; } + + private: + // edge_value(g,uv) + friend constexpr edge_value_type& + tag_invoke(::std::graph::tag_invoke::edge_value_fn_t, graph_type& g, edge_type& uv) { + auto uv_idx = g.index_of(uv); + csr_col_values& col_vals = g; + return col_vals.v_[uv_idx]; + } + friend constexpr const edge_value_type& + tag_invoke(::std::graph::tag_invoke::edge_value_fn_t, const graph_type& g, const edge_type& uv) { + auto uv_idx = g.index_of(uv); + const csr_col_values& col_vals = g; + return col_vals.v_[uv_idx]; + } -private: - vector_type v_; -}; + private: + vector_type v_; + }; -template -class csr_col_values { -public: - constexpr csr_col_values(const Alloc& alloc) {} - constexpr csr_col_values() = default; + template + class csr_col_values { + public: + constexpr csr_col_values(const Alloc& alloc) {} + constexpr csr_col_values() = default; - using value_type = void; - using size_type = size_t; //VId; + using value_type = void; + using size_type = size_t; //VId; -public: // Properties - [[nodiscard]] constexpr size_type size() const noexcept { return 0; } - [[nodiscard]] constexpr bool empty() const noexcept { return true; } - [[nodiscard]] constexpr size_type capacity() const noexcept { return 0; } + public: // Properties + [[nodiscard]] constexpr size_type size() const noexcept { return 0; } + [[nodiscard]] constexpr bool empty() const noexcept { return true; } + [[nodiscard]] constexpr size_type capacity() const noexcept { return 0; } -public: // Operations - constexpr void reserve(size_type new_cap) {} - constexpr void resize(size_type new_size) {} + public: // Operations + constexpr void reserve(size_type new_cap) {} + constexpr void resize(size_type new_size) {} - constexpr void clear() noexcept {} - constexpr void swap(csr_col_values& other) noexcept {} -}; + constexpr void clear() noexcept {} + constexpr void swap(csr_col_values& other) noexcept {} + }; -/** + /** * @ingroup graph_containers * @brief Base class for compressed sparse row adjacency graph * @@ -386,63 +410,63 @@ class csr_col_values { * where |E| is the total number of edges in the graph. * @tparam Alloc The allocator type. */ -template -class compressed_graph_base - : public csr_row_values - , public csr_col_values { - using row_values_base = csr_row_values; - using col_values_base = csr_col_values; - - using row_type = csr_row; // index into col_index_ - using row_allocator_type = typename allocator_traits::template rebind_alloc; - using row_index_vector = vector; - - using partition_type = VId; // index into row_indexes - using partition_allocator_type = typename allocator_traits::template rebind_alloc; - using partition_index_vector = vector; - - using col_type = csr_col; // target_id - using col_allocator_type = typename allocator_traits::template rebind_alloc; - using col_index_vector = vector; - -public: // Types - using graph_type = compressed_graph_base; - - using vertex_id_type = VId; - using vertex_type = row_type; - using vertex_value_type = VV; - using vertices_type = ranges::subrange>; - using const_vertices_type = ranges::subrange>; - - using partition_id_type = vertex_id_type; - using partition_size_type = vertex_id_type; - - using edge_type = col_type; // index into v_ - using edge_value_type = EV; - using edge_index_type = EIndex; - using edges_type = ranges::subrange>; - using const_edges_type = ranges::subrange>; - - using const_iterator = typename row_index_vector::const_iterator; - using iterator = typename row_index_vector::iterator; - - using size_type = ranges::range_size_t; - -public: // Construction/Destruction - constexpr compressed_graph_base() = default; - constexpr compressed_graph_base(const compressed_graph_base&) = default; - constexpr compressed_graph_base(compressed_graph_base&&) = default; - constexpr ~compressed_graph_base() = default; - - constexpr compressed_graph_base& operator=(const compressed_graph_base&) = default; - constexpr compressed_graph_base& operator=(compressed_graph_base&&) = default; - - constexpr compressed_graph_base(const Alloc& alloc) - : row_values_base(alloc), col_values_base(alloc), row_index_(alloc), col_index_(alloc), part_index_(alloc) { - set_default_partition(); - } + template + class compressed_graph_base + : public csr_row_values + , public csr_col_values { + using row_values_base = csr_row_values; + using col_values_base = csr_col_values; + + using row_type = csr_row; // index into col_index_ + using row_allocator_type = typename allocator_traits::template rebind_alloc; + using row_index_vector = vector; + + using partition_type = VId; // index into row_indexes + using partition_allocator_type = typename allocator_traits::template rebind_alloc; + using partition_index_vector = vector; + + using col_type = csr_col; // target_id + using col_allocator_type = typename allocator_traits::template rebind_alloc; + using col_index_vector = vector; + + public: // Types + using graph_type = compressed_graph_base; + + using vertex_id_type = VId; + using vertex_type = row_type; + using vertex_value_type = VV; + using vertices_type = ranges::subrange>; + using const_vertices_type = ranges::subrange>; + + using partition_id_type = vertex_id_type; + using partition_size_type = vertex_id_type; + + using edge_type = col_type; // index into v_ + using edge_value_type = EV; + using edge_index_type = EIndex; + using edges_type = ranges::subrange>; + using const_edges_type = ranges::subrange>; + + using const_iterator = typename row_index_vector::const_iterator; + using iterator = typename row_index_vector::iterator; + + using size_type = ranges::range_size_t; + + public: // Construction/Destruction + constexpr compressed_graph_base() = default; + constexpr compressed_graph_base(const compressed_graph_base&) = default; + constexpr compressed_graph_base(compressed_graph_base&&) = default; + constexpr ~compressed_graph_base() = default; + + constexpr compressed_graph_base& operator=(const compressed_graph_base&) = default; + constexpr compressed_graph_base& operator=(compressed_graph_base&&) = default; + + constexpr compressed_graph_base(const Alloc& alloc) + : row_values_base(alloc), col_values_base(alloc), row_index_(alloc), col_index_(alloc), part_index_(alloc) { + set_default_partition(); + } - /** + /** * @brief Constructor that takes a edge range to create the CSR graph. * * Edges must be ordered by source_id (enforced by asssertion). @@ -454,15 +478,15 @@ class compressed_graph_base * @param eprojection Projection function that creates a @c copyable_edge_t from an erng value * @param alloc Allocator to use for internal containers */ - template - requires copyable_edge>, VId, EV> - constexpr compressed_graph_base(const ERng& erng, EProj eprojection = {}, const Alloc& alloc = Alloc()) - : row_values_base(alloc), col_values_base(alloc), row_index_(alloc), col_index_(alloc), part_index_(alloc) { + template + requires copyable_edge>, VId, EV> + constexpr compressed_graph_base(const ERng& erng, EProj eprojection = {}, const Alloc& alloc = Alloc()) + : row_values_base(alloc), col_values_base(alloc), row_index_(alloc), col_index_(alloc), part_index_(alloc) { - load_edges(erng, eprojection); - } + load_edges(erng, eprojection); + } - /** + /** * @brief Constructor that takes edge range and vertex range to create the CSR graph. * * Edges must be ordered by source_id (enforced by asssertion). @@ -478,51 +502,52 @@ class compressed_graph_base * @param vprojection Projection function that creates a @c copyable_vertex_t from a @c vrng value * @param alloc Allocator to use for internal containers */ - template - //requires copyable_edge>, VId, EV> && - // copyable_vertex>, VId, VV> - constexpr compressed_graph_base(const ERng& erng, - const VRng& vrng, - EProj eprojection = {}, // eproj(eval) -> {source_id,target_id [,value]} - VProj vprojection = {}, // vproj(vval) -> {target_id [,value]} - const Alloc& alloc = Alloc()) - : row_values_base(alloc), col_values_base(alloc), row_index_(alloc), col_index_(alloc), part_index_(alloc) { - - load(erng, vrng, eprojection, vprojection); - } + template + //requires copyable_edge>, VId, EV> && + // copyable_vertex>, VId, VV> + constexpr compressed_graph_base(const ERng& erng, + const VRng& vrng, + EProj eprojection = {}, // eproj(eval) -> {source_id,target_id [,value]} + VProj vprojection = {}, // vproj(vval) -> {target_id [,value]} + const Alloc& alloc = Alloc()) + : row_values_base(alloc), col_values_base(alloc), row_index_(alloc), col_index_(alloc), part_index_(alloc) { + + load(erng, vrng, eprojection, vprojection); + } - /** + /** * @brief Constructor for easy creation of a graph that takes an initializer list * of @c copyable_edge_t -> [source_id, target_id, value]. * * @param ilist Initializer list of @c copyable_edge_t -> [source_id, target_id, value] * @param alloc Allocator to use for internal containers */ - constexpr compressed_graph_base(const initializer_list>& ilist, const Alloc& alloc = Alloc()) - : row_values_base(alloc), col_values_base(alloc), row_index_(alloc), col_index_(alloc), part_index_(alloc) { - load_edges(ilist, identity()); - } + constexpr compressed_graph_base(const initializer_list>& ilist, + const Alloc& alloc = Alloc()) + : row_values_base(alloc), col_values_base(alloc), row_index_(alloc), col_index_(alloc), part_index_(alloc) { + load_edges(ilist, identity()); + } -public: // Operations - void reserve_vertices(size_type count) { - row_index_.reserve(count + 1); // +1 for terminating row - row_values_base::reserve(count); - } - void reserve_edges(size_type count) { - col_index_.reserve(count); - static_cast(*this).reserve(count); - } + public: // Operations + void reserve_vertices(size_type count) { + row_index_.reserve(count + 1); // +1 for terminating row + row_values_base::reserve(count); + } + void reserve_edges(size_type count) { + col_index_.reserve(count); + static_cast(*this).reserve(count); + } - void resize_vertices(size_type count) { - row_index_.resize(count + 1); // +1 for terminating row - row_values_resize(count); - } - void resize_edges(size_type count) { - col_index_.reserve(count); - static_cast(*this).reserve(count); - } + void resize_vertices(size_type count) { + row_index_.resize(count + 1); // +1 for terminating row + row_values_resize(count); + } + void resize_edges(size_type count) { + col_index_.reserve(count); + static_cast(*this).reserve(count); + } - /** + /** * @brief Load vertex values, callable either before or after @c load_edges(erng,eproj). * * If @c load_edges(vrng,vproj) has been called before this, the @c row_values_ vector will be @@ -536,14 +561,14 @@ class compressed_graph_base * preserved in the internal vector. * @param vprojection Projection function for @c vrng values */ - template - //requires copyable_vertex>, VId, VV> - constexpr void load_vertices(const VRng& vrng, VProj vprojection, size_type vertex_count = 0) { - row_values_base::load_row_values(vrng, vprojection, max(vertex_count, ranges::size(vrng))); - set_default_partition(); - } + template + //requires copyable_vertex>, VId, VV> + constexpr void load_vertices(const VRng& vrng, VProj vprojection, size_type vertex_count = 0) { + row_values_base::load_row_values(vrng, vprojection, max(vertex_count, ranges::size(vrng))); + set_default_partition(); + } - /** + /** * Load vertex values, callable either before or after @c load_edges(erng,eproj). * * If @c load_edges(vrng,vproj) has been called before this, the @c row_values_ vector will be @@ -556,14 +581,14 @@ class compressed_graph_base * @param vrng Range of values to load for vertices. The order of the values is preserved in the internal vector. * @param vprojection Projection function for @c vrng values */ - template - //requires copyable_vertex>, VId, VV> - constexpr void load_vertices(VRng& vrng, VProj vprojection = {}, size_type vertex_count = 0) { - row_values_base::load_row_values(vrng, vprojection, max(vertex_count, ranges::size(vrng))); - set_default_partition(); - } + template + //requires copyable_vertex>, VId, VV> + constexpr void load_vertices(VRng& vrng, VProj vprojection = {}, size_type vertex_count = 0) { + row_values_base::load_row_values(vrng, vprojection, max(vertex_count, ranges::size(vrng))); + set_default_partition(); + } - /** + /** * @brief Load the edges for the graph, callable either before or after @c load_vertices(erng,eproj). * * @c erng must be ordered by source_id (copyable_edge_t) and is enforced by assertion. target_id @@ -597,115 +622,116 @@ class compressed_graph_base * @param erng Input range for edges * @param eprojection Edge projection function that returns a @ copyable_edge_t for an element in @c erng */ - template - //requires copyable_edge>, VId, EV> - constexpr void load_edges(ERng&& erng, EProj eprojection = {}, size_type vertex_count = 0, size_type edge_count = 0) { - // should only be loading into an empty graph - assert(row_index_.empty() && col_index_.empty() && static_cast(*this).empty()); - - // Nothing to do? - if (ranges::begin(erng) == ranges::end(erng)) { - return; - } + template + //requires copyable_edge>, VId, EV> + constexpr void + load_edges(ERng&& erng, EProj eprojection = {}, size_type vertex_count = 0, size_type edge_count = 0) { + // should only be loading into an empty graph + assert(row_index_.empty() && col_index_.empty() && static_cast(*this).empty()); + + // Nothing to do? + if (ranges::begin(erng) == ranges::end(erng)) { + return; + } - // We can get the last vertex id from the list because erng is required to be ordered by - // the source id. It's possible a target_id could have a larger id also, which is taken - // care of at the end of this function. - vertex_count = std::max(vertex_count, - static_cast(last_erng_id(erng, eprojection) + 1)); // +1 for zero-based index - reserve_vertices(vertex_count); + // We can get the last vertex id from the list because erng is required to be ordered by + // the source id. It's possible a target_id could have a larger id also, which is taken + // care of at the end of this function. + vertex_count = std::max(vertex_count, + static_cast(last_erng_id(erng, eprojection) + 1)); // +1 for zero-based index + reserve_vertices(vertex_count); + + // Eval number of input rows and reserve space for the edges, if possible + if constexpr (ranges::sized_range) + edge_count = max(edge_count, ranges::size(erng)); + reserve_edges(edge_count); + + // Add edges + vertex_id_type last_uid = 0, max_vid = 0; + for (auto&& edge_data : erng) { + auto&& edge = eprojection(edge_data); // compressed_graph requires EV!=void + assert(edge.source_id >= last_uid); // ordered by uid? (requirement) + row_index_.resize(static_cast(edge.source_id) + 1, + vertex_type{static_cast(static_cast(*this).size())}); + col_index_.push_back(edge_type{edge.target_id}); + if (!is_void_v) + static_cast(*this).emplace_back(std::move(edge.value)); + last_uid = edge.source_id; + max_vid = max(max_vid, edge.target_id); + } - // Eval number of input rows and reserve space for the edges, if possible - if constexpr (ranges::sized_range) - edge_count = max(edge_count, ranges::size(erng)); - reserve_edges(edge_count); + // uid and vid may refer to rows that exceed the value evaluated for vertex_count (if any) + vertex_count = max(vertex_count, max(row_index_.size(), static_cast(max_vid + 1))); - // Add edges - vertex_id_type last_uid = 0, max_vid = 0; - for (auto&& edge_data : erng) { - auto&& edge = eprojection(edge_data); // compressed_graph requires EV!=void - assert(edge.source_id >= last_uid); // ordered by uid? (requirement) - row_index_.resize(static_cast(edge.source_id) + 1, + // add any rows that haven't been added yet, and (+1) terminating row + row_index_.resize(vertex_count + 1, vertex_type{static_cast(static_cast(*this).size())}); - col_index_.push_back(edge_type{edge.target_id}); - if (!is_void_v) - static_cast(*this).emplace_back(std::move(edge.value)); - last_uid = edge.source_id; - max_vid = max(max_vid, edge.target_id); - } - // uid and vid may refer to rows that exceed the value evaluated for vertex_count (if any) - vertex_count = max(vertex_count, max(row_index_.size(), static_cast(max_vid + 1))); + // If load_vertices(vrng,vproj) has been called but it doesn't have enough values for all + // the vertices then we extend the size to remove possibility of out-of-bounds occuring when + // getting a value for a row. + if (row_values_base::size() > 1 && row_values_base::size() < vertex_count) + row_values_base::resize(vertex_count); - // add any rows that haven't been added yet, and (+1) terminating row - row_index_.resize(vertex_count + 1, - vertex_type{static_cast(static_cast(*this).size())}); - - // If load_vertices(vrng,vproj) has been called but it doesn't have enough values for all - // the vertices then we extend the size to remove possibility of out-of-bounds occuring when - // getting a value for a row. - if (row_values_base::size() > 1 && row_values_base::size() < vertex_count) - row_values_base::resize(vertex_count); - - set_default_partition(); - } - - // The only diff with this and ERng&& is v_.push_back vs. v_.emplace_back - template - //requires copyable_edge>, VId, EV> - constexpr void - load_edges(const ERng& erng, EProj eprojection = {}, size_type vertex_count = 0, size_type edge_count = 0) { - // should only be loading into an empty graph - assert(row_index_.empty() && col_index_.empty() && static_cast(*this).empty()); - - // Nothing to do? - if (ranges::begin(erng) == ranges::end(erng)) { - return; + set_default_partition(); } - // We can get the last vertex id from the list because erng is required to be ordered by - // the source id. It's possible a target_id could have a larger id also, which is taken - // care of at the end of this function. - vertex_count = std::max(vertex_count, - static_cast(last_erng_id(erng, eprojection) + 1)); // +1 for zero-based index - reserve_vertices(vertex_count); - - // Eval number of input rows and reserve space for the edges, if possible - if constexpr (ranges::sized_range) - edge_count = max(edge_count, ranges::size(erng)); - reserve_edges(edge_count); + // The only diff with this and ERng&& is v_.push_back vs. v_.emplace_back + template + //requires copyable_edge>, VId, EV> + constexpr void + load_edges(const ERng& erng, EProj eprojection = {}, size_type vertex_count = 0, size_type edge_count = 0) { + // should only be loading into an empty graph + assert(row_index_.empty() && col_index_.empty() && static_cast(*this).empty()); + + // Nothing to do? + if (ranges::begin(erng) == ranges::end(erng)) { + return; + } - // Add edges - vertex_id_type last_uid = 0, max_vid = 0; - for (auto&& edge_data : erng) { - auto&& edge = eprojection(edge_data); // compressed_graph requires EV!=void - assert(edge.source_id >= last_uid); // ordered by uid? (requirement) - row_index_.resize(static_cast(edge.source_id) + 1, - vertex_type{static_cast(static_cast(*this).size())}); - col_index_.push_back(edge_type{edge.target_id}); - if constexpr (!is_void_v) - static_cast(*this).push_back(edge.value); - last_uid = edge.source_id; - max_vid = max(max_vid, edge.target_id); - } + // We can get the last vertex id from the list because erng is required to be ordered by + // the source id. It's possible a target_id could have a larger id also, which is taken + // care of at the end of this function. + vertex_count = std::max(vertex_count, + static_cast(last_erng_id(erng, eprojection) + 1)); // +1 for zero-based index + reserve_vertices(vertex_count); + + // Eval number of input rows and reserve space for the edges, if possible + if constexpr (ranges::sized_range) + edge_count = max(edge_count, ranges::size(erng)); + reserve_edges(edge_count); + + // Add edges + vertex_id_type last_uid = 0, max_vid = 0; + for (auto&& edge_data : erng) { + auto&& edge = eprojection(edge_data); // compressed_graph requires EV!=void + assert(edge.source_id >= last_uid); // ordered by uid? (requirement) + row_index_.resize(static_cast(edge.source_id) + 1, + vertex_type{static_cast(static_cast(*this).size())}); + col_index_.push_back(edge_type{edge.target_id}); + if constexpr (!is_void_v) + static_cast(*this).push_back(edge.value); + last_uid = edge.source_id; + max_vid = max(max_vid, edge.target_id); + } - // uid and vid may refer to rows that exceed the value evaluated for vertex_count (if any) - vertex_count = max(vertex_count, max(row_index_.size(), static_cast(max_vid + 1))); + // uid and vid may refer to rows that exceed the value evaluated for vertex_count (if any) + vertex_count = max(vertex_count, max(row_index_.size(), static_cast(max_vid + 1))); - // add any rows that haven't been added yet, and (+1) terminating row - row_index_.resize(vertex_count + 1, - vertex_type{static_cast(static_cast(*this).size())}); + // add any rows that haven't been added yet, and (+1) terminating row + row_index_.resize(vertex_count + 1, + vertex_type{static_cast(static_cast(*this).size())}); - // If load_vertices(vrng,vproj) has been called but it doesn't have enough values for all - // the vertices then we extend the size to remove possibility of out-of-bounds occuring when - // getting a value for a row. - if (row_values_base::size() > 0 && row_values_base::size() < vertex_count) - row_values_base::resize(vertex_count); + // If load_vertices(vrng,vproj) has been called but it doesn't have enough values for all + // the vertices then we extend the size to remove possibility of out-of-bounds occuring when + // getting a value for a row. + if (row_values_base::size() > 0 && row_values_base::size() < vertex_count) + row_values_base::resize(vertex_count); - set_default_partition(); - } + set_default_partition(); + } - /** + /** * @brief Load edges and then vertices for the graph. * * See @c load_edges() and @c load_vertices() for more information. @@ -718,146 +744,148 @@ class compressed_graph_base * @param eprojection Edge projection function object * @param vprojection Vertex projection function object */ - template - //requires copyable_edge>, VId, EV> && - // views::copyable_vertex>, VId, VV> - constexpr void load(const ERng& erng, const VRng& vrng, EProj eprojection = {}, VProj vprojection = {}) { - load_edges(erng, eprojection); - load_vertices(vrng, vprojection); // load the values - } + template + //requires copyable_edge>, VId, EV> && + // views::copyable_vertex>, VId, VV> + constexpr void load(const ERng& erng, const VRng& vrng, EProj eprojection = {}, VProj vprojection = {}) { + load_edges(erng, eprojection); + load_vertices(vrng, vprojection); // load the values + } -protected: - template - constexpr vertex_id_type last_erng_id(ERng&& erng, EProj eprojection) const { - vertex_id_type last_id = vertex_id_type(); - if constexpr (ranges::bidirectional_range) { - if (ranges::begin(erng) != ranges::end(erng)) { - auto lastIt = ranges::end(erng); - --lastIt; - auto&& e = eprojection(*lastIt); // copyable_edge - last_id = max(e.source_id, e.target_id); + protected: + template + constexpr vertex_id_type last_erng_id(ERng&& erng, EProj eprojection) const { + vertex_id_type last_id = vertex_id_type(); + if constexpr (ranges::bidirectional_range) { + if (ranges::begin(erng) != ranges::end(erng)) { + auto lastIt = ranges::end(erng); + --lastIt; + auto&& e = eprojection(*lastIt); // copyable_edge + last_id = max(e.source_id, e.target_id); + } } + return last_id; } - return last_id; - } - void set_default_partition() { - if (size(part_index_) == 0) { - part_index_.push_back(0); - part_index_.push_back(static_cast(size(row_index_))); - } else if (size(part_index_) == 2) { - part_index_.back() = static_cast(size(row_index_)); - } else { - assert(false); // Multiple partitions need different logic + void set_default_partition() { + if (size(part_index_) == 0) { + part_index_.push_back(0); + part_index_.push_back(static_cast(size(row_index_))); + } else if (size(part_index_) == 2) { + part_index_.back() = static_cast(size(row_index_)); + } else { + assert(false); // Multiple partitions need different logic + } } - } -public: // Operations - constexpr ranges::iterator_t find_vertex(vertex_id_type id) noexcept { - return row_index_.begin() + id; - } - constexpr ranges::iterator_t find_vertex(vertex_id_type id) const noexcept { - return row_index_.begin() + id; - } + public: // Operations + constexpr ranges::iterator_t find_vertex(vertex_id_type id) noexcept { + return row_index_.begin() + id; + } + constexpr ranges::iterator_t find_vertex(vertex_id_type id) const noexcept { + return row_index_.begin() + id; + } - constexpr edge_index_type index_of(const row_type& u) const noexcept { - return static_cast(&u - row_index_.data()); - } - constexpr vertex_id_type index_of(const col_type& v) const noexcept { - return static_cast(&v - col_index_.data()); - } + constexpr edge_index_type index_of(const row_type& u) const noexcept { + return static_cast(&u - row_index_.data()); + } + constexpr vertex_id_type index_of(const col_type& v) const noexcept { + return static_cast(&v - col_index_.data()); + } -public: // Operators - constexpr vertex_type& operator[](vertex_id_type id) noexcept { return row_index_[id]; } - constexpr const vertex_type& operator[](vertex_id_type id) const noexcept { return row_index_[id]; } - -private: // Member variables - row_index_vector row_index_; // starting index into col_index_ and v_; holds +1 extra terminating row - col_index_vector col_index_; // col_index_[n] holds the column index (aka target) - partition_index_vector - part_index_; // row_index_[part_index_[p]] is the first row of partition p; holds +1 extra for terminating row (size(row_index_)) - -private: // tag_invoke properties - friend constexpr vertices_type tag_invoke(::std::graph::tag_invoke::vertices_fn_t, compressed_graph_base& g) { - if (g.row_index_.empty()) - return vertices_type(g.row_index_); // really empty - else - return vertices_type(g.row_index_.begin(), g.row_index_.end() - 1); // don't include terminating row - } - friend constexpr const_vertices_type tag_invoke(::std::graph::tag_invoke::vertices_fn_t, - const compressed_graph_base& g) { - if (g.row_index_.empty()) - return const_vertices_type(g.row_index_); // really empty - else - return const_vertices_type(g.row_index_.begin(), g.row_index_.end() - 1); // don't include terminating row - } + public: // Operators + constexpr vertex_type& operator[](vertex_id_type id) noexcept { return row_index_[id]; } + constexpr const vertex_type& operator[](vertex_id_type id) const noexcept { return row_index_[id]; } + + private: // Member variables + row_index_vector row_index_; // starting index into col_index_ and v_; holds +1 extra terminating row + col_index_vector col_index_; // col_index_[n] holds the column index (aka target) + partition_index_vector + part_index_; // row_index_[part_index_[p]] is the first row of partition p; holds +1 extra for terminating row (size(row_index_)) + + private: // tag_invoke properties + friend constexpr vertices_type tag_invoke(::std::graph::tag_invoke::vertices_fn_t, compressed_graph_base& g) { + if (g.row_index_.empty()) + return vertices_type(g.row_index_); // really empty + else + return vertices_type(g.row_index_.begin(), g.row_index_.end() - 1); // don't include terminating row + } + friend constexpr const_vertices_type tag_invoke(::std::graph::tag_invoke::vertices_fn_t, + const compressed_graph_base& g) { + if (g.row_index_.empty()) + return const_vertices_type(g.row_index_); // really empty + else + return const_vertices_type(g.row_index_.begin(), g.row_index_.end() - 1); // don't include terminating row + } - friend vertex_id_type - tag_invoke(::std::graph::tag_invoke::vertex_id_fn_t, const compressed_graph_base& g, const_iterator ui) { - return static_cast(ui - g.row_index_.begin()); - } + friend vertex_id_type + tag_invoke(::std::graph::tag_invoke::vertex_id_fn_t, const compressed_graph_base& g, const_iterator ui) { + return static_cast(ui - g.row_index_.begin()); + } - friend constexpr edges_type tag_invoke(::std::graph::tag_invoke::edges_fn_t, graph_type& g, vertex_type& u) { - static_assert(ranges::contiguous_range, "row_index_ must be a contiguous range to get next row"); - vertex_type* u2 = &u + 1; - assert(static_cast(u2 - &u) < g.row_index_.size()); // in row_index_ bounds? - assert(static_cast(u.index) <= g.col_index_.size() && - static_cast(u2->index) <= g.col_index_.size()); // in col_index_ bounds? - return edges_type(g.col_index_.begin() + u.index, g.col_index_.begin() + u2->index); - } - friend constexpr const_edges_type - tag_invoke(::std::graph::tag_invoke::edges_fn_t, const graph_type& g, const vertex_type& u) { - static_assert(ranges::contiguous_range, "row_index_ must be a contiguous range to get next row"); - const vertex_type* u2 = &u + 1; - assert(static_cast(u2 - &u) < g.row_index_.size()); // in row_index_ bounds? - assert(static_cast(u.index) <= g.col_index_.size() && - static_cast(u2->index) <= g.col_index_.size()); // in col_index_ bounds? - return const_edges_type(g.col_index_.begin() + u.index, g.col_index_.begin() + u2->index); - } + friend constexpr edges_type tag_invoke(::std::graph::tag_invoke::edges_fn_t, graph_type& g, vertex_type& u) { + static_assert(ranges::contiguous_range, + "row_index_ must be a contiguous range to get next row"); + vertex_type* u2 = &u + 1; + assert(static_cast(u2 - &u) < g.row_index_.size()); // in row_index_ bounds? + assert(static_cast(u.index) <= g.col_index_.size() && + static_cast(u2->index) <= g.col_index_.size()); // in col_index_ bounds? + return edges_type(g.col_index_.begin() + u.index, g.col_index_.begin() + u2->index); + } + friend constexpr const_edges_type + tag_invoke(::std::graph::tag_invoke::edges_fn_t, const graph_type& g, const vertex_type& u) { + static_assert(ranges::contiguous_range, + "row_index_ must be a contiguous range to get next row"); + const vertex_type* u2 = &u + 1; + assert(static_cast(u2 - &u) < g.row_index_.size()); // in row_index_ bounds? + assert(static_cast(u.index) <= g.col_index_.size() && + static_cast(u2->index) <= g.col_index_.size()); // in col_index_ bounds? + return const_edges_type(g.col_index_.begin() + u.index, g.col_index_.begin() + u2->index); + } - friend constexpr edges_type - tag_invoke(::std::graph::tag_invoke::edges_fn_t, graph_type& g, const vertex_id_type uid) { - assert(static_cast(uid + 1) < g.row_index_.size()); // in row_index_ bounds? - assert(static_cast(g.row_index_[uid + 1].index) <= g.col_index_.size()); // in col_index_ bounds? - return edges_type(g.col_index_.begin() + g.row_index_[uid].index, - g.col_index_.begin() + g.row_index_[uid + 1].index); - } - friend constexpr const_edges_type - tag_invoke(::std::graph::tag_invoke::edges_fn_t, const graph_type& g, const vertex_id_type uid) { - assert(static_cast(uid + 1) < g.row_index_.size()); // in row_index_ bounds? - assert(static_cast(g.row_index_[uid + 1].index) <= g.col_index_.size()); // in col_index_ bounds? - return const_edges_type(g.col_index_.begin() + g.row_index_[uid].index, - g.col_index_.begin() + g.row_index_[uid + 1].index); - } + friend constexpr edges_type + tag_invoke(::std::graph::tag_invoke::edges_fn_t, graph_type& g, const vertex_id_type uid) { + assert(static_cast(uid + 1) < g.row_index_.size()); // in row_index_ bounds? + assert(static_cast(g.row_index_[uid + 1].index) <= g.col_index_.size()); // in col_index_ bounds? + return edges_type(g.col_index_.begin() + g.row_index_[uid].index, + g.col_index_.begin() + g.row_index_[uid + 1].index); + } + friend constexpr const_edges_type + tag_invoke(::std::graph::tag_invoke::edges_fn_t, const graph_type& g, const vertex_id_type uid) { + assert(static_cast(uid + 1) < g.row_index_.size()); // in row_index_ bounds? + assert(static_cast(g.row_index_[uid + 1].index) <= g.col_index_.size()); // in col_index_ bounds? + return const_edges_type(g.col_index_.begin() + g.row_index_[uid].index, + g.col_index_.begin() + g.row_index_[uid + 1].index); + } - // target_id(g,uv), target(g,uv) - friend constexpr vertex_id_type - tag_invoke(::std::graph::tag_invoke::target_id_fn_t, const graph_type& g, const edge_type& uv) noexcept { - return uv.index; - } - friend constexpr vertex_type& - tag_invoke(::std::graph::tag_invoke::target_fn_t, graph_type& g, edge_type& uv) noexcept { - return g.row_index_[uv.index]; - } - friend constexpr const vertex_type& - tag_invoke(::std::graph::tag_invoke::target_fn_t, const graph_type& g, const edge_type& uv) noexcept { - return g.row_index_[uv.index]; - } + // target_id(g,uv), target(g,uv) + friend constexpr vertex_id_type + tag_invoke(::std::graph::tag_invoke::target_id_fn_t, const graph_type& g, const edge_type& uv) noexcept { + return uv.index; + } + friend constexpr vertex_type& + tag_invoke(::std::graph::tag_invoke::target_fn_t, graph_type& g, edge_type& uv) noexcept { + return g.row_index_[uv.index]; + } + friend constexpr const vertex_type& + tag_invoke(::std::graph::tag_invoke::target_fn_t, const graph_type& g, const edge_type& uv) noexcept { + return g.row_index_[uv.index]; + } - // partitions - friend constexpr partition_id_type tag_invoke(::std::graph::tag_invoke::partition_count_fn_t, - const graph_type& g) noexcept { - return static_cast(size(g.part_index_) - 1); - } + // partitions + friend constexpr partition_id_type tag_invoke(::std::graph::tag_invoke::partition_count_fn_t, + const graph_type& g) noexcept { + return static_cast(size(g.part_index_) - 1); + } - friend row_values_base; - friend col_values_base; -}; + friend row_values_base; + friend col_values_base; + }; -/** + /** * @ingroup graph_containers * @brief Compressed Sparse Row adjacency graph container. * @@ -868,116 +896,116 @@ class compressed_graph_base * @tparam EIndex Edge Index type. This must be large enough for the count of edges. * @tparam Alloc Allocator type */ -template -class compressed_graph : public compressed_graph_base { -public: // Types - using graph_type = compressed_graph; - using base_type = compressed_graph_base; - - using edge_value_type = EV; - using vertex_value_type = VV; - using graph_value_type = GV; - using value_type = GV; - - using vertex_id_type = VId; - -public: // Construction/Destruction - constexpr compressed_graph() = default; - constexpr compressed_graph(const compressed_graph&) = default; - constexpr compressed_graph(compressed_graph&&) = default; - constexpr ~compressed_graph() = default; - - constexpr compressed_graph& operator=(const compressed_graph&) = default; - constexpr compressed_graph& operator=(compressed_graph&&) = default; - - // compressed_graph( alloc) - // compressed_graph(gv&, alloc) - // compressed_graph(gv&&, alloc) - - constexpr compressed_graph(const Alloc& alloc) : base_type(alloc) {} - constexpr compressed_graph(const graph_value_type& value, const Alloc& alloc = Alloc()) - : base_type(alloc), value_(value) {} - constexpr compressed_graph(graph_value_type&& value, const Alloc& alloc = Alloc()) - : base_type(alloc), value_(move(value)) {} - - // compressed_graph( erng, eprojection, alloc) - // compressed_graph(gv&, erng, eprojection, alloc) - // compressed_graph(gv&&, erng, eprojection, alloc) - - template - requires copyable_edge>, VId, EV> - constexpr compressed_graph(const ERng& erng, EProj eprojection, const Alloc& alloc = Alloc()) - : base_type(erng, eprojection, alloc) {} - - template - requires copyable_edge>, VId, EV> - constexpr compressed_graph(const graph_value_type& value, - const ERng& erng, - EProj eprojection, - const Alloc& alloc = Alloc()) - : base_type(erng, eprojection, alloc), value_(value) {} - - template - requires copyable_edge>, VId, EV> - constexpr compressed_graph(graph_value_type&& value, - const ERng& erng, - EProj eprojection, - const Alloc& alloc = Alloc()) - : base_type(erng, eprojection, alloc), value_(move(value)) {} - - // compressed_graph( erng, vrng, eprojection, vprojection, alloc) - // compressed_graph(gv&, erng, vrng, eprojection, vprojection, alloc) - // compressed_graph(gv&&, erng, vrng, eprojection, vprojection, alloc) - - template - requires copyable_edge>, VId, EV> && - copyable_vertex>, VId, VV> - constexpr compressed_graph(const ERng& erng, - const VRng& vrng, - EProj eprojection = {}, - VProj vprojection = {}, - const Alloc& alloc = Alloc()) - : base_type(erng, vrng, eprojection, vprojection, alloc) {} - - template - requires copyable_edge>, VId, EV> && - copyable_vertex>, VId, VV> - constexpr compressed_graph(const graph_value_type& value, - const ERng& erng, - const VRng& vrng, - EProj eprojection = {}, - VProj vprojection = {}, - const Alloc& alloc = Alloc()) - : base_type(erng, vrng, eprojection, vprojection, alloc), value_(value) {} - - template - requires copyable_edge>, VId, EV> && - copyable_vertex>, VId, VV> - constexpr compressed_graph(graph_value_type&& value, - const ERng& erng, - const VRng& vrng, - EProj eprojection = {}, - VProj vprojection = {}, - const Alloc& alloc = Alloc()) - : base_type(erng, vrng, eprojection, vprojection, alloc), value_(move(value)) {} - - - constexpr compressed_graph(const initializer_list>& ilist, const Alloc& alloc = Alloc()) - : base_type(ilist, alloc) {} - -private: // tag_invoke properties - friend constexpr value_type& tag_invoke(::std::graph::tag_invoke::graph_value_fn_t, graph_type& g) { - return g.value_; - } - friend constexpr const value_type& tag_invoke(::std::graph::tag_invoke::graph_value_fn_t, const graph_type& g) { - return g.value_; - } + template + class compressed_graph : public compressed_graph_base { + public: // Types + using graph_type = compressed_graph; + using base_type = compressed_graph_base; + + using edge_value_type = EV; + using vertex_value_type = VV; + using graph_value_type = GV; + using value_type = GV; + + using vertex_id_type = VId; + + public: // Construction/Destruction + constexpr compressed_graph() = default; + constexpr compressed_graph(const compressed_graph&) = default; + constexpr compressed_graph(compressed_graph&&) = default; + constexpr ~compressed_graph() = default; + + constexpr compressed_graph& operator=(const compressed_graph&) = default; + constexpr compressed_graph& operator=(compressed_graph&&) = default; + + // compressed_graph( alloc) + // compressed_graph(gv&, alloc) + // compressed_graph(gv&&, alloc) + + constexpr compressed_graph(const Alloc& alloc) : base_type(alloc) {} + constexpr compressed_graph(const graph_value_type& value, const Alloc& alloc = Alloc()) + : base_type(alloc), value_(value) {} + constexpr compressed_graph(graph_value_type&& value, const Alloc& alloc = Alloc()) + : base_type(alloc), value_(move(value)) {} + + // compressed_graph( erng, eprojection, alloc) + // compressed_graph(gv&, erng, eprojection, alloc) + // compressed_graph(gv&&, erng, eprojection, alloc) + + template + requires copyable_edge>, VId, EV> + constexpr compressed_graph(const ERng& erng, EProj eprojection, const Alloc& alloc = Alloc()) + : base_type(erng, eprojection, alloc) {} + + template + requires copyable_edge>, VId, EV> + constexpr compressed_graph(const graph_value_type& value, + const ERng& erng, + EProj eprojection, + const Alloc& alloc = Alloc()) + : base_type(erng, eprojection, alloc), value_(value) {} + + template + requires copyable_edge>, VId, EV> + constexpr compressed_graph(graph_value_type&& value, + const ERng& erng, + EProj eprojection, + const Alloc& alloc = Alloc()) + : base_type(erng, eprojection, alloc), value_(move(value)) {} + + // compressed_graph( erng, vrng, eprojection, vprojection, alloc) + // compressed_graph(gv&, erng, vrng, eprojection, vprojection, alloc) + // compressed_graph(gv&&, erng, vrng, eprojection, vprojection, alloc) + + template + requires copyable_edge>, VId, EV> && + copyable_vertex>, VId, VV> + constexpr compressed_graph(const ERng& erng, + const VRng& vrng, + EProj eprojection = {}, + VProj vprojection = {}, + const Alloc& alloc = Alloc()) + : base_type(erng, vrng, eprojection, vprojection, alloc) {} + + template + requires copyable_edge>, VId, EV> && + copyable_vertex>, VId, VV> + constexpr compressed_graph(const graph_value_type& value, + const ERng& erng, + const VRng& vrng, + EProj eprojection = {}, + VProj vprojection = {}, + const Alloc& alloc = Alloc()) + : base_type(erng, vrng, eprojection, vprojection, alloc), value_(value) {} + + template + requires copyable_edge>, VId, EV> && + copyable_vertex>, VId, VV> + constexpr compressed_graph(graph_value_type&& value, + const ERng& erng, + const VRng& vrng, + EProj eprojection = {}, + VProj vprojection = {}, + const Alloc& alloc = Alloc()) + : base_type(erng, vrng, eprojection, vprojection, alloc), value_(move(value)) {} + + + constexpr compressed_graph(const initializer_list>& ilist, const Alloc& alloc = Alloc()) + : base_type(ilist, alloc) {} + + private: // tag_invoke properties + friend constexpr value_type& tag_invoke(::std::graph::tag_invoke::graph_value_fn_t, graph_type& g) { + return g.value_; + } + friend constexpr const value_type& tag_invoke(::std::graph::tag_invoke::graph_value_fn_t, const graph_type& g) { + return g.value_; + } -private: // Member variables - graph_value_type value_ = graph_value_type(); -}; + private: // Member variables + graph_value_type value_ = graph_value_type(); + }; -/** + /** * @ingroup graph_containers * @brief Compressed Sparse Row adjacency graph container. * @@ -987,52 +1015,52 @@ class compressed_graph : public compressed_graph_base -class compressed_graph - : public compressed_graph_base { -public: // Types - using graph_type = compressed_graph; - using base_type = compressed_graph_base; - - using vertex_id_type = VId; - using vertex_value_type = VV; - - using graph_value_type = void; - using value_type = void; - -public: // Construction/Destruction - constexpr compressed_graph() = default; - constexpr compressed_graph(const compressed_graph&) = default; - constexpr compressed_graph(compressed_graph&&) = default; - constexpr ~compressed_graph() = default; - - constexpr compressed_graph& operator=(const compressed_graph&) = default; - constexpr compressed_graph& operator=(compressed_graph&&) = default; - - // edge-only construction - template - requires copyable_edge>, VId, EV> - constexpr compressed_graph(const ERng& erng, EProj eprojection, const Alloc& alloc = Alloc()) - : base_type(erng, eprojection, alloc) {} - - // edge and vertex value construction - template - requires copyable_edge>, VId, EV> && - copyable_vertex>, VId, VV> - constexpr compressed_graph(const ERng& erng, - const VRng& vrng, - EProj eprojection = {}, - VProj vprojection = {}, - const Alloc& alloc = Alloc()) - : base_type(erng, vrng, eprojection, vprojection, alloc) {} - - // initializer list using edge_descriptor - constexpr compressed_graph(const initializer_list>& ilist, const Alloc& alloc = Alloc()) - : base_type(ilist, alloc) {} - - -public: // Operations -private: // tag_invoke properties -}; + template + class compressed_graph + : public compressed_graph_base { + public: // Types + using graph_type = compressed_graph; + using base_type = compressed_graph_base; + + using vertex_id_type = VId; + using vertex_value_type = VV; + + using graph_value_type = void; + using value_type = void; + + public: // Construction/Destruction + constexpr compressed_graph() = default; + constexpr compressed_graph(const compressed_graph&) = default; + constexpr compressed_graph(compressed_graph&&) = default; + constexpr ~compressed_graph() = default; + + constexpr compressed_graph& operator=(const compressed_graph&) = default; + constexpr compressed_graph& operator=(compressed_graph&&) = default; + + // edge-only construction + template + requires copyable_edge>, VId, EV> + constexpr compressed_graph(const ERng& erng, EProj eprojection, const Alloc& alloc = Alloc()) + : base_type(erng, eprojection, alloc) {} + + // edge and vertex value construction + template + requires copyable_edge>, VId, EV> && + copyable_vertex>, VId, VV> + constexpr compressed_graph(const ERng& erng, + const VRng& vrng, + EProj eprojection = {}, + VProj vprojection = {}, + const Alloc& alloc = Alloc()) + : base_type(erng, vrng, eprojection, vprojection, alloc) {} + + // initializer list using edge_descriptor + constexpr compressed_graph(const initializer_list>& ilist, const Alloc& alloc = Alloc()) + : base_type(ilist, alloc) {} + + + public: // Operations + private: // tag_invoke properties + }; } // namespace std::graph::container From 3f1eced95790068a2803feb4dc84966be76607e3 Mon Sep 17 00:00:00 2001 From: Phil Ratzloff Date: Wed, 1 Nov 2023 15:24:43 -0400 Subject: [PATCH 14/15] Add num_vertices(g) CPO --- include/graph/detail/graph_cpo.hpp | 39 +++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/include/graph/detail/graph_cpo.hpp b/include/graph/detail/graph_cpo.hpp index 0fffe1d..8a7536f 100644 --- a/include/graph/detail/graph_cpo.hpp +++ b/include/graph/detail/graph_cpo.hpp @@ -641,6 +641,42 @@ auto contains_edge(G&& g, vertex_id_t uid, vertex_id_t vid) { } } + +// +// num_vertices(g) -> integral +// default = size(vertices(g)) +// +namespace tag_invoke { + TAG_INVOKE_DEF(num_vertices); + + template + concept _has_num_vertices_adl = requires(G&& g, vertex_reference_t u) { + { num_vertices(g) }; + }; +} // namespace tag_invoke + +/** + * @brief The number of outgoing edges of a vertex. + * + * Complexity: O(1) + * + * Default implementation: size(vertices(g)) + * + * @tparam G The graph type. + * @param g A graph instance. + * @return The number of vertices in a graph. +*/ +template +requires tag_invoke::_has_num_vertices_adl +auto num_vertices(G&& g) { + if constexpr (tag_invoke::_has_num_vertices_adl) + return tag_invoke::num_vertices(g); + else { + return ranges::size(vertices(g)); + } +} + + // // degree(g,u) -> integral // default = size(edges(g,u)) if sized_range> @@ -821,7 +857,6 @@ namespace edgelist { using edge_value_t = decltype(edge_value(declval(), declval>())); } // namespace edgelist -# if 1 // bipartite idea //template , // class VV = tuple, @@ -1470,8 +1505,6 @@ inline namespace _Cpos { inline constexpr _Partition_source_id::_Cpo partition_source_id; } -# endif - } // namespace std::graph From 9cf7b2a873a5515f3826e937c1c9d7862fc06d37 Mon Sep 17 00:00:00 2001 From: Phil Ratzloff Date: Wed, 1 Nov 2023 17:15:01 -0400 Subject: [PATCH 15/15] Back out formatting changes and function prototypes for P1709 --- include/graph/container/compressed_graph.hpp | 1474 +++++++++--------- 1 file changed, 702 insertions(+), 772 deletions(-) diff --git a/include/graph/container/compressed_graph.hpp b/include/graph/container/compressed_graph.hpp index 06287e2..a13a185 100644 --- a/include/graph/container/compressed_graph.hpp +++ b/include/graph/container/compressed_graph.hpp @@ -8,28 +8,6 @@ #include #include "graph/graph.hpp" - -// Example prototypes for P1709 -namespace std::graph::prototypes { -template -requires copyable_vertex>, vertex_id_t, vertex_value_t> -constexpr void load_vertices(G&, const VRng&, VProj); - -template -requires copyable_edge>, vertex_id_t, edge_value_t> -constexpr void load_edges(G&, const ERng&, EProj = {}, ranges::range_size_t vertex_count = 0); - -template -requires copyable_edge>, vertex_id_t, edge_value_t> && - copyable_vertex>, vertex_id_t, edge_value_t> -constexpr void load(G&, const ERng&, const VRng&, EProj = {}, VProj = {}); -} // namespace std::graph - - // NOTES // have public load_edges(...), load_vertices(...), and load() // allow separation of construction and load @@ -49,7 +27,7 @@ constexpr void load(G&, const ERng&, const VRng&, EProj = {}, VProj = {}); // namespace std::graph::container { - /** +/** * @ingroup graph_containers * @brief Scans a range used for input for loading edges to determine the largest vertex id used. * @@ -65,59 +43,59 @@ namespace std::graph::container { * * @return A @c pair with the max vertex id used and the number of edges scanned. */ - template - requires copyable_edge>, VId, EV> - constexpr auto max_vertex_id(const ERng& erng, const EProj& eprojection) { - size_t edge_count = 0; - VId max_id = 0; - for (auto&& edge_data : erng) { - const copyable_edge_t& uv = eprojection(edge_data); - max_id = max(max_id, max(static_cast(uv.source_id), static_cast(uv.target_id))); - ++edge_count; - } - return pair(max_id, edge_count); +template +requires copyable_edge>, VId, EV> +constexpr auto max_vertex_id(const ERng& erng, const EProj& eprojection) { + size_t edge_count = 0; + VId max_id = 0; + for (auto&& edge_data : erng) { + const copyable_edge_t& uv = eprojection(edge_data); + max_id = max(max_id, max(static_cast(uv.source_id), static_cast(uv.target_id))); + ++edge_count; } + return pair(max_id, edge_count); +} - // - // forward declarations - // - template > // for internal containers - class compressed_graph; - - /** +// +// forward declarations +// +template > // for internal containers +class compressed_graph; + +/** * @ingroup graph_containers * @brief Wrapper struct for the row index to distinguish it from a vertex_id_type (VId). * * @tparam EIndex The type for storing an edge index. It must be able to store a value of |E|+1, * where |E| is the total number of edges in the graph. */ - template - struct csr_row { - using edge_index_type = EIndex; - edge_index_type index = 0; - }; +template +struct csr_row { + using edge_index_type = EIndex; + edge_index_type index = 0; +}; - /** +/** * @ingroup graph_containers * @brief Wrapper struct for the col (edge) index to distinguish it from a vertex_id_type (VId). * * @tparam VId Vertex id type. The type must be able to store a value of |V|+1, where |V| is the * number of vertices in the graph. */ - template - struct csr_col { - using vertex_id_type = VId; - vertex_id_type index = 0; - }; +template +struct csr_col { + using vertex_id_type = VId; + vertex_id_type index = 0; +}; - /** +/** * @ingroup graph_containers * @brief Holds vertex values in a vector that is the same size as @c row_index_. * @@ -136,151 +114,139 @@ namespace std::graph::container { * where |E| is the total number of edges in the graph. * @tparam Alloc The allocator type. */ - template - class csr_row_values { - using row_type = csr_row; // index into col_index_ - using row_allocator_type = typename allocator_traits::template rebind_alloc; - using row_index_vector = vector; - - public: - using graph_type = compressed_graph; - using vertex_type = row_type; - using vertex_value_type = VV; - using allocator_type = typename allocator_traits::template rebind_alloc; - using vector_type = vector; - - using value_type = VV; - using size_type = size_t; //VId; - using difference_type = typename vector_type::difference_type; - using reference = value_type&; - using const_reference = const value_type&; - using pointer = typename vector_type::pointer; - using const_pointer = typename vector_type::const_pointer; - using iterator = typename vector_type::iterator; - using const_iterator = typename vector_type::const_iterator; - - constexpr csr_row_values(const Alloc& alloc) : v_(alloc) {} - - constexpr csr_row_values() = default; - constexpr csr_row_values(const csr_row_values&) = default; - constexpr csr_row_values(csr_row_values&&) = default; - constexpr ~csr_row_values() = default; - - constexpr csr_row_values& operator=(const csr_row_values&) = default; - constexpr csr_row_values& operator=(csr_row_values&&) = default; - - - public: // Properties - [[nodiscard]] constexpr size_type size() const noexcept { return static_cast(v_.size()); } - [[nodiscard]] constexpr bool empty() const noexcept { return v_.empty(); } - [[nodiscard]] constexpr size_type capacity() const noexcept { return static_cast(v_.size()); } - - public: // Operations - constexpr void reserve(size_type new_cap) { v_.reserve(new_cap); } - constexpr void resize(size_type new_size) { v_.resize(new_size); } - - constexpr void clear() noexcept { v_.clear(); } - constexpr void push_back(const value_type& value) { v_.push_back(value); } - constexpr void emplace_back(value_type&& value) { v_.emplace_back(forward(value)); } - - constexpr void swap(csr_row_values& other) noexcept { swap(v_, other.v_); } - - template - //requires copyable_vertex>, VId, VV> - constexpr void load_row_values(const VRng& vrng, VProj projection, size_type vertex_count) { - static_assert(copyable_vertex>, VId, VV>); - - if constexpr (ranges::sized_range) - vertex_count = max(vertex_count, ranges::size(vrng)); - resize(ranges::size(vrng)); - - for (auto&& vtx : vrng) { - const auto& [id, value] = projection(vtx); - - // if an unsized vrng is passed, the caller is responsible to call - // resize_vertices(n) with enough entries for all the values. - assert(static_cast(id) < size()); - - (*this)[static_cast(id)] = value; - } +template +class csr_row_values { + using row_type = csr_row; // index into col_index_ + using row_allocator_type = typename allocator_traits::template rebind_alloc; + using row_index_vector = vector; + +public: + using graph_type = compressed_graph; + using vertex_type = row_type; + using vertex_value_type = VV; + using allocator_type = typename allocator_traits::template rebind_alloc; + using vector_type = vector; + + using value_type = VV; + using size_type = size_t; //VId; + using difference_type = typename vector_type::difference_type; + using reference = value_type&; + using const_reference = const value_type&; + using pointer = typename vector_type::pointer; + using const_pointer = typename vector_type::const_pointer; + using iterator = typename vector_type::iterator; + using const_iterator = typename vector_type::const_iterator; + + constexpr csr_row_values(const Alloc& alloc) : v_(alloc) {} + + constexpr csr_row_values() = default; + constexpr csr_row_values(const csr_row_values&) = default; + constexpr csr_row_values(csr_row_values&&) = default; + constexpr ~csr_row_values() = default; + + constexpr csr_row_values& operator=(const csr_row_values&) = default; + constexpr csr_row_values& operator=(csr_row_values&&) = default; + + +public: // Properties + [[nodiscard]] constexpr size_type size() const noexcept { return static_cast(v_.size()); } + [[nodiscard]] constexpr bool empty() const noexcept { return v_.empty(); } + [[nodiscard]] constexpr size_type capacity() const noexcept { return static_cast(v_.size()); } + +public: // Operations + constexpr void reserve(size_type new_cap) { v_.reserve(new_cap); } + constexpr void resize(size_type new_size) { v_.resize(new_size); } + + constexpr void clear() noexcept { v_.clear(); } + constexpr void push_back(const value_type& value) { v_.push_back(value); } + constexpr void emplace_back(value_type&& value) { v_.emplace_back(forward(value)); } + + constexpr void swap(csr_row_values& other) noexcept { swap(v_, other.v_); } + + template + //requires views::copyable_vertex>, VId, VV> + constexpr void load_row_values(const VRng& vrng, VProj projection, size_type vertex_count) { + if constexpr (ranges::sized_range) + vertex_count = max(vertex_count, ranges::size(vrng)); + resize(ranges::size(vrng)); + + for (auto&& vtx : vrng) { + const auto& [id, value] = projection(vtx); + + // if an unsized vrng is passed, the caller is responsible to call + // resize_vertices(n) with enough entries for all the values. + assert(static_cast(id) < size()); + + (*this)[static_cast(id)] = value; } + } - template - //requires copyable_vertex>, VId, VV> - constexpr void load_row_values(VRng&& vrng, VProj projection, size_type vertex_count) { - //using result_t = invoke_result_t>; - //using desc_t = vertex_descriptor; - - //result_t result; - //desc_t desc{result}; - //static_assert(convertible_to); - //static_assert(copyable_vertex>, VId, VV>); - - if constexpr (ranges::sized_range) - vertex_count = max(vertex_count, ranges::size(vrng)); - resize(ranges::size(vrng)); + template + //requires views::copyable_vertex>, VId, VV> + constexpr void load_row_values(VRng&& vrng, VProj projection, size_type vertex_count) { + if constexpr (ranges::sized_range) + vertex_count = max(vertex_count, ranges::size(vrng)); + resize(ranges::size(vrng)); - for (auto&& vtx : vrng) { - auto&& [id, value] = projection(vtx); + for (auto&& vtx : vrng) { + auto&& [id, value] = projection(vtx); - // if an unsized vrng is passed, the caller is responsible to call - // resize_vertices(n) with enough entries for all the values. - assert(static_cast(id) < size()); + // if an unsized vrng is passed, the caller is responsible to call + // resize_vertices(n) with enough entries for all the values. + assert(static_cast(id) < size()); - (*this)[id] = std::move(value); - } + (*this)[id] = std::move(value); } + } - public: - constexpr reference operator[](size_type pos) { return v_[pos]; } - constexpr const_reference operator[](size_type pos) const { return v_[pos]; } - - private: - friend constexpr vertex_value_type& - tag_invoke(::std::graph::tag_invoke::vertex_value_fn_t, graph_type& g, vertex_type& u) { - static_assert(ranges::contiguous_range, - "row_index_ must be a contiguous range to evaluate uidx"); - auto uidx = g.index_of(u); - csr_row_values& row_vals = g; - return row_vals.v_[uidx]; - } - friend constexpr const vertex_value_type& - tag_invoke(::std::graph::tag_invoke::vertex_value_fn_t, const graph_type& g, const vertex_type& u) { - static_assert(ranges::contiguous_range, - "row_index_ must be a contiguous range to evaluate uidx"); - auto uidx = g.index_of(u); - const csr_row_values& row_vals = g; - return row_vals.v_[uidx]; - } +public: + constexpr reference operator[](size_type pos) { return v_[pos]; } + constexpr const_reference operator[](size_type pos) const { return v_[pos]; } + +private: + friend constexpr vertex_value_type& + tag_invoke(::std::graph::tag_invoke::vertex_value_fn_t, graph_type& g, vertex_type& u) { + static_assert(ranges::contiguous_range, "row_index_ must be a contiguous range to evaluate uidx"); + auto uidx = g.index_of(u); + csr_row_values& row_vals = g; + return row_vals.v_[uidx]; + } + friend constexpr const vertex_value_type& + tag_invoke(::std::graph::tag_invoke::vertex_value_fn_t, const graph_type& g, const vertex_type& u) { + static_assert(ranges::contiguous_range, "row_index_ must be a contiguous range to evaluate uidx"); + auto uidx = g.index_of(u); + const csr_row_values& row_vals = g; + return row_vals.v_[uidx]; + } - private: - vector_type v_; - }; +private: + vector_type v_; +}; - template - class csr_row_values { - public: - constexpr csr_row_values(const Alloc& alloc) {} - constexpr csr_row_values() = default; +template +class csr_row_values { +public: + constexpr csr_row_values(const Alloc& alloc) {} + constexpr csr_row_values() = default; - using value_type = void; - using size_type = size_t; //VId; + using value_type = void; + using size_type = size_t; //VId; - public: // Properties - [[nodiscard]] constexpr size_type size() const noexcept { return 0; } - [[nodiscard]] constexpr bool empty() const noexcept { return true; } - [[nodiscard]] constexpr size_type capacity() const noexcept { return 0; } +public: // Properties + [[nodiscard]] constexpr size_type size() const noexcept { return 0; } + [[nodiscard]] constexpr bool empty() const noexcept { return true; } + [[nodiscard]] constexpr size_type capacity() const noexcept { return 0; } - public: // Operations - constexpr void reserve(size_type new_cap) {} - constexpr void resize(size_type new_size) {} +public: // Operations + constexpr void reserve(size_type new_cap) {} + constexpr void resize(size_type new_size) {} - constexpr void clear() noexcept {} - constexpr void swap(csr_row_values& other) noexcept {} - }; + constexpr void clear() noexcept {} + constexpr void swap(csr_row_values& other) noexcept {} +}; - /** +/** * @ingroup graph_containers * @brief Class to hold vertex values in a vector that is the same size as col_index_. * @@ -299,102 +265,102 @@ namespace std::graph::container { * where |E| is the total number of edges in the graph. * @tparam Alloc The allocator type. */ - template - class csr_col_values { - using col_type = csr_col; // target_id - using col_allocator_type = typename allocator_traits::template rebind_alloc; - using col_index_vector = vector; - - public: - using graph_type = compressed_graph; - using edge_type = col_type; // index into v_ - using edge_value_type = EV; - using allocator_type = typename allocator_traits::template rebind_alloc; - using vector_type = vector; - - using value_type = EV; - using size_type = size_t; //VId; - using difference_type = typename vector_type::difference_type; - using reference = value_type&; - using const_reference = const value_type&; - using pointer = typename vector_type::pointer; - using const_pointer = typename vector_type::const_pointer; - using iterator = typename vector_type::iterator; - using const_iterator = typename vector_type::const_iterator; - - constexpr csr_col_values(const Alloc& alloc) : v_(alloc) {} - - constexpr csr_col_values() = default; - constexpr csr_col_values(const csr_col_values&) = default; - constexpr csr_col_values(csr_col_values&&) = default; - constexpr ~csr_col_values() = default; - - constexpr csr_col_values& operator=(const csr_col_values&) = default; - constexpr csr_col_values& operator=(csr_col_values&&) = default; - - - public: // Properties - [[nodiscard]] constexpr size_type size() const noexcept { return static_cast(v_.size()); } - [[nodiscard]] constexpr bool empty() const noexcept { return v_.empty(); } - [[nodiscard]] constexpr size_type capacity() const noexcept { return static_cast(v_.size()); } - - public: // Operations - constexpr void reserve(size_type new_cap) { v_.reserve(new_cap); } - constexpr void resize(size_type new_size) { v_.resize(new_size); } - - constexpr void clear() noexcept { v_.clear(); } - constexpr void push_back(const value_type& value) { v_.push_back(value); } - constexpr void emplace_back(value_type&& value) { v_.emplace_back(forward(value)); } - - constexpr void swap(csr_col_values& other) noexcept { swap(v_, other.v_); } - - public: - constexpr reference operator[](size_type pos) { return v_[pos]; } - constexpr const_reference operator[](size_type pos) const { return v_[pos]; } - - private: - // edge_value(g,uv) - friend constexpr edge_value_type& - tag_invoke(::std::graph::tag_invoke::edge_value_fn_t, graph_type& g, edge_type& uv) { - auto uv_idx = g.index_of(uv); - csr_col_values& col_vals = g; - return col_vals.v_[uv_idx]; - } - friend constexpr const edge_value_type& - tag_invoke(::std::graph::tag_invoke::edge_value_fn_t, const graph_type& g, const edge_type& uv) { - auto uv_idx = g.index_of(uv); - const csr_col_values& col_vals = g; - return col_vals.v_[uv_idx]; - } +template +class csr_col_values { + using col_type = csr_col; // target_id + using col_allocator_type = typename allocator_traits::template rebind_alloc; + using col_index_vector = vector; + +public: + using graph_type = compressed_graph; + using edge_type = col_type; // index into v_ + using edge_value_type = EV; + using allocator_type = typename allocator_traits::template rebind_alloc; + using vector_type = vector; + + using value_type = EV; + using size_type = size_t; //VId; + using difference_type = typename vector_type::difference_type; + using reference = value_type&; + using const_reference = const value_type&; + using pointer = typename vector_type::pointer; + using const_pointer = typename vector_type::const_pointer; + using iterator = typename vector_type::iterator; + using const_iterator = typename vector_type::const_iterator; + + constexpr csr_col_values(const Alloc& alloc) : v_(alloc) {} + + constexpr csr_col_values() = default; + constexpr csr_col_values(const csr_col_values&) = default; + constexpr csr_col_values(csr_col_values&&) = default; + constexpr ~csr_col_values() = default; + + constexpr csr_col_values& operator=(const csr_col_values&) = default; + constexpr csr_col_values& operator=(csr_col_values&&) = default; + + +public: // Properties + [[nodiscard]] constexpr size_type size() const noexcept { return static_cast(v_.size()); } + [[nodiscard]] constexpr bool empty() const noexcept { return v_.empty(); } + [[nodiscard]] constexpr size_type capacity() const noexcept { return static_cast(v_.size()); } + +public: // Operations + constexpr void reserve(size_type new_cap) { v_.reserve(new_cap); } + constexpr void resize(size_type new_size) { v_.resize(new_size); } + + constexpr void clear() noexcept { v_.clear(); } + constexpr void push_back(const value_type& value) { v_.push_back(value); } + constexpr void emplace_back(value_type&& value) { v_.emplace_back(forward(value)); } + + constexpr void swap(csr_col_values& other) noexcept { swap(v_, other.v_); } + +public: + constexpr reference operator[](size_type pos) { return v_[pos]; } + constexpr const_reference operator[](size_type pos) const { return v_[pos]; } + +private: + // edge_value(g,uv) + friend constexpr edge_value_type& + tag_invoke(::std::graph::tag_invoke::edge_value_fn_t, graph_type& g, edge_type& uv) { + auto uv_idx = g.index_of(uv); + csr_col_values& col_vals = g; + return col_vals.v_[uv_idx]; + } + friend constexpr const edge_value_type& + tag_invoke(::std::graph::tag_invoke::edge_value_fn_t, const graph_type& g, const edge_type& uv) { + auto uv_idx = g.index_of(uv); + const csr_col_values& col_vals = g; + return col_vals.v_[uv_idx]; + } - private: - vector_type v_; - }; +private: + vector_type v_; +}; - template - class csr_col_values { - public: - constexpr csr_col_values(const Alloc& alloc) {} - constexpr csr_col_values() = default; +template +class csr_col_values { +public: + constexpr csr_col_values(const Alloc& alloc) {} + constexpr csr_col_values() = default; - using value_type = void; - using size_type = size_t; //VId; + using value_type = void; + using size_type = size_t; //VId; - public: // Properties - [[nodiscard]] constexpr size_type size() const noexcept { return 0; } - [[nodiscard]] constexpr bool empty() const noexcept { return true; } - [[nodiscard]] constexpr size_type capacity() const noexcept { return 0; } +public: // Properties + [[nodiscard]] constexpr size_type size() const noexcept { return 0; } + [[nodiscard]] constexpr bool empty() const noexcept { return true; } + [[nodiscard]] constexpr size_type capacity() const noexcept { return 0; } - public: // Operations - constexpr void reserve(size_type new_cap) {} - constexpr void resize(size_type new_size) {} +public: // Operations + constexpr void reserve(size_type new_cap) {} + constexpr void resize(size_type new_size) {} - constexpr void clear() noexcept {} - constexpr void swap(csr_col_values& other) noexcept {} - }; + constexpr void clear() noexcept {} + constexpr void swap(csr_col_values& other) noexcept {} +}; - /** +/** * @ingroup graph_containers * @brief Base class for compressed sparse row adjacency graph * @@ -410,63 +376,54 @@ namespace std::graph::container { * where |E| is the total number of edges in the graph. * @tparam Alloc The allocator type. */ - template - class compressed_graph_base - : public csr_row_values - , public csr_col_values { - using row_values_base = csr_row_values; - using col_values_base = csr_col_values; - - using row_type = csr_row; // index into col_index_ - using row_allocator_type = typename allocator_traits::template rebind_alloc; - using row_index_vector = vector; - - using partition_type = VId; // index into row_indexes - using partition_allocator_type = typename allocator_traits::template rebind_alloc; - using partition_index_vector = vector; - - using col_type = csr_col; // target_id - using col_allocator_type = typename allocator_traits::template rebind_alloc; - using col_index_vector = vector; - - public: // Types - using graph_type = compressed_graph_base; - - using vertex_id_type = VId; - using vertex_type = row_type; - using vertex_value_type = VV; - using vertices_type = ranges::subrange>; - using const_vertices_type = ranges::subrange>; - - using partition_id_type = vertex_id_type; - using partition_size_type = vertex_id_type; - - using edge_type = col_type; // index into v_ - using edge_value_type = EV; - using edge_index_type = EIndex; - using edges_type = ranges::subrange>; - using const_edges_type = ranges::subrange>; - - using const_iterator = typename row_index_vector::const_iterator; - using iterator = typename row_index_vector::iterator; - - using size_type = ranges::range_size_t; - - public: // Construction/Destruction - constexpr compressed_graph_base() = default; - constexpr compressed_graph_base(const compressed_graph_base&) = default; - constexpr compressed_graph_base(compressed_graph_base&&) = default; - constexpr ~compressed_graph_base() = default; - - constexpr compressed_graph_base& operator=(const compressed_graph_base&) = default; - constexpr compressed_graph_base& operator=(compressed_graph_base&&) = default; - - constexpr compressed_graph_base(const Alloc& alloc) - : row_values_base(alloc), col_values_base(alloc), row_index_(alloc), col_index_(alloc), part_index_(alloc) { - set_default_partition(); - } +template +class compressed_graph_base + : public csr_row_values + , public csr_col_values { + using row_values_base = csr_row_values; + using col_values_base = csr_col_values; + + using row_type = csr_row; // index into col_index_ + using row_allocator_type = typename allocator_traits::template rebind_alloc; + using row_index_vector = vector; + + using col_type = csr_col; // target_id + using col_allocator_type = typename allocator_traits::template rebind_alloc; + using col_index_vector = vector; - /** +public: // Types + using graph_type = compressed_graph_base; + + using vertex_id_type = VId; + using vertex_type = row_type; + using vertex_value_type = VV; + using vertices_type = ranges::subrange>; + using const_vertices_type = ranges::subrange>; + + using edge_type = col_type; // index into v_ + using edge_value_type = EV; + using edge_index_type = EIndex; + using edges_type = ranges::subrange>; + using const_edges_type = ranges::subrange>; + + using const_iterator = typename row_index_vector::const_iterator; + using iterator = typename row_index_vector::iterator; + + using size_type = ranges::range_size_t; + +public: // Construction/Destruction + constexpr compressed_graph_base() = default; + constexpr compressed_graph_base(const compressed_graph_base&) = default; + constexpr compressed_graph_base(compressed_graph_base&&) = default; + constexpr ~compressed_graph_base() = default; + + constexpr compressed_graph_base& operator=(const compressed_graph_base&) = default; + constexpr compressed_graph_base& operator=(compressed_graph_base&&) = default; + + constexpr compressed_graph_base(const Alloc& alloc) + : row_values_base(alloc), col_values_base(alloc), row_index_(alloc), col_index_(alloc) {} + + /** * @brief Constructor that takes a edge range to create the CSR graph. * * Edges must be ordered by source_id (enforced by asssertion). @@ -478,15 +435,15 @@ namespace std::graph::container { * @param eprojection Projection function that creates a @c copyable_edge_t from an erng value * @param alloc Allocator to use for internal containers */ - template - requires copyable_edge>, VId, EV> - constexpr compressed_graph_base(const ERng& erng, EProj eprojection = {}, const Alloc& alloc = Alloc()) - : row_values_base(alloc), col_values_base(alloc), row_index_(alloc), col_index_(alloc), part_index_(alloc) { + template + requires copyable_edge>, VId, EV> + constexpr compressed_graph_base(const ERng& erng, EProj eprojection = {}, const Alloc& alloc = Alloc()) + : row_values_base(alloc), col_values_base(alloc), row_index_(alloc), col_index_(alloc) { - load_edges(erng, eprojection); - } + load_edges(erng, eprojection); + } - /** + /** * @brief Constructor that takes edge range and vertex range to create the CSR graph. * * Edges must be ordered by source_id (enforced by asssertion). @@ -502,52 +459,52 @@ namespace std::graph::container { * @param vprojection Projection function that creates a @c copyable_vertex_t from a @c vrng value * @param alloc Allocator to use for internal containers */ - template - //requires copyable_edge>, VId, EV> && - // copyable_vertex>, VId, VV> - constexpr compressed_graph_base(const ERng& erng, - const VRng& vrng, - EProj eprojection = {}, // eproj(eval) -> {source_id,target_id [,value]} - VProj vprojection = {}, // vproj(vval) -> {target_id [,value]} - const Alloc& alloc = Alloc()) - : row_values_base(alloc), col_values_base(alloc), row_index_(alloc), col_index_(alloc), part_index_(alloc) { - - load(erng, vrng, eprojection, vprojection); - } + template + //requires copyable_edge>, VId, EV> && + // copyable_vertex>, VId, VV> + constexpr compressed_graph_base(const ERng& erng, + const VRng& vrng, + EProj eprojection = {}, // eproj(eval) -> {source_id,target_id [,value]} + VProj vprojection = {}, // vproj(vval) -> {target_id [,value]} + const Alloc& alloc = Alloc()) + : row_values_base(alloc), col_values_base(alloc), row_index_(alloc), col_index_(alloc) { + + load(erng, vrng, eprojection, vprojection); + } - /** + /** * @brief Constructor for easy creation of a graph that takes an initializer list * of @c copyable_edge_t -> [source_id, target_id, value]. * * @param ilist Initializer list of @c copyable_edge_t -> [source_id, target_id, value] * @param alloc Allocator to use for internal containers */ - constexpr compressed_graph_base(const initializer_list>& ilist, - const Alloc& alloc = Alloc()) - : row_values_base(alloc), col_values_base(alloc), row_index_(alloc), col_index_(alloc), part_index_(alloc) { - load_edges(ilist, identity()); - } + constexpr compressed_graph_base(const initializer_list>& ilist, const Alloc& alloc = Alloc()) + : row_values_base(alloc), col_values_base(alloc), row_index_(alloc), col_index_(alloc) { + load_edges(ilist, identity()); + } - public: // Operations - void reserve_vertices(size_type count) { - row_index_.reserve(count + 1); // +1 for terminating row - row_values_base::reserve(count); - } - void reserve_edges(size_type count) { - col_index_.reserve(count); - static_cast(*this).reserve(count); - } +public: +public: // Operations + void reserve_vertices(size_type count) { + row_index_.reserve(count + 1); // +1 for terminating row + row_values_base::reserve(count); + } + void reserve_edges(size_type count) { + col_index_.reserve(count); + static_cast(*this).reserve(count); + } - void resize_vertices(size_type count) { - row_index_.resize(count + 1); // +1 for terminating row - row_values_resize(count); - } - void resize_edges(size_type count) { - col_index_.reserve(count); - static_cast(*this).reserve(count); - } + void resize_vertices(size_type count) { + row_index_.resize(count + 1); // +1 for terminating row + row_values_resize(count); + } + void resize_edges(size_type count) { + col_index_.reserve(count); + static_cast(*this).reserve(count); + } - /** + /** * @brief Load vertex values, callable either before or after @c load_edges(erng,eproj). * * If @c load_edges(vrng,vproj) has been called before this, the @c row_values_ vector will be @@ -561,14 +518,13 @@ namespace std::graph::container { * preserved in the internal vector. * @param vprojection Projection function for @c vrng values */ - template - //requires copyable_vertex>, VId, VV> - constexpr void load_vertices(const VRng& vrng, VProj vprojection, size_type vertex_count = 0) { - row_values_base::load_row_values(vrng, vprojection, max(vertex_count, ranges::size(vrng))); - set_default_partition(); - } + template + //requires views::copyable_vertex>, VId, VV> + constexpr void load_vertices(const VRng& vrng, VProj vprojection, size_type vertex_count = 0) { + row_values_base::load_row_values(vrng, vprojection, max(vertex_count, ranges::size(vrng))); + } - /** + /** * Load vertex values, callable either before or after @c load_edges(erng,eproj). * * If @c load_edges(vrng,vproj) has been called before this, the @c row_values_ vector will be @@ -581,14 +537,13 @@ namespace std::graph::container { * @param vrng Range of values to load for vertices. The order of the values is preserved in the internal vector. * @param vprojection Projection function for @c vrng values */ - template - //requires copyable_vertex>, VId, VV> - constexpr void load_vertices(VRng& vrng, VProj vprojection = {}, size_type vertex_count = 0) { - row_values_base::load_row_values(vrng, vprojection, max(vertex_count, ranges::size(vrng))); - set_default_partition(); - } + template + //requires views::copyable_vertex>, VId, VV> + constexpr void load_vertices(VRng& vrng, VProj vprojection = {}, size_type vertex_count = 0) { + row_values_base::load_row_values(vrng, vprojection, max(vertex_count, ranges::size(vrng))); + } - /** + /** * @brief Load the edges for the graph, callable either before or after @c load_vertices(erng,eproj). * * @c erng must be ordered by source_id (copyable_edge_t) and is enforced by assertion. target_id @@ -622,116 +577,111 @@ namespace std::graph::container { * @param erng Input range for edges * @param eprojection Edge projection function that returns a @ copyable_edge_t for an element in @c erng */ - template - //requires copyable_edge>, VId, EV> - constexpr void - load_edges(ERng&& erng, EProj eprojection = {}, size_type vertex_count = 0, size_type edge_count = 0) { - // should only be loading into an empty graph - assert(row_index_.empty() && col_index_.empty() && static_cast(*this).empty()); - - // Nothing to do? - if (ranges::begin(erng) == ranges::end(erng)) { - return; - } - - // We can get the last vertex id from the list because erng is required to be ordered by - // the source id. It's possible a target_id could have a larger id also, which is taken - // care of at the end of this function. - vertex_count = std::max(vertex_count, - static_cast(last_erng_id(erng, eprojection) + 1)); // +1 for zero-based index - reserve_vertices(vertex_count); - - // Eval number of input rows and reserve space for the edges, if possible - if constexpr (ranges::sized_range) - edge_count = max(edge_count, ranges::size(erng)); - reserve_edges(edge_count); - - // Add edges - vertex_id_type last_uid = 0, max_vid = 0; - for (auto&& edge_data : erng) { - auto&& edge = eprojection(edge_data); // compressed_graph requires EV!=void - assert(edge.source_id >= last_uid); // ordered by uid? (requirement) - row_index_.resize(static_cast(edge.source_id) + 1, - vertex_type{static_cast(static_cast(*this).size())}); - col_index_.push_back(edge_type{edge.target_id}); - if (!is_void_v) - static_cast(*this).emplace_back(std::move(edge.value)); - last_uid = edge.source_id; - max_vid = max(max_vid, edge.target_id); - } - - // uid and vid may refer to rows that exceed the value evaluated for vertex_count (if any) - vertex_count = max(vertex_count, max(row_index_.size(), static_cast(max_vid + 1))); - - // add any rows that haven't been added yet, and (+1) terminating row - row_index_.resize(vertex_count + 1, + template + //requires views::copyable_edge>, VId, EV> + constexpr void load_edges(ERng&& erng, EProj eprojection = {}, size_type vertex_count = 0, size_type edge_count = 0) { + // should only be loading into an empty graph + assert(row_index_.empty() && col_index_.empty() && static_cast(*this).empty()); + + // Nothing to do? + if (ranges::begin(erng) == ranges::end(erng)) { + return; + } + + // We can get the last vertex id from the list because erng is required to be ordered by + // the source id. It's possible a target_id could have a larger id also, which is taken + // care of at the end of this function. + vertex_count = std::max(vertex_count, + static_cast(last_erng_id(erng, eprojection) + 1)); // +1 for zero-based index + reserve_vertices(vertex_count); + + // Eval number of input rows and reserve space for the edges, if possible + if constexpr (ranges::sized_range) + edge_count = max(edge_count, ranges::size(erng)); + reserve_edges(edge_count); + + // Add edges + vertex_id_type last_uid = 0, max_vid = 0; + for (auto&& edge_data : erng) { + auto&& edge = eprojection(edge_data); // compressed_graph requires EV!=void + assert(edge.source_id >= last_uid); // ordered by uid? (requirement) + row_index_.resize(static_cast(edge.source_id) + 1, vertex_type{static_cast(static_cast(*this).size())}); - - // If load_vertices(vrng,vproj) has been called but it doesn't have enough values for all - // the vertices then we extend the size to remove possibility of out-of-bounds occuring when - // getting a value for a row. - if (row_values_base::size() > 1 && row_values_base::size() < vertex_count) - row_values_base::resize(vertex_count); - - set_default_partition(); + col_index_.push_back(edge_type{edge.target_id}); + if (!is_void_v) + static_cast(*this).emplace_back(std::move(edge.value)); + last_uid = edge.source_id; + max_vid = max(max_vid, edge.target_id); } - // The only diff with this and ERng&& is v_.push_back vs. v_.emplace_back - template - //requires copyable_edge>, VId, EV> - constexpr void - load_edges(const ERng& erng, EProj eprojection = {}, size_type vertex_count = 0, size_type edge_count = 0) { - // should only be loading into an empty graph - assert(row_index_.empty() && col_index_.empty() && static_cast(*this).empty()); - - // Nothing to do? - if (ranges::begin(erng) == ranges::end(erng)) { - return; - } + // uid and vid may refer to rows that exceed the value evaluated for vertex_count (if any) + vertex_count = max(vertex_count, max(row_index_.size(), static_cast(max_vid + 1))); - // We can get the last vertex id from the list because erng is required to be ordered by - // the source id. It's possible a target_id could have a larger id also, which is taken - // care of at the end of this function. - vertex_count = std::max(vertex_count, - static_cast(last_erng_id(erng, eprojection) + 1)); // +1 for zero-based index - reserve_vertices(vertex_count); - - // Eval number of input rows and reserve space for the edges, if possible - if constexpr (ranges::sized_range) - edge_count = max(edge_count, ranges::size(erng)); - reserve_edges(edge_count); - - // Add edges - vertex_id_type last_uid = 0, max_vid = 0; - for (auto&& edge_data : erng) { - auto&& edge = eprojection(edge_data); // compressed_graph requires EV!=void - assert(edge.source_id >= last_uid); // ordered by uid? (requirement) - row_index_.resize(static_cast(edge.source_id) + 1, - vertex_type{static_cast(static_cast(*this).size())}); - col_index_.push_back(edge_type{edge.target_id}); - if constexpr (!is_void_v) - static_cast(*this).push_back(edge.value); - last_uid = edge.source_id; - max_vid = max(max_vid, edge.target_id); - } + // add any rows that haven't been added yet, and (+1) terminating row + row_index_.resize(vertex_count + 1, + vertex_type{static_cast(static_cast(*this).size())}); - // uid and vid may refer to rows that exceed the value evaluated for vertex_count (if any) - vertex_count = max(vertex_count, max(row_index_.size(), static_cast(max_vid + 1))); + // If load_vertices(vrng,vproj) has been called but it doesn't have enough values for all + // the vertices then we extend the size to remove possibility of out-of-bounds occuring when + // getting a value for a row. + if (row_values_base::size() > 1 && row_values_base::size() < vertex_count) + row_values_base::resize(vertex_count); + } - // add any rows that haven't been added yet, and (+1) terminating row - row_index_.resize(vertex_count + 1, + // The only diff with this and ERng&& is v_.push_back vs. v_.emplace_back + template + //requires views::copyable_edge>, VId, EV> + constexpr void + load_edges(const ERng& erng, EProj eprojection = {}, size_type vertex_count = 0, size_type edge_count = 0) { + // should only be loading into an empty graph + assert(row_index_.empty() && col_index_.empty() && static_cast(*this).empty()); + + // Nothing to do? + if (ranges::begin(erng) == ranges::end(erng)) { + return; + } + + // We can get the last vertex id from the list because erng is required to be ordered by + // the source id. It's possible a target_id could have a larger id also, which is taken + // care of at the end of this function. + vertex_count = std::max(vertex_count, + static_cast(last_erng_id(erng, eprojection) + 1)); // +1 for zero-based index + reserve_vertices(vertex_count); + + // Eval number of input rows and reserve space for the edges, if possible + if constexpr (ranges::sized_range) + edge_count = max(edge_count, ranges::size(erng)); + reserve_edges(edge_count); + + // Add edges + vertex_id_type last_uid = 0, max_vid = 0; + for (auto&& edge_data : erng) { + auto&& edge = eprojection(edge_data); // compressed_graph requires EV!=void + assert(edge.source_id >= last_uid); // ordered by uid? (requirement) + row_index_.resize(static_cast(edge.source_id) + 1, vertex_type{static_cast(static_cast(*this).size())}); + col_index_.push_back(edge_type{edge.target_id}); + if constexpr (!is_void_v) + static_cast(*this).push_back(edge.value); + last_uid = edge.source_id; + max_vid = max(max_vid, edge.target_id); + } - // If load_vertices(vrng,vproj) has been called but it doesn't have enough values for all - // the vertices then we extend the size to remove possibility of out-of-bounds occuring when - // getting a value for a row. - if (row_values_base::size() > 0 && row_values_base::size() < vertex_count) - row_values_base::resize(vertex_count); + // uid and vid may refer to rows that exceed the value evaluated for vertex_count (if any) + vertex_count = max(vertex_count, max(row_index_.size(), static_cast(max_vid + 1))); - set_default_partition(); - } + // add any rows that haven't been added yet, and (+1) terminating row + row_index_.resize(vertex_count + 1, + vertex_type{static_cast(static_cast(*this).size())}); + + // If load_vertices(vrng,vproj) has been called but it doesn't have enough values for all + // the vertices then we extend the size to remove possibility of out-of-bounds occuring when + // getting a value for a row. + if (row_values_base::size() > 0 && row_values_base::size() < vertex_count) + row_values_base::resize(vertex_count); + } - /** + /** * @brief Load edges and then vertices for the graph. * * See @c load_edges() and @c load_vertices() for more information. @@ -744,148 +694,128 @@ namespace std::graph::container { * @param eprojection Edge projection function object * @param vprojection Vertex projection function object */ - template - //requires copyable_edge>, VId, EV> && - // views::copyable_vertex>, VId, VV> - constexpr void load(const ERng& erng, const VRng& vrng, EProj eprojection = {}, VProj vprojection = {}) { - load_edges(erng, eprojection); - load_vertices(vrng, vprojection); // load the values - } - - protected: - template - constexpr vertex_id_type last_erng_id(ERng&& erng, EProj eprojection) const { - vertex_id_type last_id = vertex_id_type(); - if constexpr (ranges::bidirectional_range) { - if (ranges::begin(erng) != ranges::end(erng)) { - auto lastIt = ranges::end(erng); - --lastIt; - auto&& e = eprojection(*lastIt); // copyable_edge - last_id = max(e.source_id, e.target_id); - } - } - return last_id; - } + template + //requires views::copyable_edge>, VId, EV> && + // views::copyable_vertex>, VId, VV> + constexpr void load(const ERng& erng, const VRng& vrng, EProj eprojection = {}, VProj vprojection = {}) { + load_edges(erng, eprojection); + load_vertices(vrng, vprojection); // load the values + } - void set_default_partition() { - if (size(part_index_) == 0) { - part_index_.push_back(0); - part_index_.push_back(static_cast(size(row_index_))); - } else if (size(part_index_) == 2) { - part_index_.back() = static_cast(size(row_index_)); - } else { - assert(false); // Multiple partitions need different logic +protected: + template + constexpr vertex_id_type last_erng_id(ERng&& erng, EProj eprojection) const { + vertex_id_type last_id = vertex_id_type(); + if constexpr (ranges::bidirectional_range) { + if (ranges::begin(erng) != ranges::end(erng)) { + auto lastIt = ranges::end(erng); + --lastIt; + auto&& e = eprojection(*lastIt); // copyable_edge + last_id = max(e.source_id, e.target_id); } } + return last_id; + } - public: // Operations - constexpr ranges::iterator_t find_vertex(vertex_id_type id) noexcept { - return row_index_.begin() + id; - } - constexpr ranges::iterator_t find_vertex(vertex_id_type id) const noexcept { - return row_index_.begin() + id; - } - - constexpr edge_index_type index_of(const row_type& u) const noexcept { - return static_cast(&u - row_index_.data()); - } - constexpr vertex_id_type index_of(const col_type& v) const noexcept { - return static_cast(&v - col_index_.data()); - } - - public: // Operators - constexpr vertex_type& operator[](vertex_id_type id) noexcept { return row_index_[id]; } - constexpr const vertex_type& operator[](vertex_id_type id) const noexcept { return row_index_[id]; } - - private: // Member variables - row_index_vector row_index_; // starting index into col_index_ and v_; holds +1 extra terminating row - col_index_vector col_index_; // col_index_[n] holds the column index (aka target) - partition_index_vector - part_index_; // row_index_[part_index_[p]] is the first row of partition p; holds +1 extra for terminating row (size(row_index_)) - - private: // tag_invoke properties - friend constexpr vertices_type tag_invoke(::std::graph::tag_invoke::vertices_fn_t, compressed_graph_base& g) { - if (g.row_index_.empty()) - return vertices_type(g.row_index_); // really empty - else - return vertices_type(g.row_index_.begin(), g.row_index_.end() - 1); // don't include terminating row - } - friend constexpr const_vertices_type tag_invoke(::std::graph::tag_invoke::vertices_fn_t, - const compressed_graph_base& g) { - if (g.row_index_.empty()) - return const_vertices_type(g.row_index_); // really empty - else - return const_vertices_type(g.row_index_.begin(), g.row_index_.end() - 1); // don't include terminating row - } +public: // Operations + constexpr ranges::iterator_t find_vertex(vertex_id_type id) noexcept { + return row_index_.begin() + id; + } + constexpr ranges::iterator_t find_vertex(vertex_id_type id) const noexcept { + return row_index_.begin() + id; + } - friend vertex_id_type - tag_invoke(::std::graph::tag_invoke::vertex_id_fn_t, const compressed_graph_base& g, const_iterator ui) { - return static_cast(ui - g.row_index_.begin()); - } + constexpr edge_index_type index_of(const row_type& u) const noexcept { + return static_cast(&u - row_index_.data()); + } + constexpr vertex_id_type index_of(const col_type& v) const noexcept { + return static_cast(&v - col_index_.data()); + } - friend constexpr edges_type tag_invoke(::std::graph::tag_invoke::edges_fn_t, graph_type& g, vertex_type& u) { - static_assert(ranges::contiguous_range, - "row_index_ must be a contiguous range to get next row"); - vertex_type* u2 = &u + 1; - assert(static_cast(u2 - &u) < g.row_index_.size()); // in row_index_ bounds? - assert(static_cast(u.index) <= g.col_index_.size() && - static_cast(u2->index) <= g.col_index_.size()); // in col_index_ bounds? - return edges_type(g.col_index_.begin() + u.index, g.col_index_.begin() + u2->index); - } - friend constexpr const_edges_type - tag_invoke(::std::graph::tag_invoke::edges_fn_t, const graph_type& g, const vertex_type& u) { - static_assert(ranges::contiguous_range, - "row_index_ must be a contiguous range to get next row"); - const vertex_type* u2 = &u + 1; - assert(static_cast(u2 - &u) < g.row_index_.size()); // in row_index_ bounds? - assert(static_cast(u.index) <= g.col_index_.size() && - static_cast(u2->index) <= g.col_index_.size()); // in col_index_ bounds? - return const_edges_type(g.col_index_.begin() + u.index, g.col_index_.begin() + u2->index); - } +public: // Operators + constexpr vertex_type& operator[](vertex_id_type id) noexcept { return row_index_[id]; } + constexpr const vertex_type& operator[](vertex_id_type id) const noexcept { return row_index_[id]; } + +private: // Member variables + row_index_vector row_index_; // starting index into col_index_ and v_; holds +1 extra terminating row + col_index_vector col_index_; // col_index_[n] holds the column index (aka target) + //v_vector_type v_; // v_[n] holds the edge value for col_index_[n] + //row_values_type row_value_; // row_value_[r] holds the value for row_index_[r], for VV!=void + +private: // tag_invoke properties + friend constexpr vertices_type tag_invoke(::std::graph::tag_invoke::vertices_fn_t, compressed_graph_base& g) { + if (g.row_index_.empty()) + return vertices_type(g.row_index_); // really empty + else + return vertices_type(g.row_index_.begin(), g.row_index_.end() - 1); // don't include terminating row + } + friend constexpr const_vertices_type tag_invoke(::std::graph::tag_invoke::vertices_fn_t, + const compressed_graph_base& g) { + if (g.row_index_.empty()) + return const_vertices_type(g.row_index_); // really empty + else + return const_vertices_type(g.row_index_.begin(), g.row_index_.end() - 1); // don't include terminating row + } - friend constexpr edges_type - tag_invoke(::std::graph::tag_invoke::edges_fn_t, graph_type& g, const vertex_id_type uid) { - assert(static_cast(uid + 1) < g.row_index_.size()); // in row_index_ bounds? - assert(static_cast(g.row_index_[uid + 1].index) <= g.col_index_.size()); // in col_index_ bounds? - return edges_type(g.col_index_.begin() + g.row_index_[uid].index, - g.col_index_.begin() + g.row_index_[uid + 1].index); - } - friend constexpr const_edges_type - tag_invoke(::std::graph::tag_invoke::edges_fn_t, const graph_type& g, const vertex_id_type uid) { - assert(static_cast(uid + 1) < g.row_index_.size()); // in row_index_ bounds? - assert(static_cast(g.row_index_[uid + 1].index) <= g.col_index_.size()); // in col_index_ bounds? - return const_edges_type(g.col_index_.begin() + g.row_index_[uid].index, - g.col_index_.begin() + g.row_index_[uid + 1].index); - } + friend vertex_id_type + tag_invoke(::std::graph::tag_invoke::vertex_id_fn_t, const compressed_graph_base& g, const_iterator ui) { + return static_cast(ui - g.row_index_.begin()); + } + friend constexpr edges_type tag_invoke(::std::graph::tag_invoke::edges_fn_t, graph_type& g, vertex_type& u) { + static_assert(ranges::contiguous_range, "row_index_ must be a contiguous range to get next row"); + vertex_type* u2 = &u + 1; + assert(static_cast(u2 - &u) < g.row_index_.size()); // in row_index_ bounds? + assert(static_cast(u.index) <= g.col_index_.size() && + static_cast(u2->index) <= g.col_index_.size()); // in col_index_ bounds? + return edges_type(g.col_index_.begin() + u.index, g.col_index_.begin() + u2->index); + } + friend constexpr const_edges_type + tag_invoke(::std::graph::tag_invoke::edges_fn_t, const graph_type& g, const vertex_type& u) { + static_assert(ranges::contiguous_range, "row_index_ must be a contiguous range to get next row"); + const vertex_type* u2 = &u + 1; + assert(static_cast(u2 - &u) < g.row_index_.size()); // in row_index_ bounds? + assert(static_cast(u.index) <= g.col_index_.size() && + static_cast(u2->index) <= g.col_index_.size()); // in col_index_ bounds? + return const_edges_type(g.col_index_.begin() + u.index, g.col_index_.begin() + u2->index); + } - // target_id(g,uv), target(g,uv) - friend constexpr vertex_id_type - tag_invoke(::std::graph::tag_invoke::target_id_fn_t, const graph_type& g, const edge_type& uv) noexcept { - return uv.index; - } - friend constexpr vertex_type& - tag_invoke(::std::graph::tag_invoke::target_fn_t, graph_type& g, edge_type& uv) noexcept { - return g.row_index_[uv.index]; - } - friend constexpr const vertex_type& - tag_invoke(::std::graph::tag_invoke::target_fn_t, const graph_type& g, const edge_type& uv) noexcept { - return g.row_index_[uv.index]; - } + friend constexpr edges_type + tag_invoke(::std::graph::tag_invoke::edges_fn_t, graph_type& g, const vertex_id_type uid) { + assert(static_cast(uid + 1) < g.row_index_.size()); // in row_index_ bounds? + assert(static_cast(g.row_index_[uid + 1].index) <= g.col_index_.size()); // in col_index_ bounds? + return edges_type(g.col_index_.begin() + g.row_index_[uid].index, + g.col_index_.begin() + g.row_index_[uid + 1].index); + } + friend constexpr const_edges_type + tag_invoke(::std::graph::tag_invoke::edges_fn_t, const graph_type& g, const vertex_id_type uid) { + assert(static_cast(uid + 1) < g.row_index_.size()); // in row_index_ bounds? + assert(static_cast(g.row_index_[uid + 1].index) <= g.col_index_.size()); // in col_index_ bounds? + return const_edges_type(g.col_index_.begin() + g.row_index_[uid].index, + g.col_index_.begin() + g.row_index_[uid + 1].index); + } - // partitions - friend constexpr partition_id_type tag_invoke(::std::graph::tag_invoke::partition_count_fn_t, - const graph_type& g) noexcept { - return static_cast(size(g.part_index_) - 1); - } + // target_id(g,uv), target(g,uv) + friend constexpr vertex_id_type + tag_invoke(::std::graph::tag_invoke::target_id_fn_t, const graph_type& g, const edge_type& uv) noexcept { + return uv.index; + } + friend constexpr vertex_type& + tag_invoke(::std::graph::tag_invoke::target_fn_t, graph_type& g, edge_type& uv) noexcept { + return g.row_index_[uv.index]; + } + friend constexpr const vertex_type& + tag_invoke(::std::graph::tag_invoke::target_fn_t, const graph_type& g, const edge_type& uv) noexcept { + return g.row_index_[uv.index]; + } - friend row_values_base; - friend col_values_base; - }; + friend row_values_base; + friend col_values_base; +}; - /** +/** * @ingroup graph_containers * @brief Compressed Sparse Row adjacency graph container. * @@ -896,116 +826,116 @@ namespace std::graph::container { * @tparam EIndex Edge Index type. This must be large enough for the count of edges. * @tparam Alloc Allocator type */ - template - class compressed_graph : public compressed_graph_base { - public: // Types - using graph_type = compressed_graph; - using base_type = compressed_graph_base; - - using edge_value_type = EV; - using vertex_value_type = VV; - using graph_value_type = GV; - using value_type = GV; - - using vertex_id_type = VId; - - public: // Construction/Destruction - constexpr compressed_graph() = default; - constexpr compressed_graph(const compressed_graph&) = default; - constexpr compressed_graph(compressed_graph&&) = default; - constexpr ~compressed_graph() = default; - - constexpr compressed_graph& operator=(const compressed_graph&) = default; - constexpr compressed_graph& operator=(compressed_graph&&) = default; - - // compressed_graph( alloc) - // compressed_graph(gv&, alloc) - // compressed_graph(gv&&, alloc) - - constexpr compressed_graph(const Alloc& alloc) : base_type(alloc) {} - constexpr compressed_graph(const graph_value_type& value, const Alloc& alloc = Alloc()) - : base_type(alloc), value_(value) {} - constexpr compressed_graph(graph_value_type&& value, const Alloc& alloc = Alloc()) - : base_type(alloc), value_(move(value)) {} - - // compressed_graph( erng, eprojection, alloc) - // compressed_graph(gv&, erng, eprojection, alloc) - // compressed_graph(gv&&, erng, eprojection, alloc) - - template - requires copyable_edge>, VId, EV> - constexpr compressed_graph(const ERng& erng, EProj eprojection, const Alloc& alloc = Alloc()) - : base_type(erng, eprojection, alloc) {} - - template - requires copyable_edge>, VId, EV> - constexpr compressed_graph(const graph_value_type& value, - const ERng& erng, - EProj eprojection, - const Alloc& alloc = Alloc()) - : base_type(erng, eprojection, alloc), value_(value) {} - - template - requires copyable_edge>, VId, EV> - constexpr compressed_graph(graph_value_type&& value, - const ERng& erng, - EProj eprojection, - const Alloc& alloc = Alloc()) - : base_type(erng, eprojection, alloc), value_(move(value)) {} - - // compressed_graph( erng, vrng, eprojection, vprojection, alloc) - // compressed_graph(gv&, erng, vrng, eprojection, vprojection, alloc) - // compressed_graph(gv&&, erng, vrng, eprojection, vprojection, alloc) - - template - requires copyable_edge>, VId, EV> && - copyable_vertex>, VId, VV> - constexpr compressed_graph(const ERng& erng, - const VRng& vrng, - EProj eprojection = {}, - VProj vprojection = {}, - const Alloc& alloc = Alloc()) - : base_type(erng, vrng, eprojection, vprojection, alloc) {} - - template - requires copyable_edge>, VId, EV> && - copyable_vertex>, VId, VV> - constexpr compressed_graph(const graph_value_type& value, - const ERng& erng, - const VRng& vrng, - EProj eprojection = {}, - VProj vprojection = {}, - const Alloc& alloc = Alloc()) - : base_type(erng, vrng, eprojection, vprojection, alloc), value_(value) {} - - template - requires copyable_edge>, VId, EV> && - copyable_vertex>, VId, VV> - constexpr compressed_graph(graph_value_type&& value, - const ERng& erng, - const VRng& vrng, - EProj eprojection = {}, - VProj vprojection = {}, - const Alloc& alloc = Alloc()) - : base_type(erng, vrng, eprojection, vprojection, alloc), value_(move(value)) {} - - - constexpr compressed_graph(const initializer_list>& ilist, const Alloc& alloc = Alloc()) - : base_type(ilist, alloc) {} - - private: // tag_invoke properties - friend constexpr value_type& tag_invoke(::std::graph::tag_invoke::graph_value_fn_t, graph_type& g) { - return g.value_; - } - friend constexpr const value_type& tag_invoke(::std::graph::tag_invoke::graph_value_fn_t, const graph_type& g) { - return g.value_; - } +template +class compressed_graph : public compressed_graph_base { +public: // Types + using graph_type = compressed_graph; + using base_type = compressed_graph_base; + + using edge_value_type = EV; + using vertex_value_type = VV; + using graph_value_type = GV; + using value_type = GV; + + using vertex_id_type = VId; + +public: // Construction/Destruction + constexpr compressed_graph() = default; + constexpr compressed_graph(const compressed_graph&) = default; + constexpr compressed_graph(compressed_graph&&) = default; + constexpr ~compressed_graph() = default; + + constexpr compressed_graph& operator=(const compressed_graph&) = default; + constexpr compressed_graph& operator=(compressed_graph&&) = default; + + // compressed_graph( alloc) + // compressed_graph(gv&, alloc) + // compressed_graph(gv&&, alloc) + + constexpr compressed_graph(const Alloc& alloc) : base_type(alloc) {} + constexpr compressed_graph(const graph_value_type& value, const Alloc& alloc = Alloc()) + : base_type(alloc), value_(value) {} + constexpr compressed_graph(graph_value_type&& value, const Alloc& alloc = Alloc()) + : base_type(alloc), value_(move(value)) {} + + // compressed_graph( erng, eprojection, alloc) + // compressed_graph(gv&, erng, eprojection, alloc) + // compressed_graph(gv&&, erng, eprojection, alloc) + + template + requires copyable_edge>, VId, EV> + constexpr compressed_graph(const ERng& erng, EProj eprojection, const Alloc& alloc = Alloc()) + : base_type(erng, eprojection, alloc) {} - private: // Member variables - graph_value_type value_ = graph_value_type(); - }; + template + requires copyable_edge>, VId, EV> + constexpr compressed_graph(const graph_value_type& value, + const ERng& erng, + EProj eprojection, + const Alloc& alloc = Alloc()) + : base_type(erng, eprojection, alloc), value_(value) {} - /** + template + requires copyable_edge>, VId, EV> + constexpr compressed_graph(graph_value_type&& value, + const ERng& erng, + EProj eprojection, + const Alloc& alloc = Alloc()) + : base_type(erng, eprojection, alloc), value_(move(value)) {} + + // compressed_graph( erng, vrng, eprojection, vprojection, alloc) + // compressed_graph(gv&, erng, vrng, eprojection, vprojection, alloc) + // compressed_graph(gv&&, erng, vrng, eprojection, vprojection, alloc) + + template + requires copyable_edge>, VId, EV> && + copyable_vertex>, VId, VV> + constexpr compressed_graph(const ERng& erng, + const VRng& vrng, + EProj eprojection = {}, + VProj vprojection = {}, + const Alloc& alloc = Alloc()) + : base_type(erng, vrng, eprojection, vprojection, alloc) {} + + template + requires copyable_edge>, VId, EV> && + copyable_vertex>, VId, VV> + constexpr compressed_graph(const graph_value_type& value, + const ERng& erng, + const VRng& vrng, + EProj eprojection = {}, + VProj vprojection = {}, + const Alloc& alloc = Alloc()) + : base_type(erng, vrng, eprojection, vprojection, alloc), value_(value) {} + + template + requires copyable_edge>, VId, EV> && + copyable_vertex>, VId, VV> + constexpr compressed_graph(graph_value_type&& value, + const ERng& erng, + const VRng& vrng, + EProj eprojection = {}, + VProj vprojection = {}, + const Alloc& alloc = Alloc()) + : base_type(erng, vrng, eprojection, vprojection, alloc), value_(move(value)) {} + + + constexpr compressed_graph(const initializer_list>& ilist, const Alloc& alloc = Alloc()) + : base_type(ilist, alloc) {} + +private: // tag_invoke properties + friend constexpr value_type& tag_invoke(::std::graph::tag_invoke::graph_value_fn_t, graph_type& g) { + return g.value_; + } + friend constexpr const value_type& tag_invoke(::std::graph::tag_invoke::graph_value_fn_t, const graph_type& g) { + return g.value_; + } + +private: // Member variables + graph_value_type value_ = graph_value_type(); +}; + +/** * @ingroup graph_containers * @brief Compressed Sparse Row adjacency graph container. * @@ -1015,52 +945,52 @@ namespace std::graph::container { * @tparam EIndex Edge Index type. This must be large enough for the count of edges. * @tparam Alloc Allocator type */ - template - class compressed_graph - : public compressed_graph_base { - public: // Types - using graph_type = compressed_graph; - using base_type = compressed_graph_base; - - using vertex_id_type = VId; - using vertex_value_type = VV; - - using graph_value_type = void; - using value_type = void; - - public: // Construction/Destruction - constexpr compressed_graph() = default; - constexpr compressed_graph(const compressed_graph&) = default; - constexpr compressed_graph(compressed_graph&&) = default; - constexpr ~compressed_graph() = default; - - constexpr compressed_graph& operator=(const compressed_graph&) = default; - constexpr compressed_graph& operator=(compressed_graph&&) = default; - - // edge-only construction - template - requires copyable_edge>, VId, EV> - constexpr compressed_graph(const ERng& erng, EProj eprojection, const Alloc& alloc = Alloc()) - : base_type(erng, eprojection, alloc) {} - - // edge and vertex value construction - template - requires copyable_edge>, VId, EV> && - copyable_vertex>, VId, VV> - constexpr compressed_graph(const ERng& erng, - const VRng& vrng, - EProj eprojection = {}, - VProj vprojection = {}, - const Alloc& alloc = Alloc()) - : base_type(erng, vrng, eprojection, vprojection, alloc) {} - - // initializer list using edge_descriptor - constexpr compressed_graph(const initializer_list>& ilist, const Alloc& alloc = Alloc()) - : base_type(ilist, alloc) {} - - - public: // Operations - private: // tag_invoke properties - }; +template +class compressed_graph + : public compressed_graph_base { +public: // Types + using graph_type = compressed_graph; + using base_type = compressed_graph_base; + + using vertex_id_type = VId; + using vertex_value_type = VV; + + using graph_value_type = void; + using value_type = void; + +public: // Construction/Destruction + constexpr compressed_graph() = default; + constexpr compressed_graph(const compressed_graph&) = default; + constexpr compressed_graph(compressed_graph&&) = default; + constexpr ~compressed_graph() = default; + + constexpr compressed_graph& operator=(const compressed_graph&) = default; + constexpr compressed_graph& operator=(compressed_graph&&) = default; + + // edge-only construction + template + requires copyable_edge>, VId, EV> + constexpr compressed_graph(const ERng& erng, EProj eprojection, const Alloc& alloc = Alloc()) + : base_type(erng, eprojection, alloc) {} + + // edge and vertex value construction + template + requires copyable_edge>, VId, EV> && + copyable_vertex>, VId, VV> + constexpr compressed_graph(const ERng& erng, + const VRng& vrng, + EProj eprojection = {}, + VProj vprojection = {}, + const Alloc& alloc = Alloc()) + : base_type(erng, vrng, eprojection, vprojection, alloc) {} + + // initializer list using edge_descriptor + constexpr compressed_graph(const initializer_list>& ilist, const Alloc& alloc = Alloc()) + : base_type(ilist, alloc) {} + + +public: // Operations +private: // tag_invoke properties +}; } // namespace std::graph::container