diff --git a/kagen/edge_range.cpp b/kagen/edge_range.cpp new file mode 100644 index 0000000..66a4f42 --- /dev/null +++ b/kagen/edge_range.cpp @@ -0,0 +1,201 @@ +#include "kagen/edge_range.h" + +#include +#include + +namespace kagen { + +EdgeRange::EdgeRange(const Edgelist& edgelist) noexcept + : representation_(GraphRepresentation::EDGE_LIST), + edgelist_ptr_(std::addressof(edgelist)), + xadj_ptr_(nullptr), + adjncy_ptr_(nullptr), + vertex_base_(Vertex(0)) {} + +EdgeRange::EdgeRange(const XadjArray& xadj, const AdjncyArray& adjncy, VertexRange vertex_range) noexcept + : representation_(GraphRepresentation::CSR), + edgelist_ptr_(nullptr), + xadj_ptr_(std::addressof(xadj)), + adjncy_ptr_(std::addressof(adjncy)), + vertex_base_(vertex_range.first) { + assert(xadj_ptr_->size() >= 1); +} + +EdgeRange::EdgeRange(const Graph& graph) noexcept : EdgeRange(FromGraph(graph)) {} + +EdgeRange EdgeRange::FromGraph(const Graph& g) noexcept { + if (g.representation == GraphRepresentation::EDGE_LIST) { + return EdgeRange(g.edges); + } else { + return EdgeRange(g.xadj, g.adjncy, g.vertex_range); + } +} + +EdgeRange::iterator EdgeRange::iterator::edgelist_begin(const EdgeRange* parent) noexcept { + iterator it; + it.parent_ = parent; + it.idx_ = 0; + it.load_current(); + return it; +} + +EdgeRange::iterator EdgeRange::iterator::edgelist_end(const EdgeRange* parent) noexcept { + iterator it; + it.parent_ = parent; + it.idx_ = parent->edgelist_ptr_->size(); + return it; +} + +EdgeRange::iterator EdgeRange::iterator::csr_begin(const EdgeRange* parent) noexcept { + iterator it; + it.parent_ = parent; + it.u_ = 0; + it.off_ = 0; + it.init_csr_begin(); // finds first valid (u,off) or sets to end + it.load_current(); + return it; +} + +EdgeRange::iterator EdgeRange::iterator::csr_end(const EdgeRange* parent) noexcept { + iterator it; + it.parent_ = parent; + it.u_ = parent->xadj_ptr_->size() == 0 ? 0u : parent->xadj_ptr_->size() - 1; + it.off_ = parent->adjncy_ptr_->size(); + return it; +} + +std::size_t EdgeRange::iterator::edge_index() const noexcept { + if (parent_->representation_ == GraphRepresentation::EDGE_LIST) { + return idx_; + } else { + return off_; + } +} + +EdgeRange::iterator::value_type EdgeRange::iterator::operator*() const noexcept { + return cur_; +} + +EdgeRange::iterator& EdgeRange::iterator::operator++() { + assert(parent_ != nullptr); + + if (parent_->representation_ == GraphRepresentation::EDGE_LIST) { + ++idx_; + load_current(); + return *this; + } + + advance_to_next_valid_csr(); + load_current(); + return *this; +} + +EdgeRange::iterator EdgeRange::iterator::operator++(int) { + iterator tmp = *this; + ++(*this); + return tmp; +} + +bool EdgeRange::iterator::operator==(const iterator& other) const noexcept { + // must belong to same parent to compare reliably + if (parent_ != other.parent_) + return false; + if (parent_ == nullptr) + return true; // both default constructed? + if (parent_->representation_ != other.parent_->representation_) + return false; + if (parent_->representation_ == GraphRepresentation::EDGE_LIST) { + return idx_ == other.idx_; + } else { + return (u_ == other.u_) && (off_ == other.off_); + } +} + +bool EdgeRange::iterator::operator!=(const iterator& other) const noexcept { + return !(*this == other); +} + +void EdgeRange::iterator::load_current() noexcept { + if (parent_->representation_ == GraphRepresentation::EDGE_LIST) { + const auto& elist = *parent_->edgelist_ptr_; + if (idx_ < elist.size()) { + cur_ = elist[idx_]; + } + // else leave cur_ unspecified for end() + } else { + const auto& xadj = *parent_->xadj_ptr_; + const auto& adjncy = *parent_->adjncy_ptr_; + const std::size_t n_local = xadj.empty() ? 0 : (xadj.size() - 1); + if (u_ >= n_local || off_ >= adjncy.size()) { + // end iterator; cur_ remains unspecified + return; + } + Vertex u_global = static_cast(u_) + parent_->vertex_base_; + Vertex v_global = adjncy[off_]; + cur_.first = u_global; + cur_.second = v_global; + } +} + +void EdgeRange::iterator::init_csr_begin() noexcept { + const auto& xadj = *parent_->xadj_ptr_; + const auto& adjncy = *parent_->adjncy_ptr_; + const std::size_t n_local = xadj.empty() ? 0 : (xadj.size() - 1); + if (n_local == 0) { + // empty CSR: set end + u_ = 0; + off_ = 0; + return; + } + off_ = xadj[0]; + u_ = 0; + // skip vertices with empty adjacency ranges + while (u_ < n_local && off_ >= xadj[u_ + 1]) { + ++u_; + if (u_ < n_local) + off_ = xadj[u_]; + } + if (u_ >= n_local) { + // set to end state + u_ = n_local; + off_ = adjncy.size(); + } +} + +void EdgeRange::iterator::advance_to_next_valid_csr() noexcept { + // CSR: advance off_, move to next vertex if necessary + ++off_; + const auto& xadj = *parent_->xadj_ptr_; + const std::size_t n_local = xadj.empty() ? 0 : (xadj.size() - 1); + while (u_ < n_local && off_ >= xadj[u_ + 1]) { + ++u_; + if (u_ < n_local) + off_ = xadj[u_]; + } + if (u_ >= n_local) { + // set canonical end state + off_ = parent_->adjncy_ptr_->size(); + } +} + +EdgeRange::iterator EdgeRange::begin() const noexcept { + if (representation_ == GraphRepresentation::EDGE_LIST) + return iterator::edgelist_begin(this); + return iterator::csr_begin(this); +} + +EdgeRange::iterator EdgeRange::end() const noexcept { + if (representation_ == GraphRepresentation::EDGE_LIST) + return iterator::edgelist_end(this); + return iterator::csr_end(this); +} + +std::size_t EdgeRange::size() const noexcept { + if (representation_ == GraphRepresentation::EDGE_LIST) { + return edgelist_ptr_->size(); + } else { + return adjncy_ptr_->size(); + } +} + +} // namespace kagen diff --git a/kagen/edge_range.h b/kagen/edge_range.h new file mode 100644 index 0000000..1b92507 --- /dev/null +++ b/kagen/edge_range.h @@ -0,0 +1,81 @@ +#pragma once + +#include "kagen/kagen.h" + +#include +#include +#include + +namespace kagen { + +class EdgeRange { +public: + using Vertex = SInt; + using Edge = std::pair; + + explicit EdgeRange(const Edgelist& edgelist) noexcept; + EdgeRange(const XadjArray& xadj, const AdjncyArray& adjncy, VertexRange vertex_range) noexcept; + EdgeRange(const Graph& graph)noexcept; + + static EdgeRange FromGraph(const Graph& graph) noexcept; + + class iterator { + public: + using iterator_category = std::forward_iterator_tag; + using value_type = Edge; + using difference_type = std::ptrdiff_t; + + iterator() noexcept = default; + + static iterator edgelist_begin(const EdgeRange* parent) noexcept; + static iterator edgelist_end(const EdgeRange* parent) noexcept; + + // begin / end for CSR + static iterator csr_begin(const EdgeRange* parent) noexcept; + static iterator csr_end(const EdgeRange* parent) noexcept; + + std::size_t edge_index() const noexcept; + + value_type operator*() const noexcept; + + iterator& operator++(); + + iterator operator++(int); + + bool operator==(const iterator& other) const noexcept; + bool operator!=(const iterator& other) const noexcept; + + private: + const EdgeRange* parent_{nullptr}; + + // EDGELIST state + std::size_t idx_{0}; + + // CSR state + std::size_t u_{0}; // current vertex index (0..n_local-1) + std::size_t off_{0}; // current offset into adjncy + + Edge cur_{}; + + void load_current() noexcept; + void init_csr_begin() noexcept; + void advance_to_next_valid_csr() noexcept; + }; + + iterator begin() const noexcept; + iterator end() const noexcept; + + std::size_t size() const noexcept; + +private: + GraphRepresentation representation_; + + const Edgelist* edgelist_ptr_; + const XadjArray* xadj_ptr_; + const AdjncyArray* adjncy_ptr_; + Vertex vertex_base_; + + friend class iterator; +}; + +} // namespace kagen diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4dca900..baa40c4 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -74,3 +74,6 @@ kagen_add_test(test_permutation FILES permutation/permutation_test.cpp CORES 1 2 4) +kagen_add_test(test_edge_range + FILES edge_range.cpp + CORES 1) diff --git a/tests/edge_range.cpp b/tests/edge_range.cpp new file mode 100644 index 0000000..4256f8e --- /dev/null +++ b/tests/edge_range.cpp @@ -0,0 +1,148 @@ +#include "kagen/edge_range.h" + +#include "kagen/kagen.h" + +#include +#include + +#include +#include +#include + +#include "tests/gather.h" +#include "tests/utils.h" +#include "tools/converter.h" + +using namespace kagen; + +using GeneratorFunc = std::function; + +MATCHER(EdgeIndexMatches, "") { + auto [iter, expected_idx] = arg; + return iter.edge_index() == expected_idx; +} + +struct EdgeRangeTestFixture : public ::testing::TestWithParam> {}; + + +INSTANTIATE_TEST_SUITE_P( + EdgeRangeTests, EdgeRangeTestFixture, + ::testing::Values( + std::make_tuple("GNM", GeneratorFunc([](KaGen& gen, SInt n, SInt m) { return gen.GenerateUndirectedGNM(n, m); })), + std::make_tuple("RMAT", GeneratorFunc([](KaGen& gen, SInt n, SInt m) { return gen.GenerateRMAT(n, m, 0.56, 0.19, 0.19); })), + std::make_tuple("RGG2D", GeneratorFunc([](KaGen& gen, SInt n, SInt m) { return gen.GenerateRGG2D_NM(n, m); })), + std::make_tuple("RGG3D", GeneratorFunc([](KaGen& gen, SInt n, SInt m) { return gen.GenerateRGG3D_NM(n, m); })), + std::make_tuple("RHG", GeneratorFunc([](KaGen& gen, SInt n, SInt m) { return gen.GenerateRHG_NM(2.6, n, m); })), + std::make_tuple("Grid2D", GeneratorFunc([](KaGen& gen, SInt n, SInt m) { return gen.GenerateGrid2D_NM(n, m); })), + std::make_tuple("Grid3D", GeneratorFunc([](KaGen& gen, SInt n, SInt m) { return gen.GenerateGrid3D_NM(n, m); }))), + [](const ::testing::TestParamInfo& info) { + return std::get<0>(info.param); + }); + +TEST_P(EdgeRangeTestFixture, iterate_edgelist_representation) { + using ::testing::ElementsAreArray; + using ::testing::Pointwise; + + auto [name, generate] = GetParam(); + const SInt n = 1000; + const SInt m = 16 * n; + + kagen::KaGen generator(MPI_COMM_WORLD); + generator.UseEdgeListRepresentation(); + Graph graph = generate(generator, n, m); + + Edgelist expected = graph.edges; + EdgeRange edge_range(graph); + + // Check edges match and indices are consecutive + EXPECT_THAT(std::vector(edge_range.begin(), edge_range.end()), ElementsAreArray(expected)); + + std::vector iterators; + for (auto it = edge_range.begin(); it != edge_range.end(); ++it) { + iterators.push_back(it); + } + std::vector expected_indices(edge_range.size()); + std::iota(expected_indices.begin(), expected_indices.end(), 0); + EXPECT_THAT(iterators, Pointwise(EdgeIndexMatches(), expected_indices)); +} + +TEST_P(EdgeRangeTestFixture, iterate_sparse_edgelist_representation) { + using ::testing::ElementsAreArray; + using ::testing::Pointwise; + + auto [name, generate] = GetParam(); + const SInt n = 1000; + const SInt m = 2 * n; + + kagen::KaGen generator(MPI_COMM_WORLD); + generator.UseEdgeListRepresentation(); + Graph graph = generate(generator, n, m); + + Edgelist expected = graph.edges; + EdgeRange edge_range(graph); + + // Check edges match and indices are consecutive + EXPECT_THAT(std::vector(edge_range.begin(), edge_range.end()), ElementsAreArray(expected)); + + std::vector iterators; + for (auto it = edge_range.begin(); it != edge_range.end(); ++it) { + iterators.push_back(it); + } + std::vector expected_indices(edge_range.size()); + std::iota(expected_indices.begin(), expected_indices.end(), 0); + EXPECT_THAT(iterators, Pointwise(EdgeIndexMatches(), expected_indices)); +} + +TEST_P(EdgeRangeTestFixture, iterate_csr_representation) { + using ::testing::ElementsAreArray; + using ::testing::Pointwise; + + auto [name, generate] = GetParam(); + const SInt n = 1000; + const SInt m = 16 * n; + + kagen::KaGen generator(MPI_COMM_WORLD); + generator.UseCSRRepresentation(); + Graph graph = generate(generator, n, m); + + Edgelist expected = BuildEdgeListFromCSR(graph.vertex_range, graph.xadj, graph.adjncy); + EdgeRange edge_range(graph); + + // Check edges match and indices are consecutive + EXPECT_THAT(std::vector(edge_range.begin(), edge_range.end()), ElementsAreArray(expected)); + + std::vector iterators; + for (auto it = edge_range.begin(); it != edge_range.end(); ++it) { + iterators.push_back(it); + } + std::vector expected_indices(edge_range.size()); + std::iota(expected_indices.begin(), expected_indices.end(), 0); + EXPECT_THAT(iterators, Pointwise(EdgeIndexMatches(), expected_indices)); +} + +TEST_P(EdgeRangeTestFixture, iterate_sparse_csr_representation) { + using ::testing::ElementsAreArray; + using ::testing::Pointwise; + + auto [name, generate] = GetParam(); + const SInt n = 1000; + const SInt m = 2 * n; + + kagen::KaGen generator(MPI_COMM_WORLD); + generator.UseCSRRepresentation(); + Graph graph = generate(generator, n, m); + + Edgelist expected = BuildEdgeListFromCSR(graph.vertex_range, graph.xadj, graph.adjncy); + EdgeRange edge_range(graph); + + // Check edges match and indices are consecutive + EXPECT_THAT(std::vector(edge_range.begin(), edge_range.end()), ElementsAreArray(expected)); + + std::vector iterators; + for (auto it = edge_range.begin(); it != edge_range.end(); ++it) { + iterators.push_back(it); + } + std::vector expected_indices(edge_range.size()); + std::iota(expected_indices.begin(), expected_indices.end(), 0); + EXPECT_THAT(iterators, Pointwise(EdgeIndexMatches(), expected_indices)); +}