From a11ba98f2b23fe4ed62af1de981ece67a9ba67a6 Mon Sep 17 00:00:00 2001 From: maniospas Date: Sun, 24 May 2026 05:07:13 +0300 Subject: [PATCH 1/7] first take on personalized page rank --- .../boost/graph/personalized_page_rank.hpp | 300 ++++++++++++++++++ 1 file changed, 300 insertions(+) create mode 100644 include/boost/graph/personalized_page_rank.hpp diff --git a/include/boost/graph/personalized_page_rank.hpp b/include/boost/graph/personalized_page_rank.hpp new file mode 100644 index 000000000..ce04dc240 --- /dev/null +++ b/include/boost/graph/personalized_page_rank.hpp @@ -0,0 +1,300 @@ +// Copyright 2026 Emmanouil Krasanakis + +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// Authors: Emmanouil Krasanakis + +#ifndef BOOST_GRAPH_PAGE_RANK_HPP +#define BOOST_GRAPH_PAGE_RANK_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost +{ + namespace graph + { + + template < typename Graph > + auto asymmetric_normalization_weights(const Graph& g) { + using Edge = typename boost::graph_traits::edge_descriptor; + return make_function_property_map( + [&g](Edge e) { + auto u = source(e, g); + return 1.0 / out_degree(u, g); + }); + } + + template < typename Graph > + auto symmetric_normalization_weights(const Graph& g) { + using Edge = typename boost::graph_traits::edge_descriptor; + return make_function_property_map( + [&g](Edge e) { + auto u = source(e, g); + auto v = target(e, g); + return 1.0 / std::sqrt(out_degree(u, g) * out_degree(v, g)); + }); + } + + class BOOST_SYMBOL_VISIBLE vertex_map_not_converged : public std::exception + { + }; + + struct vertex_map_convergence + { + explicit vertex_map_convergence(std::size_t n, double tol=0) : n(n), tol(tol) {} // allowing tolerance for early stopping + + template < typename RankMap, typename RankMap2, typename Graph > + bool operator()( + const RankMap& rank_map, + const RankMap2& rank_map2, + const Graph& g) + { + if ( n-- == 0 ) + { + if(tol) boost::throw_exception(vertex_map_not_converged()); + return true; + } + if (!tol) + return false; + + typedef typename property_traits< RankMap >::value_type rank_type; + rank_type sum_abs(0); + BGL_FORALL_VERTICES_T(v, g, Graph) + { + rank_type difference = get(rank_map, v)-get(rank_map2, v); + if(difference<0) + difference = -difference; + sum_abs += difference; + } + return sum_abs + void personalized_page_rank_step( + const Graph& g, + const WeightMap& weight_map, + PersonalizationMap personalization_map, + RankMap from_rank, + RankMap2 to_rank, + typename property_traits< RankMap >::value_type damping, + incidence_graph_tag) + { + typedef typename property_traits< RankMap >::value_type rank_type; + rank_type l1_norm(0); // Computing the norm simultaneously avoids an extra summing iteration. + + // Initialize the constant part of maps + BGL_FORALL_VERTICES_T(v, g, Graph) { + auto v_constant = rank_type(1 - damping) * get(personalization_map, v); + put(to_rank, v, v_constant); + l1_norm += v_constant; + } + + BGL_FORALL_VERTICES_T(u, g, Graph) + { + rank_type u_rank_factor = damping * get(from_rank, u); + rank_type l1_accumulated_norm(0); // TBD: Consider making l1_norm volatile even, to reduce accumulation errors. + BGL_FORALL_OUTEDGES_T(u, e, g, Graph) + { + auto v = target(e, g); + rank_type u_rank_out = get(weight_map, e)*u_rank_factor; + put(to_rank, v, get(to_rank, v) + u_rank_out); + l1_accumulated_norm += u_rank_out; + } + l1_norm += l1_accumulated_norm; + } + BGL_FORALL_VERTICES_T(v, g, Graph) put(to_rank, v, get(to_rank, v)/l1_norm); + } + + template < + typename Graph, + typename WeightMap, + typename PersonalizationMap, + typename RankMap, + typename RankMap2 > + void page_rank_step( + const Graph& g, + const WeightMap& weight_map, + PersonalizationMap personalization_map, + RankMap from_rank, + RankMap2 to_rank, + typename property_traits< RankMap >::value_type damping, + bidirectional_graph_tag) + { + typedef typename property_traits< RankMap >::value_type damping_type; + damping_type l1_norm(0); // Computing the norm simultaneously avoids an extra summing iteration. + BGL_FORALL_VERTICES_T(v, g, Graph) + { + typename property_traits< RankMap >::value_type rank(0); + BGL_FORALL_INEDGES_T(v, e, g, Graph) rank += get(from_rank, source(e, g))*get(weight_map, e);//get(from_rank, source(e, g)) / out_degree(source(e, g), g); + auto v_score = (damping_type(1) - damping) * get(personalization_map, v) + damping * rank; + put(to_rank, v, v_score); + l1_norm += v_score; + } + BGL_FORALL_VERTICES_T(v, g, Graph) put(to_rank, v, get(to_rank, v)/l1_norm); + } + } // end namespace detail + + template < + typename Graph, + typename WeightMap, + typename PersonalizationMap, + typename RankMap, + typename Done, + typename RankMap2 > + void personalized_page_rank( + const Graph& g, + const WeightMap& weight_map, + PersonalizationMap personalization_map, + RankMap rank_map, + Done& done, + typename property_traits< RankMap >::value_type damping, + typename graph_traits< Graph >::vertices_size_type n, + RankMap2 rank_map2 + BOOST_GRAPH_ENABLE_IF_MODELS_PARM(Graph, vertex_list_graph_tag)) + { + typedef typename property_traits< PersonalizationMap >::value_type rank_type; + + rank_type personalization_norm(0); + BGL_FORALL_VERTICES_T(v, g, Graph) personalization_norm += get(personalization_map, v); + if(!personalization_norm) personalization_norm = 1; // TBD: perhaps throw error here + + // TBD: This implementation couples iterators when possible under reduced L1 cache invalidation assumptions, + // but this is not ncessarily the case due to writes. Perhaps investigate which pattern is faster. + BGL_FORALL_VERTICES_T(v, g, Graph) + { + rank_type value = get(personalization_map, v)/personalization_norm; + put(personalization_map, v, value); + put(rank_map, v, value); + } + + bool to_map_2 = true; + do + { + typedef typename graph_traits< Graph >::traversal_category category; + if (to_map_2) + detail::personalized_page_rank_step(g, weight_map, personalization_map, rank_map, rank_map2, damping, category()); + else + detail::personalized_page_rank_step(g, weight_map, personalization_map, rank_map2, rank_map, damping, category()); + to_map_2 = !to_map_2; + } while (!done(rank_map, rank_map2, g)); // Symmetric call signature that should be unaffected by mode. + + if (!to_map_2) + { + BGL_FORALL_VERTICES_T(v, g, Graph) + { + put(rank_map, v, get(rank_map2, v)*personalization_norm); + put(personalization_map, v, get(personalization_map, v)*personalization_norm); // restore to near-exact original + } + } + else + { + BGL_FORALL_VERTICES_T(v, g, Graph) + { + put(rank_map, v, get(rank_map, v)*personalization_norm); + put(personalization_map, v, get(personalization_map, v)*personalization_norm); // restore to near-exact original + } + } + } + + template < + typename Graph, + typename WeightMap, + typename PersonalizationMap, + typename RankMap, + typename Done > + void personalized_page_rank( + const Graph& g, + const WeightMap& weight_map, + PersonalizationMap personalization_map, + RankMap rank_map, + Done& done, + typename property_traits< RankMap >::value_type damping, + typename graph_traits< Graph >::vertices_size_type n) + { + typedef typename property_traits< RankMap >::value_type rank_type; + + std::vector< rank_type > ranks2(num_vertices(g)); + personalized_page_rank(g, weight_map, personalization_map, rank_map, done, damping, n, + make_iterator_property_map(ranks2.begin(), get(vertex_index, g))); + } + + template < + typename Graph, + typename WeightMap, + typename PersonalizationMap, + typename RankMap, + typename Done > + inline void personalized_page_rank( + const Graph& g, + const WeightMap& weight_map, + PersonalizationMap personalization_map, + RankMap rank_map, + Done& done, + typename property_traits< RankMap >::value_type damping = 0.85) + { + personalized_page_rank(g, weight_map, personalization_map, rank_map, done, damping, num_vertices(g)); + } + + template < + typename Graph, + typename WeightMap, + typename PersonalizationMap, + typename RankMap > + inline void personalized_page_rank( + const Graph& g, + const WeightMap& weight_map, + PersonalizationMap personalization_map, + RankMap rank_map, + typename property_traits< RankMap >::value_type damping = 0.85) + { + std::size_t n_iters(1.0/(1-damping)+0.5); // accounts for "most" random walks + auto convergence = vertex_map_convergence(n_iters); + personalized_page_rank(g, weight_map, personalization_map, rank_map, convergence, damping, num_vertices(g)); + } + + template < + typename Graph, + typename PersonalizationMap, + typename RankMap > + inline void personalized_page_rank( + const Graph& g, + PersonalizationMap personalization_map, + RankMap rank_map, + typename property_traits< RankMap >::value_type damping = 0.85) + { + personalized_page_rank(g, asymmetric_normalization(g), personalization_map, rank_map); + } + + } +} // end namespace boost::graph + +#include BOOST_GRAPH_MPI_INCLUDE() + +#endif // BOOST_GRAPH_PAGE_RANK_HPP From da8bdccc71f467eb7d4088b424d0aba90099962e Mon Sep 17 00:00:00 2001 From: maniospas Date: Sun, 24 May 2026 13:03:54 +0300 Subject: [PATCH 2/7] fully fleshed normalization schemes --- .../boost/graph/personalized_page_rank.hpp | 438 ++++++++++-------- 1 file changed, 239 insertions(+), 199 deletions(-) diff --git a/include/boost/graph/personalized_page_rank.hpp b/include/boost/graph/personalized_page_rank.hpp index ce04dc240..446ca2d77 100644 --- a/include/boost/graph/personalized_page_rank.hpp +++ b/include/boost/graph/personalized_page_rank.hpp @@ -21,278 +21,318 @@ namespace boost { - namespace graph +namespace graph +{ + + template < typename Graph > + auto markovian_weights(const Graph& g) { + using Edge = typename boost::graph_traits::edge_descriptor; + return make_function_property_map( + [&g](Edge e) + { + auto u = source(e, g); + auto denom = out_degree(u, g);; + if(!denom) return denom; + return 1.0 / denom; + } + ); + } - template < typename Graph > - auto asymmetric_normalization_weights(const Graph& g) { - using Edge = typename boost::graph_traits::edge_descriptor; - return make_function_property_map( - [&g](Edge e) { + template < typename Graph > + auto spectral_weights(const Graph& g) + { + using Edge = typename boost::graph_traits::edge_descriptor; + return make_function_property_map( + [&g](Edge e) + { auto u = source(e, g); - return 1.0 / out_degree(u, g); - }); - } + auto v = target(e, g); + std::size_t denom; + if constexpr (boost::is_bidirectional_graph::value) + denom = out_degree(u, g) * in_degree(v, g); + else + denom = out_degree(u, g) * out_degree(v, g); + return 1.0 / std::sqrt(denom); + } + ); + } - template < typename Graph > - auto symmetric_normalization_weights(const Graph& g) { - using Edge = typename boost::graph_traits::edge_descriptor; - return make_function_property_map( - [&g](Edge e) { + template < typename Graph > + auto renormalized_weights(const Graph& g) + { + using Edge = typename boost::graph_traits::edge_descriptor; + return make_function_property_map( + [&g](Edge e) + { auto u = source(e, g); auto v = target(e, g); - return 1.0 / std::sqrt(out_degree(u, g) * out_degree(v, g)); - }); - } + std::size_t denom; + if constexpr (boost::is_bidirectional_graph::value) + denom = (1+out_degree(u, g)) * (1+in_degree(v, g)); + else + denom = (1+out_degree(u, g)) * (1+out_degree(v, g)); + return 1.0 / std::sqrt(denom); + } + ); + } - class BOOST_SYMBOL_VISIBLE vertex_map_not_converged : public std::exception - { - }; + class BOOST_SYMBOL_VISIBLE vertex_map_not_converged : public std::exception + { + }; + class BOOST_SYMBOL_VISIBLE zero_personalization : public std::exception + { + }; - struct vertex_map_convergence - { - explicit vertex_map_convergence(std::size_t n, double tol=0) : n(n), tol(tol) {} // allowing tolerance for early stopping + struct vertex_map_convergence + { + explicit vertex_map_convergence(std::size_t n, double tol=0) : n(n), tol(tol) {} // allowing tolerance for early stopping - template < typename RankMap, typename RankMap2, typename Graph > - bool operator()( - const RankMap& rank_map, - const RankMap2& rank_map2, - const Graph& g) + template < typename RankMap, typename RankMap2, typename Graph > + bool operator()( + const RankMap& rank_map, + const RankMap2& rank_map2, + const Graph& g) + { + if ( n-- == 0 ) { - if ( n-- == 0 ) - { - if(tol) boost::throw_exception(vertex_map_not_converged()); - return true; - } - if (!tol) - return false; - - typedef typename property_traits< RankMap >::value_type rank_type; - rank_type sum_abs(0); - BGL_FORALL_VERTICES_T(v, g, Graph) - { - rank_type difference = get(rank_map, v)-get(rank_map2, v); - if(difference<0) - difference = -difference; - sum_abs += difference; - } - return sum_abs::value_type rank_type; + rank_type sum_abs(0); + BGL_FORALL_VERTICES_T(v, g, Graph) { - return n; + rank_type difference = get(rank_map, v)-get(rank_map2, v); + if(difference<0) + difference = -difference; + sum_abs += difference; } + return sum_abs*num_vertices(g) - void personalized_page_rank_step( - const Graph& g, - const WeightMap& weight_map, - PersonalizationMap personalization_map, - RankMap from_rank, - RankMap2 to_rank, - typename property_traits< RankMap >::value_type damping, - incidence_graph_tag) - { - typedef typename property_traits< RankMap >::value_type rank_type; - rank_type l1_norm(0); // Computing the norm simultaneously avoids an extra summing iteration. + void personalized_page_rank_step( + const Graph& g, + const WeightMap& weight_map, + PersonalizationMap personalization_map, + RankMap from_rank, + RankMap2 to_rank, + typename property_traits< RankMap >::value_type damping, + incidence_graph_tag) + { + typedef typename property_traits< RankMap >::value_type rank_type; + rank_type l1_norm(0); // Computing the norm simultaneously avoids an extra summing iteration. - // Initialize the constant part of maps - BGL_FORALL_VERTICES_T(v, g, Graph) { - auto v_constant = rank_type(1 - damping) * get(personalization_map, v); - put(to_rank, v, v_constant); - l1_norm += v_constant; - } + // Initialize the constant part of maps + BGL_FORALL_VERTICES_T(v, g, Graph) { + auto v_constant = rank_type(1 - damping) * get(personalization_map, v); + put(to_rank, v, v_constant); + l1_norm += v_constant; + } - BGL_FORALL_VERTICES_T(u, g, Graph) + BGL_FORALL_VERTICES_T(u, g, Graph) + { + rank_type u_rank_factor = damping * get(from_rank, u); + rank_type l1_accumulated_norm(0); // TBD: Consider making l1_norm volatile to reduce accumulation errors. + BGL_FORALL_OUTEDGES_T(u, e, g, Graph) { - rank_type u_rank_factor = damping * get(from_rank, u); - rank_type l1_accumulated_norm(0); // TBD: Consider making l1_norm volatile even, to reduce accumulation errors. - BGL_FORALL_OUTEDGES_T(u, e, g, Graph) - { - auto v = target(e, g); - rank_type u_rank_out = get(weight_map, e)*u_rank_factor; - put(to_rank, v, get(to_rank, v) + u_rank_out); - l1_accumulated_norm += u_rank_out; - } - l1_norm += l1_accumulated_norm; + auto v = target(e, g); + rank_type u_rank_out = get(weight_map, e)*u_rank_factor; + put(to_rank, v, get(to_rank, v) + u_rank_out); + l1_accumulated_norm += u_rank_out; } - BGL_FORALL_VERTICES_T(v, g, Graph) put(to_rank, v, get(to_rank, v)/l1_norm); + l1_norm += l1_accumulated_norm; } + BGL_FORALL_VERTICES_T(v, g, Graph) put(to_rank, v, get(to_rank, v)/l1_norm); + } - template < + template < typename Graph, typename WeightMap, typename PersonalizationMap, typename RankMap, typename RankMap2 > - void page_rank_step( - const Graph& g, - const WeightMap& weight_map, - PersonalizationMap personalization_map, - RankMap from_rank, - RankMap2 to_rank, - typename property_traits< RankMap >::value_type damping, - bidirectional_graph_tag) + void page_rank_step( + const Graph& g, + const WeightMap& weight_map, + PersonalizationMap personalization_map, + RankMap from_rank, + RankMap2 to_rank, + typename property_traits< RankMap >::value_type damping, + bidirectional_graph_tag) + { + typedef typename property_traits< RankMap >::value_type damping_type; + damping_type l1_norm(0); // Computing the norm simultaneously avoids an extra summing iteration. + BGL_FORALL_VERTICES_T(v, g, Graph) { - typedef typename property_traits< RankMap >::value_type damping_type; - damping_type l1_norm(0); // Computing the norm simultaneously avoids an extra summing iteration. - BGL_FORALL_VERTICES_T(v, g, Graph) - { - typename property_traits< RankMap >::value_type rank(0); - BGL_FORALL_INEDGES_T(v, e, g, Graph) rank += get(from_rank, source(e, g))*get(weight_map, e);//get(from_rank, source(e, g)) / out_degree(source(e, g), g); - auto v_score = (damping_type(1) - damping) * get(personalization_map, v) + damping * rank; - put(to_rank, v, v_score); - l1_norm += v_score; - } - BGL_FORALL_VERTICES_T(v, g, Graph) put(to_rank, v, get(to_rank, v)/l1_norm); + typename property_traits< RankMap >::value_type rank(0); + BGL_FORALL_INEDGES_T(v, e, g, Graph) rank += get(from_rank, source(e, g))*get(weight_map, e);//get(from_rank, source(e, g)) / out_degree(source(e, g), g); + auto v_score = (damping_type(1) - damping) * get(personalization_map, v) + damping * rank; + put(to_rank, v, v_score); + l1_norm += v_score; } - } // end namespace detail + BGL_FORALL_VERTICES_T(v, g, Graph) put(to_rank, v, get(to_rank, v)/l1_norm); + } + } // end namespace detail - template < + template < typename Graph, typename WeightMap, typename PersonalizationMap, typename RankMap, typename Done, typename RankMap2 > - void personalized_page_rank( - const Graph& g, - const WeightMap& weight_map, - PersonalizationMap personalization_map, - RankMap rank_map, - Done& done, - typename property_traits< RankMap >::value_type damping, - typename graph_traits< Graph >::vertices_size_type n, - RankMap2 rank_map2 - BOOST_GRAPH_ENABLE_IF_MODELS_PARM(Graph, vertex_list_graph_tag)) - { - typedef typename property_traits< PersonalizationMap >::value_type rank_type; + void personalized_page_rank( + const Graph& g, + const WeightMap& weight_map, + PersonalizationMap personalization_map, + RankMap rank_map, + Done& done, + typename property_traits< RankMap >::value_type damping, + typename graph_traits< Graph >::vertices_size_type n, + RankMap2 rank_map2 + BOOST_GRAPH_ENABLE_IF_MODELS_PARM(Graph, vertex_list_graph_tag)) + { + typedef typename property_traits< PersonalizationMap >::value_type rank_type; - rank_type personalization_norm(0); - BGL_FORALL_VERTICES_T(v, g, Graph) personalization_norm += get(personalization_map, v); - if(!personalization_norm) personalization_norm = 1; // TBD: perhaps throw error here + rank_type personalization_norm(0); + BGL_FORALL_VERTICES_T(v, g, Graph) personalization_norm += get(personalization_map, v); + if(!personalization_norm) + boost::throw_exception(zero_personalization()); - // TBD: This implementation couples iterators when possible under reduced L1 cache invalidation assumptions, - // but this is not ncessarily the case due to writes. Perhaps investigate which pattern is faster. - BGL_FORALL_VERTICES_T(v, g, Graph) - { - rank_type value = get(personalization_map, v)/personalization_norm; - put(personalization_map, v, value); - put(rank_map, v, value); - } + // TBD: This implementation couples iterators when possible under reduced L1 cache invalidation assumptions, + // but this is not necessarily the case because we may be grabbing twice memory lanes each time to write there. + // Should investigate which pattern is faster. + BGL_FORALL_VERTICES_T(v, g, Graph) + { + rank_type value = get(personalization_map, v)/personalization_norm; + put(personalization_map, v, value); + put(rank_map, v, value); + } - bool to_map_2 = true; - do - { - typedef typename graph_traits< Graph >::traversal_category category; - if (to_map_2) - detail::personalized_page_rank_step(g, weight_map, personalization_map, rank_map, rank_map2, damping, category()); - else - detail::personalized_page_rank_step(g, weight_map, personalization_map, rank_map2, rank_map, damping, category()); - to_map_2 = !to_map_2; - } while (!done(rank_map, rank_map2, g)); // Symmetric call signature that should be unaffected by mode. + bool to_map_2 = true; + do + { + typedef typename graph_traits< Graph >::traversal_category category; + if (to_map_2) + detail::personalized_page_rank_step(g, weight_map, personalization_map, rank_map, rank_map2, damping, category()); + else + detail::personalized_page_rank_step(g, weight_map, personalization_map, rank_map2, rank_map, damping, category()); + to_map_2 = !to_map_2; + } while (!done(rank_map, rank_map2, g)); // Symmetric call signature that should be unaffected by mode. - if (!to_map_2) + // Now multiply the result with personalization_norm to restore the order of magnitude and store it in rank_map. + // Also restore the original personalization_map's magnitude for reuse (this is only a tiny bit lossy).' + if (!to_map_2) + { + BGL_FORALL_VERTICES_T(v, g, Graph) { - BGL_FORALL_VERTICES_T(v, g, Graph) - { - put(rank_map, v, get(rank_map2, v)*personalization_norm); - put(personalization_map, v, get(personalization_map, v)*personalization_norm); // restore to near-exact original - } + put(rank_map, v, get(rank_map2, v)*personalization_norm); + put(personalization_map, v, get(personalization_map, v)*personalization_norm); } - else + } + else + { + BGL_FORALL_VERTICES_T(v, g, Graph) { - BGL_FORALL_VERTICES_T(v, g, Graph) - { - put(rank_map, v, get(rank_map, v)*personalization_norm); - put(personalization_map, v, get(personalization_map, v)*personalization_norm); // restore to near-exact original - } + put(rank_map, v, get(rank_map, v)*personalization_norm); + put(personalization_map, v, get(personalization_map, v)*personalization_norm); } } + } - template < + template < typename Graph, typename WeightMap, typename PersonalizationMap, typename RankMap, typename Done > - void personalized_page_rank( - const Graph& g, - const WeightMap& weight_map, - PersonalizationMap personalization_map, - RankMap rank_map, - Done& done, - typename property_traits< RankMap >::value_type damping, - typename graph_traits< Graph >::vertices_size_type n) - { - typedef typename property_traits< RankMap >::value_type rank_type; + void personalized_page_rank( + const Graph& g, + const WeightMap& weight_map, + PersonalizationMap personalization_map, + RankMap rank_map, + Done& done, + typename property_traits< RankMap >::value_type damping, + typename graph_traits< Graph >::vertices_size_type n) + { + typedef typename property_traits< RankMap >::value_type rank_type; - std::vector< rank_type > ranks2(num_vertices(g)); - personalized_page_rank(g, weight_map, personalization_map, rank_map, done, damping, n, - make_iterator_property_map(ranks2.begin(), get(vertex_index, g))); - } + std::vector< rank_type > ranks2(num_vertices(g)); + personalized_page_rank(g, weight_map, personalization_map, rank_map, done, damping, n, + make_iterator_property_map(ranks2.begin(), get(vertex_index, g))); + } - template < + template < typename Graph, typename WeightMap, typename PersonalizationMap, typename RankMap, typename Done > - inline void personalized_page_rank( - const Graph& g, - const WeightMap& weight_map, - PersonalizationMap personalization_map, - RankMap rank_map, - Done& done, - typename property_traits< RankMap >::value_type damping = 0.85) - { - personalized_page_rank(g, weight_map, personalization_map, rank_map, done, damping, num_vertices(g)); - } + inline void personalized_page_rank( + const Graph& g, + const WeightMap& weight_map, + PersonalizationMap personalization_map, + RankMap rank_map, + Done& done, + typename property_traits< RankMap >::value_type damping = 0.85) + { + personalized_page_rank(g, weight_map, personalization_map, rank_map, done, damping, num_vertices(g)); + } - template < + template < typename Graph, typename WeightMap, typename PersonalizationMap, typename RankMap > - inline void personalized_page_rank( - const Graph& g, - const WeightMap& weight_map, - PersonalizationMap personalization_map, - RankMap rank_map, - typename property_traits< RankMap >::value_type damping = 0.85) - { - std::size_t n_iters(1.0/(1-damping)+0.5); // accounts for "most" random walks - auto convergence = vertex_map_convergence(n_iters); - personalized_page_rank(g, weight_map, personalization_map, rank_map, convergence, damping, num_vertices(g)); - } + inline void personalized_page_rank( + const Graph& g, + const WeightMap& weight_map, + PersonalizationMap personalization_map, + RankMap rank_map, + typename property_traits< RankMap >::value_type damping = 0.85) + { + std::size_t n_iters(1.0/(1-damping)+0.5); // accounts for "most" random walks + auto convergence = vertex_map_convergence(n_iters); + personalized_page_rank(g, weight_map, personalization_map, rank_map, convergence, damping, num_vertices(g)); + } - template < + template < typename Graph, typename PersonalizationMap, typename RankMap > - inline void personalized_page_rank( - const Graph& g, - PersonalizationMap personalization_map, - RankMap rank_map, - typename property_traits< RankMap >::value_type damping = 0.85) - { - personalized_page_rank(g, asymmetric_normalization(g), personalization_map, rank_map); - } - + inline void personalized_page_rank( + const Graph& g, + PersonalizationMap personalization_map, + RankMap rank_map, + typename property_traits< RankMap >::value_type damping = 0.85) + { + personalized_page_rank(g, asymmetric_normalization(g), personalization_map, rank_map); } + +} } // end namespace boost::graph #include BOOST_GRAPH_MPI_INCLUDE() From 4e358c4684ca7cc3affc7e79242db6944bd48a76 Mon Sep 17 00:00:00 2001 From: maniospas Date: Mon, 25 May 2026 11:57:09 +0300 Subject: [PATCH 3/7] cpp14 compliance, no exceptions --- .../boost/graph/personalized_page_rank.hpp | 504 +++++++++--------- 1 file changed, 258 insertions(+), 246 deletions(-) diff --git a/include/boost/graph/personalized_page_rank.hpp b/include/boost/graph/personalized_page_rank.hpp index 446ca2d77..72c30cd18 100644 --- a/include/boost/graph/personalized_page_rank.hpp +++ b/include/boost/graph/personalized_page_rank.hpp @@ -6,8 +6,8 @@ // Authors: Emmanouil Krasanakis -#ifndef BOOST_GRAPH_PAGE_RANK_HPP -#define BOOST_GRAPH_PAGE_RANK_HPP +#ifndef BOOST_GRAPH_PERSONALIZED_PAGE_RANK_HPP +#define BOOST_GRAPH_PERSONALIZED_PAGE_RANK_HPP #include #include @@ -16,325 +16,337 @@ #include #include #include -#include #include namespace boost { -namespace graph -{ - - template < typename Graph > - auto markovian_weights(const Graph& g) + namespace graph { - using Edge = typename boost::graph_traits::edge_descriptor; - return make_function_property_map( - [&g](Edge e) - { - auto u = source(e, g); - auto denom = out_degree(u, g);; - if(!denom) return denom; - return 1.0 / denom; - } - ); - } + namespace detail { + template < typename Graph> + std::size_t proxy_in_degree( + typename boost::graph_traits::vertex_descriptor v, + const Graph& g + BOOST_GRAPH_ENABLE_IF_MODELS_PARM(Graph, vertex_list_graph_tag)) + { + return out_degree(v, g); + } + template < typename Graph> + std::size_t proxy_in_degree( + typename boost::graph_traits::vertex_descriptor v, + const Graph& g + BOOST_GRAPH_ENABLE_IF_MODELS_PARM(Graph, bidirectional_graph_tag)) + { + return in_degree(v, g); + } + } - template < typename Graph > - auto spectral_weights(const Graph& g) - { - using Edge = typename boost::graph_traits::edge_descriptor; - return make_function_property_map( - [&g](Edge e) + template + boost::function_property_map< + std::function::edge_descriptor)>, + typename boost::graph_traits::edge_descriptor> + markovian_weights(const Graph& g) + { + using Edge = typename boost::graph_traits::edge_descriptor; + using Func = std::function; + Func f = [&g](Edge e) + { + auto u = source(e, g); + std::size_t denom; + denom = out_degree(u, g) ; + return 1.0 / std::sqrt(denom); + }; + return make_function_property_map(f); + } + + template + boost::function_property_map< + std::function::edge_descriptor)>, + typename boost::graph_traits::edge_descriptor> + spectral_weights(const Graph& g) + { + using Edge = typename boost::graph_traits::edge_descriptor; + using Func = std::function; + Func f = [&g](Edge e) + { + auto u = source(e, g); + auto v = target(e, g); + std::size_t denom; + denom = out_degree(u, g) * detail::proxy_in_degree(v, g); + return 1.0 / std::sqrt(denom); + }; + return make_function_property_map(f); + } + + template + boost::function_property_map< + std::function::edge_descriptor)>, + typename boost::graph_traits::edge_descriptor> + renormalized_weights(const Graph& g) + { + using Edge = typename boost::graph_traits::edge_descriptor; + using Func = std::function; + Func f = [&g](Edge e) + { + auto u = source(e, g); + auto v = target(e, g); + std::size_t denom; + denom = (1+out_degree(u, g)) * (1+detail::proxy_in_degree(v, g)); + return 1.0 / std::sqrt(denom); + }; + return make_function_property_map(f); + } + + struct vertex_map_convergence + { + explicit vertex_map_convergence(std::size_t n, double tol=0) : n(n), tol(tol) {} // allowing tolerance for early stopping + + template < typename RankMap, typename RankMap2, typename Graph > + bool operator()( + const RankMap& rank_map, + const RankMap2& rank_map2, + const Graph& g) + { + if ( n-- == 0 ) { - auto u = source(e, g); - auto v = target(e, g); - std::size_t denom; - if constexpr (boost::is_bidirectional_graph::value) - denom = out_degree(u, g) * in_degree(v, g); - else - denom = out_degree(u, g) * out_degree(v, g); - return 1.0 / std::sqrt(denom); + return true; } - ); - } + if (!tol) + return false; - template < typename Graph > - auto renormalized_weights(const Graph& g) - { - using Edge = typename boost::graph_traits::edge_descriptor; - return make_function_property_map( - [&g](Edge e) + typedef typename property_traits< RankMap >::value_type rank_type; + rank_type sum_abs(0); + BGL_FORALL_VERTICES_T(v, g, Graph) { - auto u = source(e, g); - auto v = target(e, g); - std::size_t denom; - if constexpr (boost::is_bidirectional_graph::value) - denom = (1+out_degree(u, g)) * (1+in_degree(v, g)); - else - denom = (1+out_degree(u, g)) * (1+out_degree(v, g)); - return 1.0 / std::sqrt(denom); + rank_type difference = get(rank_map, v)-get(rank_map2, v); + if(difference<0) + difference = -difference; + sum_abs += difference; } - ); - } - - class BOOST_SYMBOL_VISIBLE vertex_map_not_converged : public std::exception - { - }; - class BOOST_SYMBOL_VISIBLE zero_personalization : public std::exception - { - }; - - struct vertex_map_convergence - { - explicit vertex_map_convergence(std::size_t n, double tol=0) : n(n), tol(tol) {} // allowing tolerance for early stopping + return sum_abs*num_vertices(g) - bool operator()( - const RankMap& rank_map, - const RankMap2& rank_map2, - const Graph& g) - { - if ( n-- == 0 ) + std::size_t get_remaining_iters() const { - if(tol) - boost::throw_exception(vertex_map_not_converged()); - return true; + return n; } - if (!tol) - return false; - typedef typename property_traits< RankMap >::value_type rank_type; - rank_type sum_abs(0); - BGL_FORALL_VERTICES_T(v, g, Graph) + bool has_converged() const { - rank_type difference = get(rank_map, v)-get(rank_map2, v); - if(difference<0) - difference = -difference; - sum_abs += difference; + return n || !tol; } - return sum_abs*num_vertices(g) - void personalized_page_rank_step( - const Graph& g, - const WeightMap& weight_map, - PersonalizationMap personalization_map, - RankMap from_rank, - RankMap2 to_rank, - typename property_traits< RankMap >::value_type damping, - incidence_graph_tag) - { - typedef typename property_traits< RankMap >::value_type rank_type; - rank_type l1_norm(0); // Computing the norm simultaneously avoids an extra summing iteration. + void personalized_page_rank_step( + const Graph& g, + WeightMap weight_map, + PersonalizationMap personalization_map, + RankMap from_rank, + RankMap2 to_rank, + typename property_traits< RankMap >::value_type damping, + incidence_graph_tag) + { + typedef typename property_traits< RankMap >::value_type rank_type; + rank_type l1_norm(0); // Computing the norm simultaneously avoids an extra summing iteration. - // Initialize the constant part of maps - BGL_FORALL_VERTICES_T(v, g, Graph) { - auto v_constant = rank_type(1 - damping) * get(personalization_map, v); - put(to_rank, v, v_constant); - l1_norm += v_constant; - } + // Initialize the constant part of maps + BGL_FORALL_VERTICES_T(v, g, Graph) { + auto v_constant = rank_type(1 - damping) * get(personalization_map, v); + put(to_rank, v, v_constant); + l1_norm += v_constant; + } - BGL_FORALL_VERTICES_T(u, g, Graph) - { - rank_type u_rank_factor = damping * get(from_rank, u); - rank_type l1_accumulated_norm(0); // TBD: Consider making l1_norm volatile to reduce accumulation errors. - BGL_FORALL_OUTEDGES_T(u, e, g, Graph) + BGL_FORALL_VERTICES_T(u, g, Graph) { - auto v = target(e, g); - rank_type u_rank_out = get(weight_map, e)*u_rank_factor; - put(to_rank, v, get(to_rank, v) + u_rank_out); - l1_accumulated_norm += u_rank_out; + rank_type u_rank_factor = damping * get(from_rank, u); + rank_type l1_accumulated_norm(0); // TBD: Consider making l1_norm volatile to reduce accumulation errors. + BGL_FORALL_OUTEDGES_T(u, e, g, Graph) + { + auto v = target(e, g); + rank_type u_rank_out = get(weight_map, e)*u_rank_factor; + put(to_rank, v, get(to_rank, v) + u_rank_out); + l1_accumulated_norm += u_rank_out; + } + l1_norm += l1_accumulated_norm; } - l1_norm += l1_accumulated_norm; + BGL_FORALL_VERTICES_T(v, g, Graph) put(to_rank, v, get(to_rank, v)/l1_norm); } - BGL_FORALL_VERTICES_T(v, g, Graph) put(to_rank, v, get(to_rank, v)/l1_norm); - } - template < + template < typename Graph, typename WeightMap, typename PersonalizationMap, typename RankMap, typename RankMap2 > - void page_rank_step( - const Graph& g, - const WeightMap& weight_map, - PersonalizationMap personalization_map, - RankMap from_rank, - RankMap2 to_rank, - typename property_traits< RankMap >::value_type damping, - bidirectional_graph_tag) - { - typedef typename property_traits< RankMap >::value_type damping_type; - damping_type l1_norm(0); // Computing the norm simultaneously avoids an extra summing iteration. - BGL_FORALL_VERTICES_T(v, g, Graph) + void page_rank_step( + const Graph& g, + WeightMap weight_map, + PersonalizationMap personalization_map, + RankMap from_rank, + RankMap2 to_rank, + typename property_traits< RankMap >::value_type damping, + bidirectional_graph_tag) { - typename property_traits< RankMap >::value_type rank(0); - BGL_FORALL_INEDGES_T(v, e, g, Graph) rank += get(from_rank, source(e, g))*get(weight_map, e);//get(from_rank, source(e, g)) / out_degree(source(e, g), g); - auto v_score = (damping_type(1) - damping) * get(personalization_map, v) + damping * rank; - put(to_rank, v, v_score); - l1_norm += v_score; + typedef typename property_traits< RankMap >::value_type damping_type; + damping_type l1_norm(0); // Computing the norm simultaneously avoids an extra summing iteration. + BGL_FORALL_VERTICES_T(v, g, Graph) + { + typename property_traits< RankMap >::value_type rank(0); + BGL_FORALL_INEDGES_T(v, e, g, Graph) rank += get(from_rank, source(e, g))*get(weight_map, e);//get(from_rank, source(e, g)) / out_degree(source(e, g), g); + auto v_score = (damping_type(1) - damping) * get(personalization_map, v) + damping * rank; + put(to_rank, v, v_score); + l1_norm += v_score; + } + BGL_FORALL_VERTICES_T(v, g, Graph) put(to_rank, v, get(to_rank, v)/l1_norm); } - BGL_FORALL_VERTICES_T(v, g, Graph) put(to_rank, v, get(to_rank, v)/l1_norm); - } - } // end namespace detail + } // end namespace detail - template < + template < typename Graph, typename WeightMap, typename PersonalizationMap, typename RankMap, typename Done, typename RankMap2 > - void personalized_page_rank( - const Graph& g, - const WeightMap& weight_map, - PersonalizationMap personalization_map, - RankMap rank_map, - Done& done, - typename property_traits< RankMap >::value_type damping, - typename graph_traits< Graph >::vertices_size_type n, - RankMap2 rank_map2 - BOOST_GRAPH_ENABLE_IF_MODELS_PARM(Graph, vertex_list_graph_tag)) - { - typedef typename property_traits< PersonalizationMap >::value_type rank_type; - - rank_type personalization_norm(0); - BGL_FORALL_VERTICES_T(v, g, Graph) personalization_norm += get(personalization_map, v); - if(!personalization_norm) - boost::throw_exception(zero_personalization()); - - // TBD: This implementation couples iterators when possible under reduced L1 cache invalidation assumptions, - // but this is not necessarily the case because we may be grabbing twice memory lanes each time to write there. - // Should investigate which pattern is faster. - BGL_FORALL_VERTICES_T(v, g, Graph) + void personalized_page_rank( + const Graph& g, + WeightMap weight_map, + PersonalizationMap personalization_map, + RankMap rank_map, + Done& done, + typename property_traits< RankMap >::value_type damping, + typename graph_traits< Graph >::vertices_size_type n, + RankMap2 rank_map2 + BOOST_GRAPH_ENABLE_IF_MODELS_PARM(Graph, vertex_list_graph_tag)) { - rank_type value = get(personalization_map, v)/personalization_norm; - put(personalization_map, v, value); - put(rank_map, v, value); - } + typedef typename property_traits< PersonalizationMap >::value_type rank_type; - bool to_map_2 = true; - do - { - typedef typename graph_traits< Graph >::traversal_category category; - if (to_map_2) - detail::personalized_page_rank_step(g, weight_map, personalization_map, rank_map, rank_map2, damping, category()); - else - detail::personalized_page_rank_step(g, weight_map, personalization_map, rank_map2, rank_map, damping, category()); - to_map_2 = !to_map_2; - } while (!done(rank_map, rank_map2, g)); // Symmetric call signature that should be unaffected by mode. + rank_type personalization_norm(0); + BGL_FORALL_VERTICES_T(v, g, Graph) personalization_norm += get(personalization_map, v); - // Now multiply the result with personalization_norm to restore the order of magnitude and store it in rank_map. - // Also restore the original personalization_map's magnitude for reuse (this is only a tiny bit lossy).' - if (!to_map_2) - { + // TBD: This implementation couples iterators when possible under reduced L1 cache invalidation assumptions, + // but this is not necessarily the case because we may be grabbing 2x memory lanes each time to write there. + // Should investigate which pattern is faster. BGL_FORALL_VERTICES_T(v, g, Graph) { - put(rank_map, v, get(rank_map2, v)*personalization_norm); - put(personalization_map, v, get(personalization_map, v)*personalization_norm); + rank_type value = get(personalization_map, v)/personalization_norm; + put(personalization_map, v, value); + put(rank_map, v, value); } - } - else - { - BGL_FORALL_VERTICES_T(v, g, Graph) + + bool to_map_2 = true; + do + { + typedef typename graph_traits< Graph >::traversal_category category; + if (to_map_2) + detail::personalized_page_rank_step(g, weight_map, personalization_map, rank_map, rank_map2, damping, category()); + else + detail::personalized_page_rank_step(g, weight_map, personalization_map, rank_map2, rank_map, damping, category()); + to_map_2 = !to_map_2; + } while (!done(rank_map, rank_map2, g)); // Symmetric call signature that should be unaffected by mode. + + // Now multiply the result with personalization_norm to restore the order of magnitude and store it in rank_map. + // Also restore the original personalization_map's magnitude for reuse (this is only a tiny bit lossy).' + if (!to_map_2) { - put(rank_map, v, get(rank_map, v)*personalization_norm); - put(personalization_map, v, get(personalization_map, v)*personalization_norm); + BGL_FORALL_VERTICES_T(v, g, Graph) + { + put(rank_map, v, get(rank_map2, v)*personalization_norm); + put(personalization_map, v, get(personalization_map, v)*personalization_norm); + } + } + else + { + BGL_FORALL_VERTICES_T(v, g, Graph) + { + put(rank_map, v, get(rank_map, v)*personalization_norm); + put(personalization_map, v, get(personalization_map, v)*personalization_norm); + } } } - } - template < + template < typename Graph, typename WeightMap, typename PersonalizationMap, typename RankMap, typename Done > - void personalized_page_rank( - const Graph& g, - const WeightMap& weight_map, - PersonalizationMap personalization_map, - RankMap rank_map, - Done& done, - typename property_traits< RankMap >::value_type damping, - typename graph_traits< Graph >::vertices_size_type n) - { - typedef typename property_traits< RankMap >::value_type rank_type; + void personalized_page_rank( + const Graph& g, + WeightMap weight_map, + PersonalizationMap personalization_map, + RankMap rank_map, + Done& done, + typename property_traits< RankMap >::value_type damping, + typename graph_traits< Graph >::vertices_size_type n) + { + typedef typename property_traits< RankMap >::value_type rank_type; - std::vector< rank_type > ranks2(num_vertices(g)); - personalized_page_rank(g, weight_map, personalization_map, rank_map, done, damping, n, - make_iterator_property_map(ranks2.begin(), get(vertex_index, g))); - } + std::vector< rank_type > ranks2(num_vertices(g)); + personalized_page_rank(g, weight_map, personalization_map, rank_map, done, damping, n, + make_iterator_property_map(ranks2.begin(), get(vertex_index, g))); + } - template < + template < typename Graph, typename WeightMap, typename PersonalizationMap, typename RankMap, typename Done > - inline void personalized_page_rank( - const Graph& g, - const WeightMap& weight_map, - PersonalizationMap personalization_map, - RankMap rank_map, - Done& done, - typename property_traits< RankMap >::value_type damping = 0.85) - { - personalized_page_rank(g, weight_map, personalization_map, rank_map, done, damping, num_vertices(g)); - } + inline void personalized_page_rank( + const Graph& g, + WeightMap weight_map, + PersonalizationMap personalization_map, + RankMap rank_map, + Done& done, + typename property_traits< RankMap >::value_type damping = 0.85) + { + personalized_page_rank(g, weight_map, personalization_map, rank_map, done, damping, num_vertices(g)); + } - template < + template < typename Graph, typename WeightMap, typename PersonalizationMap, typename RankMap > - inline void personalized_page_rank( - const Graph& g, - const WeightMap& weight_map, - PersonalizationMap personalization_map, - RankMap rank_map, - typename property_traits< RankMap >::value_type damping = 0.85) - { - std::size_t n_iters(1.0/(1-damping)+0.5); // accounts for "most" random walks - auto convergence = vertex_map_convergence(n_iters); - personalized_page_rank(g, weight_map, personalization_map, rank_map, convergence, damping, num_vertices(g)); - } + inline void personalized_page_rank( + const Graph& g, + WeightMap weight_map, + PersonalizationMap personalization_map, + RankMap rank_map, + typename property_traits< RankMap >::value_type damping = 0.85) + { + std::size_t n_iters(1.0/(1-damping)+0.5); // accounts for "most" random walks + auto convergence = vertex_map_convergence(n_iters); + personalized_page_rank(g, weight_map, personalization_map, rank_map, convergence, damping, num_vertices(g)); + } - template < + template < typename Graph, typename PersonalizationMap, typename RankMap > - inline void personalized_page_rank( - const Graph& g, - PersonalizationMap personalization_map, - RankMap rank_map, - typename property_traits< RankMap >::value_type damping = 0.85) - { - personalized_page_rank(g, asymmetric_normalization(g), personalization_map, rank_map); - } + inline void personalized_page_rank( + const Graph& g, + PersonalizationMap personalization_map, + RankMap rank_map, + typename property_traits< RankMap >::value_type damping = 0.85) + { + personalized_page_rank(g, asymmetric_normalization(g), personalization_map, rank_map); + } -} + } } // end namespace boost::graph -#include BOOST_GRAPH_MPI_INCLUDE() - -#endif // BOOST_GRAPH_PAGE_RANK_HPP +#endif // BOOST_GRAPH_PERSONALIZED_PAGE_RANK_HPP From 12ddd8fd68d1086baf743f50b005ffb1e2bdcc0d Mon Sep 17 00:00:00 2001 From: maniospas Date: Sun, 7 Jun 2026 23:04:52 +0300 Subject: [PATCH 4/7] moved personalized pagerank to the agreed upon api --- .../boost/graph/personalized_page_rank.hpp | 495 +++++++++--------- 1 file changed, 250 insertions(+), 245 deletions(-) diff --git a/include/boost/graph/personalized_page_rank.hpp b/include/boost/graph/personalized_page_rank.hpp index 72c30cd18..cc2b71e12 100644 --- a/include/boost/graph/personalized_page_rank.hpp +++ b/include/boost/graph/personalized_page_rank.hpp @@ -20,333 +20,338 @@ namespace boost { - namespace graph +namespace graph +{ + namespace personalized_page_rank_detail { - namespace detail { - template < typename Graph> - std::size_t proxy_in_degree( - typename boost::graph_traits::vertex_descriptor v, - const Graph& g - BOOST_GRAPH_ENABLE_IF_MODELS_PARM(Graph, vertex_list_graph_tag)) - { - return out_degree(v, g); - } - template < typename Graph> - std::size_t proxy_in_degree( - typename boost::graph_traits::vertex_descriptor v, - const Graph& g - BOOST_GRAPH_ENABLE_IF_MODELS_PARM(Graph, bidirectional_graph_tag)) - { - return in_degree(v, g); - } + template < typename Graph> + std::size_t in_or_out_degree( + typename boost::graph_traits::vertex_descriptor v, + const Graph& g, + bidirectional_graph_tag) + { + return in_degree(v, g); + } + template < typename Graph> + std::size_t in_or_out_degree( + typename boost::graph_traits::vertex_descriptor v, + const Graph& g, + incidence_graph_tag) + { + return out_degree(v, g); } + } - template - boost::function_property_map< + template + boost::function_property_map< std::function::edge_descriptor)>, typename boost::graph_traits::edge_descriptor> - markovian_weights(const Graph& g) + markovian_weights(const Graph& g) + { + using Edge = typename boost::graph_traits::edge_descriptor; + using Func = std::function; + Func f = [&g](Edge e) { - using Edge = typename boost::graph_traits::edge_descriptor; - using Func = std::function; - Func f = [&g](Edge e) - { - auto u = source(e, g); - std::size_t denom; - denom = out_degree(u, g) ; - return 1.0 / std::sqrt(denom); - }; - return make_function_property_map(f); - } + auto u = source(e, g); + std::size_t denom; + denom = out_degree(u, g) ; + return 1.0 / denom; + }; + return make_function_property_map(f); + } - template - boost::function_property_map< + template + boost::function_property_map< std::function::edge_descriptor)>, typename boost::graph_traits::edge_descriptor> - spectral_weights(const Graph& g) + spectral_weights(const Graph& g) + { + using Edge = typename boost::graph_traits::edge_descriptor; + using Func = std::function; + Func f = [&g](Edge e) { - using Edge = typename boost::graph_traits::edge_descriptor; - using Func = std::function; - Func f = [&g](Edge e) - { - auto u = source(e, g); - auto v = target(e, g); - std::size_t denom; - denom = out_degree(u, g) * detail::proxy_in_degree(v, g); - return 1.0 / std::sqrt(denom); - }; - return make_function_property_map(f); - } + typedef typename boost::graph_traits::traversal_category category; + auto u = source(e, g); + auto v = target(e, g); + std::size_t denom; + denom = out_degree(u, g) * personalized_page_rank_detail::in_or_out_degree(v, g, category()); + return 1.0 / std::sqrt(denom); + }; + return make_function_property_map(f); + } - template - boost::function_property_map< + template + boost::function_property_map< std::function::edge_descriptor)>, typename boost::graph_traits::edge_descriptor> - renormalized_weights(const Graph& g) + renormalized_weights(const Graph& g) + { + using Edge = typename boost::graph_traits::edge_descriptor; + using Func = std::function; + Func f = [&g](Edge e) { - using Edge = typename boost::graph_traits::edge_descriptor; - using Func = std::function; - Func f = [&g](Edge e) - { - auto u = source(e, g); - auto v = target(e, g); - std::size_t denom; - denom = (1+out_degree(u, g)) * (1+detail::proxy_in_degree(v, g)); - return 1.0 / std::sqrt(denom); - }; - return make_function_property_map(f); - } + typedef typename boost::graph_traits::traversal_category category; + auto u = source(e, g); + auto v = target(e, g); + std::size_t denom; + denom = (1+out_degree(u, g)) * (1+personalized_page_rank_detail::in_or_out_degree(v, g, category())); + return 1.0 / std::sqrt(denom); + }; + return make_function_property_map(f); + } - struct vertex_map_convergence - { - explicit vertex_map_convergence(std::size_t n, double tol=0) : n(n), tol(tol) {} // allowing tolerance for early stopping + struct vertex_map_convergence + { + explicit vertex_map_convergence(std::size_t n, double tol=0) : n(n), tol(tol) {} // allowing tolerance for early stopping - template < typename RankMap, typename RankMap2, typename Graph > - bool operator()( - const RankMap& rank_map, - const RankMap2& rank_map2, - const Graph& g) + template < typename RankMap, typename RankMap2, typename Graph > + bool operator()( + const RankMap& current, + const RankMap2& previous, + const Graph& g) + { + if ( n-- == 0 ) { - if ( n-- == 0 ) - { - return true; - } - if (!tol) - return false; - - typedef typename property_traits< RankMap >::value_type rank_type; - rank_type sum_abs(0); - BGL_FORALL_VERTICES_T(v, g, Graph) - { - rank_type difference = get(rank_map, v)-get(rank_map2, v); - if(difference<0) - difference = -difference; - sum_abs += difference; - } - return sum_abs*num_vertices(g)::value_type rank_type; + rank_type sum_abs(0); + for (auto v : boost::make_iterator_range(vertices(g))) { - return n || !tol; + rank_type difference = get(current, v)-get(previous, v); + sum_abs += std::abs(difference); } + return sum_abs*num_vertices(g) - void personalized_page_rank_step( - const Graph& g, - WeightMap weight_map, - PersonalizationMap personalization_map, - RankMap from_rank, - RankMap2 to_rank, - typename property_traits< RankMap >::value_type damping, - incidence_graph_tag) - { - typedef typename property_traits< RankMap >::value_type rank_type; - rank_type l1_norm(0); // Computing the norm simultaneously avoids an extra summing iteration. + void personalized_page_rank_step( + const Graph& g, + WeightMap weight_map, + PersonalizationMap personalization_map, + RankMap from_rank, + RankMap2 to_rank, + typename property_traits< RankMap >::value_type damping, + incidence_graph_tag) + { + typedef typename property_traits< RankMap >::value_type rank_type; + rank_type l1_norm(0); // Computing the norm simultaneously avoids an extra summing iteration. - // Initialize the constant part of maps - BGL_FORALL_VERTICES_T(v, g, Graph) { - auto v_constant = rank_type(1 - damping) * get(personalization_map, v); - put(to_rank, v, v_constant); - l1_norm += v_constant; - } + // Initialize the constant part of maps + for (auto v : boost::make_iterator_range(vertices(g))) + { + auto v_constant = rank_type(1 - damping) * get(personalization_map, v); + put(to_rank, v, v_constant); + l1_norm += v_constant; + } - BGL_FORALL_VERTICES_T(u, g, Graph) + for (auto u : boost::make_iterator_range(vertices(g))) + { + rank_type u_rank_factor = damping * get(from_rank, u); + rank_type l1_accumulated_norm(0); // TBD: Consider making l1_norm volatile to reduce accumulation errors. + for (auto e : boost::make_iterator_range(out_edges(u, g))) { - rank_type u_rank_factor = damping * get(from_rank, u); - rank_type l1_accumulated_norm(0); // TBD: Consider making l1_norm volatile to reduce accumulation errors. - BGL_FORALL_OUTEDGES_T(u, e, g, Graph) - { - auto v = target(e, g); - rank_type u_rank_out = get(weight_map, e)*u_rank_factor; - put(to_rank, v, get(to_rank, v) + u_rank_out); - l1_accumulated_norm += u_rank_out; - } - l1_norm += l1_accumulated_norm; + auto v = target(e, g); + rank_type u_rank_out = get(weight_map, e)*u_rank_factor; + put(to_rank, v, get(to_rank, v) + u_rank_out); + l1_accumulated_norm += u_rank_out; } - BGL_FORALL_VERTICES_T(v, g, Graph) put(to_rank, v, get(to_rank, v)/l1_norm); + l1_norm += l1_accumulated_norm; } + BGL_FORALL_VERTICES_T(v, g, Graph) put(to_rank, v, get(to_rank, v)/l1_norm); + } - template < + template < typename Graph, typename WeightMap, typename PersonalizationMap, typename RankMap, typename RankMap2 > - void page_rank_step( - const Graph& g, - WeightMap weight_map, - PersonalizationMap personalization_map, - RankMap from_rank, - RankMap2 to_rank, - typename property_traits< RankMap >::value_type damping, - bidirectional_graph_tag) + void page_rank_step( + const Graph& g, + WeightMap weight_map, + PersonalizationMap personalization_map, + RankMap from_rank, + RankMap2 to_rank, + typename property_traits< RankMap >::value_type damping, + bidirectional_graph_tag) + { + typedef typename property_traits< RankMap >::value_type damping_type; + damping_type l1_norm(0); // Computing the norm simultaneously avoids an extra summing iteration. + BGL_FORALL_VERTICES_T(v, g, Graph) { - typedef typename property_traits< RankMap >::value_type damping_type; - damping_type l1_norm(0); // Computing the norm simultaneously avoids an extra summing iteration. - BGL_FORALL_VERTICES_T(v, g, Graph) - { - typename property_traits< RankMap >::value_type rank(0); - BGL_FORALL_INEDGES_T(v, e, g, Graph) rank += get(from_rank, source(e, g))*get(weight_map, e);//get(from_rank, source(e, g)) / out_degree(source(e, g), g); - auto v_score = (damping_type(1) - damping) * get(personalization_map, v) + damping * rank; - put(to_rank, v, v_score); - l1_norm += v_score; - } - BGL_FORALL_VERTICES_T(v, g, Graph) put(to_rank, v, get(to_rank, v)/l1_norm); + typename property_traits< RankMap >::value_type rank(0); + BGL_FORALL_INEDGES_T(v, e, g, Graph) rank += get(from_rank, source(e, g))*get(weight_map, e);//get(from_rank, source(e, g)) / out_degree(source(e, g), g); + auto v_score = (damping_type(1) - damping) * get(personalization_map, v) + damping * rank; + put(to_rank, v, v_score); + l1_norm += v_score; } - } // end namespace detail + BGL_FORALL_VERTICES_T(v, g, Graph) put(to_rank, v, get(to_rank, v)/l1_norm); + } + } // end namespace detail - template < + template < typename Graph, typename WeightMap, typename PersonalizationMap, typename RankMap, typename Done, typename RankMap2 > - void personalized_page_rank( - const Graph& g, - WeightMap weight_map, - PersonalizationMap personalization_map, - RankMap rank_map, - Done& done, - typename property_traits< RankMap >::value_type damping, - typename graph_traits< Graph >::vertices_size_type n, - RankMap2 rank_map2 - BOOST_GRAPH_ENABLE_IF_MODELS_PARM(Graph, vertex_list_graph_tag)) - { - typedef typename property_traits< PersonalizationMap >::value_type rank_type; - - rank_type personalization_norm(0); - BGL_FORALL_VERTICES_T(v, g, Graph) personalization_norm += get(personalization_map, v); + Done personalized_page_rank( + const Graph& g, + WeightMap weight_map, + PersonalizationMap personalization_map, + RankMap rank_map, + Done done, + typename property_traits< RankMap >::value_type damping, + typename graph_traits< Graph >::vertices_size_type n, + RankMap2 rank_map2 + BOOST_GRAPH_ENABLE_IF_MODELS_PARM(Graph, vertex_list_graph_tag)) + { + typedef typename property_traits< PersonalizationMap >::value_type rank_type; - // TBD: This implementation couples iterators when possible under reduced L1 cache invalidation assumptions, - // but this is not necessarily the case because we may be grabbing 2x memory lanes each time to write there. - // Should investigate which pattern is faster. - BGL_FORALL_VERTICES_T(v, g, Graph) - { - rank_type value = get(personalization_map, v)/personalization_norm; - put(personalization_map, v, value); - put(rank_map, v, value); - } + rank_type personalization_norm(0); + BGL_FORALL_VERTICES_T(v, g, Graph) personalization_norm += get(personalization_map, v); - bool to_map_2 = true; - do - { - typedef typename graph_traits< Graph >::traversal_category category; - if (to_map_2) - detail::personalized_page_rank_step(g, weight_map, personalization_map, rank_map, rank_map2, damping, category()); - else - detail::personalized_page_rank_step(g, weight_map, personalization_map, rank_map2, rank_map, damping, category()); - to_map_2 = !to_map_2; - } while (!done(rank_map, rank_map2, g)); // Symmetric call signature that should be unaffected by mode. + // TBD: This implementation couples iterators when possible under reduced L1 cache invalidation assumptions, + // but this is not necessarily the case because we may be grabbing 2x memory lanes each time to write there. + // Should investigate which pattern is faster. + BGL_FORALL_VERTICES_T(v, g, Graph) + { + rank_type value = get(personalization_map, v)/personalization_norm; + put(personalization_map, v, value); + put(rank_map, v, value); + } - // Now multiply the result with personalization_norm to restore the order of magnitude and store it in rank_map. - // Also restore the original personalization_map's magnitude for reuse (this is only a tiny bit lossy).' - if (!to_map_2) + bool to_map_2 = true; + do + { + typedef typename graph_traits< Graph >::traversal_category category; + if (to_map_2) + personalized_page_rank_detail::personalized_page_rank_step(g, weight_map, personalization_map, rank_map, rank_map2, damping, category()); + else + personalized_page_rank_detail::personalized_page_rank_step(g, weight_map, personalization_map, rank_map2, rank_map, damping, category()); + to_map_2 = !to_map_2; + } while ((to_map_2 && !done(rank_map, rank_map2, g)) || (!to_map_2 && !done(rank_map2, rank_map, g))); + + // Now multiply the result with personalization_norm to restore the order of magnitude and store it in rank_map. + // Also restore the original personalization_map's magnitude for reuse (this is lossy up to numerical tolerance but leaner).' + if (!to_map_2) + { + for (auto v : boost::make_iterator_range(vertices(g))) { - BGL_FORALL_VERTICES_T(v, g, Graph) - { - put(rank_map, v, get(rank_map2, v)*personalization_norm); - put(personalization_map, v, get(personalization_map, v)*personalization_norm); - } + put(rank_map, v, get(rank_map2, v)*personalization_norm); + put(personalization_map, v, get(personalization_map, v)*personalization_norm); } - else + } + else + { + for (auto v : boost::make_iterator_range(vertices(g))) { - BGL_FORALL_VERTICES_T(v, g, Graph) - { - put(rank_map, v, get(rank_map, v)*personalization_norm); - put(personalization_map, v, get(personalization_map, v)*personalization_norm); - } + put(rank_map, v, get(rank_map, v)*personalization_norm); + put(personalization_map, v, get(personalization_map, v)*personalization_norm); } } + return done; + } - template < + template < typename Graph, typename WeightMap, typename PersonalizationMap, typename RankMap, typename Done > - void personalized_page_rank( - const Graph& g, - WeightMap weight_map, - PersonalizationMap personalization_map, - RankMap rank_map, - Done& done, - typename property_traits< RankMap >::value_type damping, - typename graph_traits< Graph >::vertices_size_type n) - { - typedef typename property_traits< RankMap >::value_type rank_type; + Done personalized_page_rank( + const Graph& g, + WeightMap weight_map, + PersonalizationMap personalization_map, + RankMap rank_map, + Done done, + typename property_traits< RankMap >::value_type damping, + typename graph_traits< Graph >::vertices_size_type n) + { + typedef typename property_traits< RankMap >::value_type rank_type; - std::vector< rank_type > ranks2(num_vertices(g)); - personalized_page_rank(g, weight_map, personalization_map, rank_map, done, damping, n, - make_iterator_property_map(ranks2.begin(), get(vertex_index, g))); - } + std::vector< rank_type > ranks2(num_vertices(g)); + return personalized_page_rank(g, weight_map, personalization_map, rank_map, done, damping, n, + make_iterator_property_map(ranks2.begin(), get(vertex_index, g))); + } - template < + template < typename Graph, typename WeightMap, typename PersonalizationMap, typename RankMap, typename Done > - inline void personalized_page_rank( - const Graph& g, - WeightMap weight_map, - PersonalizationMap personalization_map, - RankMap rank_map, - Done& done, - typename property_traits< RankMap >::value_type damping = 0.85) - { - personalized_page_rank(g, weight_map, personalization_map, rank_map, done, damping, num_vertices(g)); - } + inline Done personalized_page_rank( + const Graph& g, + WeightMap weight_map, + PersonalizationMap personalization_map, + RankMap rank_map, + Done done, + typename property_traits< RankMap >::value_type damping = 0.85) + { + return personalized_page_rank(g, weight_map, personalization_map, rank_map, done, damping, num_vertices(g)); + } - template < + template < typename Graph, typename WeightMap, typename PersonalizationMap, typename RankMap > - inline void personalized_page_rank( - const Graph& g, - WeightMap weight_map, - PersonalizationMap personalization_map, - RankMap rank_map, - typename property_traits< RankMap >::value_type damping = 0.85) - { - std::size_t n_iters(1.0/(1-damping)+0.5); // accounts for "most" random walks - auto convergence = vertex_map_convergence(n_iters); - personalized_page_rank(g, weight_map, personalization_map, rank_map, convergence, damping, num_vertices(g)); - } + inline vertex_map_convergence personalized_page_rank( + const Graph& g, + WeightMap weight_map, + PersonalizationMap personalization_map, + RankMap rank_map, + typename property_traits< RankMap >::value_type damping = 0.85) + { + std::size_t n_iters(1.0/(1-damping)+0.5); // accounts for "most" random walks + auto convergence = vertex_map_convergence(n_iters); + return personalized_page_rank(g, weight_map, personalization_map, rank_map, convergence, damping, num_vertices(g)); + } - template < + template < typename Graph, typename PersonalizationMap, typename RankMap > - inline void personalized_page_rank( - const Graph& g, - PersonalizationMap personalization_map, - RankMap rank_map, - typename property_traits< RankMap >::value_type damping = 0.85) - { - personalized_page_rank(g, asymmetric_normalization(g), personalization_map, rank_map); - } - + inline vertex_map_convergence personalized_page_rank( + const Graph& g, + PersonalizationMap personalization_map, + RankMap rank_map, + typename property_traits< RankMap >::value_type damping = 0.85) + { + return personalized_page_rank(g, asymmetric_normalization(g), personalization_map, rank_map); } + +} } // end namespace boost::graph #endif // BOOST_GRAPH_PERSONALIZED_PAGE_RANK_HPP From f2eb07c60c115688748ef50506ab827b67f198b9 Mon Sep 17 00:00:00 2001 From: maniospas Date: Sun, 7 Jun 2026 23:15:35 +0300 Subject: [PATCH 5/7] added tests --- test/Jamfile.v2 | 1 + test/personalized_pagerank_test.cpp | 68 +++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 test/personalized_pagerank_test.cpp diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index 11cfbdaae..61f87d0e8 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -167,6 +167,7 @@ alias graph_test_regular : [ run delete_edge.cpp ] [ run johnson-test.cpp ] [ run lvalue_pmap.cpp ] + [ run personalized_pagerank_test.cpp ] ; alias graph_test_with_filesystem : : diff --git a/test/personalized_pagerank_test.cpp b/test/personalized_pagerank_test.cpp new file mode 100644 index 000000000..1fcc501fd --- /dev/null +++ b/test/personalized_pagerank_test.cpp @@ -0,0 +1,68 @@ + +#include +#include +#include +#include +#include +#include + +int main(int, char*[]) { + using namespace boost; + // deliberately hard (slow-converging) graph + using Graph = adjacency_list; + std::vector> edges = { + {0,1},{1,0},{1,2},{2,1},{2,3},{3,2}, + {4,5},{5,4},{5,6},{6,5},{6,7},{7,6},{7,8},{8,7},{8,9},{9,8},{9,10},{10,9}, + {0,3},{3,0},{1,3},{3,1},{1,4},{4,1}, + {4,6},{6,4},{6,9},{9,6},{6,8},{8,6},{7,9},{9,7},{8,10},{10,8}, + {11,10},{10,11},{10,12},{12,10} + }; + Graph g(edges.begin(), edges.end(), 13); + + std::vector ranks(num_vertices(g)); + auto rank_map = make_iterator_property_map(ranks.begin(), get(vertex_index, g)); + std::vector personalization(num_vertices(g)); + auto personalization_map = make_iterator_property_map(personalization.begin(), get(vertex_index, g)); + personalization[0] = 1; + personalization[1] = 1; + personalization[2] = 1; + personalization[3] = 1; + + std::size_t max_iters(100); // Convergence is so bad in this graph that it needs such a high cap. + auto weight = spectral_weights(g); + auto convergence1 = graph::vertex_map_convergence(max_iters, 1.E-9); + convergence1 = graph::personalized_page_rank(g, weight, personalization_map, rank_map, convergence1); + + std::cout << "ended after "<0); + BOOST_ASSERT(convergence1.has_converged()); + + // COMPUTING A HIGH-PASS VERSION BY PASSING NEGATIVE DAMPING (resulting values can be negative) + // This is not completely correct yet for computing graph gradients, because it needs to eskew normalization, + // so we need to be able to customize said normalization. + // Damping should generaly be in the range [-1,1]. + auto renorm_weight = renormalized_weights(g); + auto convergence2 = graph::vertex_map_convergence(max_iters, 1.E-9); + convergence2 = graph::personalized_page_rank(g, renorm_weight, personalization_map, rank_map, convergence2, -0.8); + std::cout << "ended after "<ranks[1]+0.1); + BOOST_ASSERT(ranks[8]==ranks[9]); + BOOST_ASSERT(ranks[11]==ranks[12]); + BOOST_ASSERT(ranks[11]<0); + BOOST_ASSERT(convergence1.has_converged()); +} From 91ed5d5604793eb539aaad309b550a57f57b7541 Mon Sep 17 00:00:00 2001 From: maniospas Date: Sun, 7 Jun 2026 23:42:50 +0300 Subject: [PATCH 6/7] moved to newer edge iterator --- include/boost/graph/personalized_page_rank.hpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/include/boost/graph/personalized_page_rank.hpp b/include/boost/graph/personalized_page_rank.hpp index cc2b71e12..759623f54 100644 --- a/include/boost/graph/personalized_page_rank.hpp +++ b/include/boost/graph/personalized_page_rank.hpp @@ -185,7 +185,7 @@ namespace graph } l1_norm += l1_accumulated_norm; } - BGL_FORALL_VERTICES_T(v, g, Graph) put(to_rank, v, get(to_rank, v)/l1_norm); + for (auto v : boost::make_iterator_range(vertices(g))) put(to_rank, v, get(to_rank, v)/l1_norm); } template < @@ -194,7 +194,7 @@ namespace graph typename PersonalizationMap, typename RankMap, typename RankMap2 > - void page_rank_step( + void personalized_page_rank_step( const Graph& g, WeightMap weight_map, PersonalizationMap personalization_map, @@ -205,15 +205,16 @@ namespace graph { typedef typename property_traits< RankMap >::value_type damping_type; damping_type l1_norm(0); // Computing the norm simultaneously avoids an extra summing iteration. - BGL_FORALL_VERTICES_T(v, g, Graph) + for (auto v : boost::make_iterator_range(vertices(g))) { typename property_traits< RankMap >::value_type rank(0); - BGL_FORALL_INEDGES_T(v, e, g, Graph) rank += get(from_rank, source(e, g))*get(weight_map, e);//get(from_rank, source(e, g)) / out_degree(source(e, g), g); + for (auto e : boost::make_iterator_range(in_edges(v, g))) + rank += get(from_rank, source(e, g))*get(weight_map, e); auto v_score = (damping_type(1) - damping) * get(personalization_map, v) + damping * rank; put(to_rank, v, v_score); l1_norm += v_score; } - BGL_FORALL_VERTICES_T(v, g, Graph) put(to_rank, v, get(to_rank, v)/l1_norm); + for (auto v : boost::make_iterator_range(vertices(g))) put(to_rank, v, get(to_rank, v)/l1_norm); } } // end namespace detail @@ -238,12 +239,12 @@ namespace graph typedef typename property_traits< PersonalizationMap >::value_type rank_type; rank_type personalization_norm(0); - BGL_FORALL_VERTICES_T(v, g, Graph) personalization_norm += get(personalization_map, v); + for (auto v : boost::make_iterator_range(vertices(g))) personalization_norm += get(personalization_map, v); // TBD: This implementation couples iterators when possible under reduced L1 cache invalidation assumptions, // but this is not necessarily the case because we may be grabbing 2x memory lanes each time to write there. // Should investigate which pattern is faster. - BGL_FORALL_VERTICES_T(v, g, Graph) + for (auto v : boost::make_iterator_range(vertices(g))) { rank_type value = get(personalization_map, v)/personalization_norm; put(personalization_map, v, value); From 79f4c84e2916223e97eee4b436572eb7a0bf1f86 Mon Sep 17 00:00:00 2001 From: maniospas Date: Mon, 8 Jun 2026 23:27:11 +0300 Subject: [PATCH 7/7] leaner version of personalized pagerank --- .../boost/graph/personalized_page_rank.hpp | 220 ++++-------------- test/personalized_pagerank_test.cpp | 23 +- 2 files changed, 62 insertions(+), 181 deletions(-) diff --git a/include/boost/graph/personalized_page_rank.hpp b/include/boost/graph/personalized_page_rank.hpp index 759623f54..dfc5fea0d 100644 --- a/include/boost/graph/personalized_page_rank.hpp +++ b/include/boost/graph/personalized_page_rank.hpp @@ -22,125 +22,23 @@ namespace boost { namespace graph { - namespace personalized_page_rank_detail - { - template < typename Graph> - std::size_t in_or_out_degree( - typename boost::graph_traits::vertex_descriptor v, - const Graph& g, - bidirectional_graph_tag) - { - return in_degree(v, g); - } - template < typename Graph> - std::size_t in_or_out_degree( - typename boost::graph_traits::vertex_descriptor v, - const Graph& g, - incidence_graph_tag) - { - return out_degree(v, g); - } - } - - template - boost::function_property_map< - std::function::edge_descriptor)>, - typename boost::graph_traits::edge_descriptor> - markovian_weights(const Graph& g) - { - using Edge = typename boost::graph_traits::edge_descriptor; - using Func = std::function; - Func f = [&g](Edge e) - { - auto u = source(e, g); - std::size_t denom; - denom = out_degree(u, g) ; - return 1.0 / denom; - }; - return make_function_property_map(f); - } - - template - boost::function_property_map< - std::function::edge_descriptor)>, - typename boost::graph_traits::edge_descriptor> - spectral_weights(const Graph& g) - { - using Edge = typename boost::graph_traits::edge_descriptor; - using Func = std::function; - Func f = [&g](Edge e) - { - typedef typename boost::graph_traits::traversal_category category; - auto u = source(e, g); - auto v = target(e, g); - std::size_t denom; - denom = out_degree(u, g) * personalized_page_rank_detail::in_or_out_degree(v, g, category()); - return 1.0 / std::sqrt(denom); - }; - return make_function_property_map(f); - } - - template - boost::function_property_map< - std::function::edge_descriptor)>, - typename boost::graph_traits::edge_descriptor> - renormalized_weights(const Graph& g) + struct rank_convergence { - using Edge = typename boost::graph_traits::edge_descriptor; - using Func = std::function; - Func f = [&g](Edge e) - { - typedef typename boost::graph_traits::traversal_category category; - auto u = source(e, g); - auto v = target(e, g); - std::size_t denom; - denom = (1+out_degree(u, g)) * (1+personalized_page_rank_detail::in_or_out_degree(v, g, category())); - return 1.0 / std::sqrt(denom); - }; - return make_function_property_map(f); - } - - struct vertex_map_convergence - { - explicit vertex_map_convergence(std::size_t n, double tol=0) : n(n), tol(tol) {} // allowing tolerance for early stopping - + explicit rank_convergence(std::size_t iters, double tol=0) : iters(iters), tol(tol) {} // allowing tolerance for early stopping template < typename RankMap, typename RankMap2, typename Graph > - bool operator()( - const RankMap& current, - const RankMap2& previous, - const Graph& g) + bool operator()(const RankMap& current, const RankMap2& previous, const Graph& g) { - if ( n-- == 0 ) - { + if (--iters == 0) return true; - } if (!tol) - { return false; - } - - typedef typename property_traits< RankMap >::value_type rank_type; + using rank_type = typename property_traits< RankMap >::value_type; rank_type sum_abs(0); for (auto v : boost::make_iterator_range(vertices(g))) - { - rank_type difference = get(current, v)-get(previous, v); - sum_abs += std::abs(difference); - } + sum_abs += std::abs(get(current, v) - get(previous, v)); return sum_abs*num_vertices(g)::value_type damping, incidence_graph_tag) { - typedef typename property_traits< RankMap >::value_type rank_type; + using rank_type = typename property_traits< RankMap >::value_type; rank_type l1_norm(0); // Computing the norm simultaneously avoids an extra summing iteration. - // Initialize the constant part of maps + // Initialize the constant part of maps. for (auto v : boost::make_iterator_range(vertices(g))) { auto v_constant = rank_type(1 - damping) * get(personalization_map, v); put(to_rank, v, v_constant); l1_norm += v_constant; } - + + // Maintenance comment: for (auto u : boost::make_iterator_range(vertices(g))) { rank_type u_rank_factor = damping * get(from_rank, u); @@ -185,7 +84,11 @@ namespace graph } l1_norm += l1_accumulated_norm; } - for (auto v : boost::make_iterator_range(vertices(g))) put(to_rank, v, get(to_rank, v)/l1_norm); + // If there are negative edge weights, or if negative damping is used, l1_norm could be zero or near-zero. + // Division in those cases is conceptually correct for floating point weights, and actually expected behavior. + // That said, such edge cases are impossible to arise for all typical algorithm uses. + for (auto v : boost::make_iterator_range(vertices(g))) + put(to_rank, v, get(to_rank, v)/l1_norm); } template < @@ -203,20 +106,22 @@ namespace graph typename property_traits< RankMap >::value_type damping, bidirectional_graph_tag) { - typedef typename property_traits< RankMap >::value_type damping_type; + using damping_type = typename property_traits< RankMap >::value_type; damping_type l1_norm(0); // Computing the norm simultaneously avoids an extra summing iteration. for (auto v : boost::make_iterator_range(vertices(g))) { - typename property_traits< RankMap >::value_type rank(0); + damping_type rank(0); for (auto e : boost::make_iterator_range(in_edges(v, g))) rank += get(from_rank, source(e, g))*get(weight_map, e); auto v_score = (damping_type(1) - damping) * get(personalization_map, v) + damping * rank; put(to_rank, v, v_score); l1_norm += v_score; } - for (auto v : boost::make_iterator_range(vertices(g))) put(to_rank, v, get(to_rank, v)/l1_norm); + // See above function for potential division by zero comments. + for (auto v : boost::make_iterator_range(vertices(g))) + put(to_rank, v, get(to_rank, v)/l1_norm); } - } // end namespace detail + } // end namespace personalized_page_rank_detail template < typename Graph, @@ -232,25 +137,24 @@ namespace graph RankMap rank_map, Done done, typename property_traits< RankMap >::value_type damping, - typename graph_traits< Graph >::vertices_size_type n, RankMap2 rank_map2 BOOST_GRAPH_ENABLE_IF_MODELS_PARM(Graph, vertex_list_graph_tag)) { - typedef typename property_traits< PersonalizationMap >::value_type rank_type; - + using rank_type = typename property_traits< PersonalizationMap >::value_type; rank_type personalization_norm(0); - for (auto v : boost::make_iterator_range(vertices(g))) personalization_norm += get(personalization_map, v); + for (auto v : boost::make_iterator_range(vertices(g))) + personalization_norm += get(personalization_map, v); // TBD: This implementation couples iterators when possible under reduced L1 cache invalidation assumptions, // but this is not necessarily the case because we may be grabbing 2x memory lanes each time to write there. - // Should investigate which pattern is faster. + // Could investigate which pattern is faster in the future. for (auto v : boost::make_iterator_range(vertices(g))) { rank_type value = get(personalization_map, v)/personalization_norm; put(personalization_map, v, value); put(rank_map, v, value); } - + bool to_map_2 = true; do { @@ -260,10 +164,12 @@ namespace graph else personalized_page_rank_detail::personalized_page_rank_step(g, weight_map, personalization_map, rank_map2, rank_map, damping, category()); to_map_2 = !to_map_2; - } while ((to_map_2 && !done(rank_map, rank_map2, g)) || (!to_map_2 && !done(rank_map2, rank_map, g))); + } + while ((to_map_2 && !done(rank_map, rank_map2, g)) || (!to_map_2 && !done(rank_map2, rank_map, g))); // Done may not be symmetric. // Now multiply the result with personalization_norm to restore the order of magnitude and store it in rank_map. - // Also restore the original personalization_map's magnitude for reuse (this is lossy up to numerical tolerance but leaner).' + // Also restore the original personalization_map's magnitude for reuse (this is lossy up to numerical tolerance + // but leaner than making a copy). if (!to_map_2) { for (auto v : boost::make_iterator_range(vertices(g))) @@ -295,61 +201,33 @@ namespace graph PersonalizationMap personalization_map, RankMap rank_map, Done done, - typename property_traits< RankMap >::value_type damping, - typename graph_traits< Graph >::vertices_size_type n) + typename property_traits< RankMap >::value_type damping) { - typedef typename property_traits< RankMap >::value_type rank_type; - + using rank_type = typename property_traits< RankMap >::value_type; std::vector< rank_type > ranks2(num_vertices(g)); - return personalized_page_rank(g, weight_map, personalization_map, rank_map, done, damping, n, + return personalized_page_rank(g, weight_map, personalization_map, rank_map, done, damping, make_iterator_property_map(ranks2.begin(), get(vertex_index, g))); } - template < - typename Graph, - typename WeightMap, - typename PersonalizationMap, - typename RankMap, - typename Done > - inline Done personalized_page_rank( - const Graph& g, - WeightMap weight_map, - PersonalizationMap personalization_map, - RankMap rank_map, - Done done, - typename property_traits< RankMap >::value_type damping = 0.85) - { - return personalized_page_rank(g, weight_map, personalization_map, rank_map, done, damping, num_vertices(g)); - } - - template < - typename Graph, - typename WeightMap, - typename PersonalizationMap, - typename RankMap > - inline vertex_map_convergence personalized_page_rank( + template < typename Graph, typename PersonalizationMap, typename RankMap > + rank_convergence personalized_page_rank( const Graph& g, - WeightMap weight_map, PersonalizationMap personalization_map, RankMap rank_map, - typename property_traits< RankMap >::value_type damping = 0.85) + typename property_traits< RankMap >::value_type damping=0.85) { - std::size_t n_iters(1.0/(1-damping)+0.5); // accounts for "most" random walks - auto convergence = vertex_map_convergence(n_iters); - return personalized_page_rank(g, weight_map, personalization_map, rank_map, convergence, damping, num_vertices(g)); - } - - template < - typename Graph, - typename PersonalizationMap, - typename RankMap > - inline vertex_map_convergence personalized_page_rank( - const Graph& g, - PersonalizationMap personalization_map, - RankMap rank_map, - typename property_traits< RankMap >::value_type damping = 0.85) - { - return personalized_page_rank(g, asymmetric_normalization(g), personalization_map, rank_map); + // This is the most traditional personalized PageRank implementation, with minimized signature. + using Edge = graph_traits::edge_descriptor; + using rank_type = typename property_traits< RankMap >::value_type; + std::vector< rank_type > ranks2(num_vertices(g)); + auto markovian_weights = make_function_property_map([&g](Edge e){ return 1.0 / out_degree(source(e, g), g); }); + return personalized_page_rank(g, + markovian_weights, + personalization_map, + rank_map, + rank_convergence(100, 1.E-9), + damping, + make_iterator_property_map(ranks2.begin(), get(vertex_index, g))); } } diff --git a/test/personalized_pagerank_test.cpp b/test/personalized_pagerank_test.cpp index 1fcc501fd..697aa8a5d 100644 --- a/test/personalized_pagerank_test.cpp +++ b/test/personalized_pagerank_test.cpp @@ -18,7 +18,7 @@ int main(int, char*[]) { {11,10},{10,11},{10,12},{12,10} }; Graph g(edges.begin(), edges.end(), 13); - + std::vector ranks(num_vertices(g)); auto rank_map = make_iterator_property_map(ranks.begin(), get(vertex_index, g)); std::vector personalization(num_vertices(g)); @@ -29,11 +29,13 @@ int main(int, char*[]) { personalization[3] = 1; std::size_t max_iters(100); // Convergence is so bad in this graph that it needs such a high cap. - auto weight = spectral_weights(g); - auto convergence1 = graph::vertex_map_convergence(max_iters, 1.E-9); - convergence1 = graph::personalized_page_rank(g, weight, personalization_map, rank_map, convergence1); + using Edge = graph_traits::edge_descriptor; + auto weight = make_function_property_map( + [&g](Edge e){ return 1.0 / std::sqrt(double(out_degree(source(e, g), g) * out_degree(target(e, g), g))); }); + auto convergence1 = graph::rank_convergence(max_iters, 1.E-9); + convergence1 = graph::personalized_page_rank(g, weight, personalization_map, rank_map, convergence1, 0.9); - std::cout << "ended after "<0); - BOOST_ASSERT(convergence1.has_converged()); + BOOST_ASSERT(convergence1.iters( + [&g](Edge e){ return 1.0 / std::sqrt(double((1.0+out_degree(source(e, g), g)) * (1.0+out_degree(target(e, g), g)))); }); + auto convergence2 = graph::rank_convergence(max_iters, 1.E-9); convergence2 = graph::personalized_page_rank(g, renorm_weight, personalization_map, rank_map, convergence2, -0.8); - std::cout << "ended after "<