Skip to content

Commit

Permalink
Add tests for pagerank and fix issue with dangling nodes
Browse files Browse the repository at this point in the history
  • Loading branch information
Laxman Dhulipala committed Dec 1, 2024
1 parent 96a6e3e commit 93549d2
Show file tree
Hide file tree
Showing 3 changed files with 163 additions and 15 deletions.
14 changes: 14 additions & 0 deletions benchmarks/PageRank/BUILD
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
licenses(["notice"])

load("//internal_tools:build_defs.bzl", "gbbs_cc_test")

package(
default_visibility = ["//visibility:public"],
)
Expand All @@ -19,3 +21,15 @@ cc_binary(
srcs = ["PageRank.cc"],
deps = [":PageRank"],
)

gbbs_cc_test(
name = "PageRank_test",
srcs = ["PageRank_test.cc"],
deps = [
"//benchmarks/PageRank:PageRank",
"//gbbs:graph",
"//gbbs:macros",
"//gbbs/unit_tests:graph_test_utils",
"@googletest//:gtest_main",
],
)
57 changes: 42 additions & 15 deletions benchmarks/PageRank/PageRank.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,19 @@ struct PR_Vertex_F {
double addedConstant;
double* p_curr;
double* p_next;
PR_Vertex_F(double* _p_curr, double* _p_next, double _damping, intE n)
double dangling_sum;
double one_over_n;
PR_Vertex_F(double* _p_curr, double* _p_next, double _damping, intE n,
double _dangling_sum, double _one_over_n)
: damping(_damping),
addedConstant((1 - _damping) * (1 / (double)n)),
p_curr(_p_curr),
p_next(_p_next) {}
p_next(_p_next),
dangling_sum(_dangling_sum),
one_over_n(_one_over_n) {}
inline bool operator()(uintE i) {
p_next[i] = damping * p_next[i] + addedConstant;
p_next[i] += dangling_sum * one_over_n;
p_next[i] = damping * p_next[i] + addedConstant;
return 1;
}
};
Expand All @@ -86,23 +92,30 @@ sequence<double> PageRank_edgeMap(Graph& G, double eps = 0.000001,
double one_over_n = 1 / (double)n;
auto p_curr = sequence<double>(n, one_over_n);
auto p_next = sequence<double>(n, static_cast<double>(0));
auto frontier = sequence<bool>(n, true);

// read from special array of just degrees

auto degrees = sequence<uintE>::from_function(
n, [&](size_t i) { return G.get_vertex(i).out_degree(); });

auto frontier = sequence<bool>(n, true);
vertexSubset Frontier(n, n, std::move(frontier));

// Nodes with zero out-degree.
parlay::sequence<uintE> dangling_nodes = parlay::pack_index<uintE>(
parlay::delayed_seq<bool>(n, [&] (size_t i) {
return G.get_vertex(i).out_degree() == 0; }));

size_t iter = 0;
while (iter++ < max_iters) {
gbbs_debug(timer t; t.start(););

double dangling_sum = parlay::reduce(
parlay::delayed_map(dangling_nodes, [&] (uintE v) {
return p_curr[v];
}));

// SpMV
edgeMap(G, Frontier, PR_F<Graph>(p_curr.begin(), p_next.begin(), G), 0,
no_output);
vertexMap(Frontier,
PR_Vertex_F(p_curr.begin(), p_next.begin(), damping, n));
PR_Vertex_F(p_curr.begin(), p_next.begin(), damping, n,
dangling_sum, one_over_n));

// Check convergence: compute L1-norm between p_curr and p_next
auto differences = parlay::delayed_seq<double>(
Expand Down Expand Up @@ -135,15 +148,23 @@ sequence<double> PageRank(Graph& G, double eps = 0.000001,
auto p_next = sequence<double>(n, static_cast<double>(0));
auto frontier = sequence<bool>(n, true);
auto p_div = sequence<double>::from_function(n, [&](size_t i) -> double {
return one_over_n / static_cast<double>(G.get_vertex(i).out_degree());
return one_over_n / std::max(
double{1},
static_cast<double>(G.get_vertex(i).out_degree()));
});
auto p_div_next = sequence<double>(n);

// read from special array of just degrees

auto degrees = sequence<uintE>::from_function(
n, [&](size_t i) { return G.get_vertex(i).out_degree(); });

parlay::sequence<uintE> dangling_nodes = parlay::pack_index<uintE>(
parlay::delayed_seq<bool>(n, [&] (size_t i) {
return degrees[i] == 0;
}));

double dangling_sum{0};

vertexSubset Frontier(n, n, std::move(frontier));
auto EM = EdgeMap<double, Graph>(
G, std::make_tuple(UINT_E_MAX, static_cast<double>(0)),
Expand All @@ -157,15 +178,21 @@ sequence<double> PageRank(Graph& G, double eps = 0.000001,
auto reduce_f = [&](double l, double r) { return l + r; };
auto apply_f = [&](
std::tuple<uintE, double> k) -> std::optional<std::tuple<uintE, double>> {
const uintE& u = std::get<0>(k);
const double& contribution = std::get<1>(k);
uintE u = std::get<0>(k);
double contribution = std::get<1>(k);
contribution += dangling_sum * one_over_n;
p_next[u] = damping * contribution + addedConstant;
p_div_next[u] = p_next[u] / static_cast<double>(degrees[u]);
p_div_next[u] = (p_next[u] / static_cast<double>(degrees[u]));
return std::nullopt;
};

size_t iter = 0;
while (iter++ < max_iters) {
dangling_sum = parlay::reduce(
parlay::delayed_map(dangling_nodes, [&] (uintE v) {
return p_curr[v];
}));

timer t;
t.start();
// SpMV
Expand Down
107 changes: 107 additions & 0 deletions benchmarks/PageRank/PageRank_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#include "benchmarks/PageRank/PageRank.h"

#include <unordered_set>

#include "gbbs/graph.h"
#include "gbbs/macros.h"
#include "gbbs/unit_tests/graph_test_utils.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"

using ::testing::AnyOf;
using ::testing::ElementsAre;

namespace gbbs {

// TODO: add tests for directed graphs.

struct PageRank_ligra {
template <class Graph>
static sequence<double> compute_pagerank(Graph& G, double eps = 0.000001, size_t max_iters = 100) {
return PageRank_edgeMap(G, eps, max_iters);
}
};

struct PageRank_opt {
template <class Graph>
static sequence<double> compute_pagerank(Graph& G, double eps = 0.000001, size_t max_iters = 100) {
return PageRank(G, eps, max_iters);
}
};

template <typename T>
class PageRankFixture : public testing::Test {
public:
using Impl = T;
};

using Implementations = ::testing::Types<PageRank_ligra, PageRank_opt>;
TYPED_TEST_SUITE(PageRankFixture, Implementations);

TYPED_TEST(PageRankFixture, EdgelessGraph) {
constexpr uintE kNumVertices{3};
const std::unordered_set<UndirectedEdge> kEdges{};
auto graph{graph_test::MakeUnweightedSymmetricGraph(kNumVertices, kEdges)};

using Impl = typename TestFixture::Impl;
const sequence<double> result = Impl::compute_pagerank(graph);
EXPECT_THAT(result, ElementsAre(1.0/3, 1.0/3, 1.0/3));
}

TYPED_TEST(PageRankFixture, Cycle) {
// Graph diagram:
// 0 - 1 - 2 - 3 - 4 (loops back to 0)
//
constexpr uintE kNumVertices{5};
const std::unordered_set<UndirectedEdge> kEdges{
{0, 1}, {1, 2}, {2, 3}, {3, 4}, {4, 0},
};
auto graph{graph_test::MakeUnweightedSymmetricGraph(kNumVertices, kEdges)};
using Impl = typename TestFixture::Impl;

{
const sequence<double> result{Impl::compute_pagerank(graph)};
EXPECT_THAT(result,
ElementsAre(0.2, 0.2, 0.2, 0.2, 0.2));
}
}

TYPED_TEST(PageRankFixture, Path) {
// Graph diagram:
// 0 - 1 - 2
//
constexpr uintE kNumVertices{3};
const std::unordered_set<UndirectedEdge> kEdges{
{0, 1}, {1, 2},
};
auto graph{graph_test::MakeUnweightedSymmetricGraph(kNumVertices, kEdges)};
using Impl = typename TestFixture::Impl;

{
const sequence<double> result{Impl::compute_pagerank(graph)};
const sequence<double> expected{0.2567570878, 0.4864858243, 0.2567570878};
EXPECT_THAT(result, testing::Pointwise(testing::DoubleNear(1e-4), expected));
}
}

TYPED_TEST(PageRankFixture, BasicUndirected) {
// Graph diagram:
// 0 - 1 2 - 3 - 4
// \ |
// 5 -- 6
constexpr uintE kNumVertices{7};
const std::unordered_set<UndirectedEdge> kEdges{
{0, 1}, {2, 3}, {3, 4}, {3, 5}, {4, 5}, {5, 6},
};
auto graph{graph_test::MakeUnweightedSymmetricGraph(kNumVertices, kEdges)};
using Impl = typename TestFixture::Impl;

{
const sequence<double> result{Impl::compute_pagerank(graph)};
const sequence<double> expected{0.1428571429, 0.1428571429, 0.0802049539, 0.2074472351, 0.1389813363, 0.2074472351, 0.0802049539};

EXPECT_THAT(result, testing::Pointwise(testing::DoubleNear(1e-4), expected));
}
}

} // namespace gbbs

0 comments on commit 93549d2

Please sign in to comment.