-
Notifications
You must be signed in to change notification settings - Fork 233
Personalized page rank #502
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
maniospas
wants to merge
11
commits into
boostorg:develop
Choose a base branch
from
maniospas:personalized_page_rank
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+308
−0
Draft
Changes from 9 commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
198616d
Merge pull request #377 from boostorg/develop
jeremy-murphy 54a9491
Merge pull request #417 from boostorg/develop
jeremy-murphy 1d06b64
Merge pull request #445 from boostorg/develop
jeremy-murphy a11ba98
first take on personalized page rank
maniospas da8bdcc
fully fleshed normalization schemes
maniospas 4e358c4
cpp14 compliance, no exceptions
maniospas 12ddd8f
moved personalized pagerank to the agreed upon api
maniospas f2eb07c
added tests
maniospas 91ed5d5
moved to newer edge iterator
maniospas 79f4c84
leaner version of personalized pagerank
maniospas 71124e1
added a missing typedef
maniospas File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,358 @@ | ||
| // 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_PERSONALIZED_PAGE_RANK_HPP | ||
| #define BOOST_GRAPH_PERSONALIZED_PAGE_RANK_HPP | ||
|
|
||
| #include <boost/property_map/property_map.hpp> | ||
| #include <boost/graph/graph_traits.hpp> | ||
| #include <boost/graph/properties.hpp> | ||
| #include <boost/graph/iteration_macros.hpp> | ||
| #include <boost/graph/overloading.hpp> | ||
| #include <boost/graph/detail/mpi_include.hpp> | ||
| #include <boost/property_map/function_property_map.hpp> | ||
| #include <vector> | ||
|
|
||
| namespace boost | ||
| { | ||
| namespace graph | ||
| { | ||
| namespace personalized_page_rank_detail | ||
| { | ||
| template < typename Graph> | ||
| std::size_t in_or_out_degree( | ||
| typename boost::graph_traits<Graph>::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<Graph>::vertex_descriptor v, | ||
| const Graph& g, | ||
| incidence_graph_tag) | ||
| { | ||
| return out_degree(v, g); | ||
| } | ||
| } | ||
|
|
||
| template <typename Graph> | ||
| boost::function_property_map< | ||
| std::function<double(typename boost::graph_traits<Graph>::edge_descriptor)>, | ||
| typename boost::graph_traits<Graph>::edge_descriptor> | ||
| markovian_weights(const Graph& g) | ||
| { | ||
| using Edge = typename boost::graph_traits<Graph>::edge_descriptor; | ||
| using Func = std::function<double(Edge)>; | ||
| 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<Edge, double>(f); | ||
| } | ||
|
|
||
| template <typename Graph> | ||
| boost::function_property_map< | ||
| std::function<double(typename boost::graph_traits<Graph>::edge_descriptor)>, | ||
| typename boost::graph_traits<Graph>::edge_descriptor> | ||
| spectral_weights(const Graph& g) | ||
| { | ||
| using Edge = typename boost::graph_traits<Graph>::edge_descriptor; | ||
| using Func = std::function<double(Edge)>; | ||
| Func f = [&g](Edge e) | ||
| { | ||
| typedef typename boost::graph_traits<Graph>::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<Edge, double>(f); | ||
| } | ||
|
|
||
| template <typename Graph> | ||
| boost::function_property_map< | ||
| std::function<double(typename boost::graph_traits<Graph>::edge_descriptor)>, | ||
| typename boost::graph_traits<Graph>::edge_descriptor> | ||
| renormalized_weights(const Graph& g) | ||
| { | ||
| using Edge = typename boost::graph_traits<Graph>::edge_descriptor; | ||
| using Func = std::function<double(Edge)>; | ||
| Func f = [&g](Edge e) | ||
| { | ||
| typedef typename boost::graph_traits<Graph>::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<Edge, double>(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& current, | ||
| const RankMap2& previous, | ||
| const Graph& g) | ||
| { | ||
| if ( n-- == 0 ) | ||
| { | ||
| return true; | ||
| } | ||
| if (!tol) | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| typedef typename property_traits< RankMap >::value_type rank_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); | ||
| } | ||
| return sum_abs*num_vertices(g)<tol; | ||
| } | ||
|
|
||
| std::size_t get_remaining_iters() const | ||
| { | ||
| return n; | ||
| } | ||
|
|
||
| bool has_converged() const | ||
| { | ||
| return n || !tol; | ||
| } | ||
|
|
||
| private: | ||
| std::size_t n; | ||
| double tol; | ||
| }; | ||
|
Becheler marked this conversation as resolved.
Outdated
|
||
|
|
||
| namespace personalized_page_rank_detail | ||
| { | ||
| template < | ||
| typename Graph, | ||
| typename WeightMap, | ||
| typename PersonalizationMap, | ||
| typename RankMap, | ||
| typename RankMap2 > | ||
| 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 | ||
| 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; | ||
| } | ||
|
|
||
| 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))) | ||
| { | ||
| 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; | ||
| } | ||
| for (auto v : boost::make_iterator_range(vertices(g))) put(to_rank, v, get(to_rank, v)/l1_norm); | ||
| } | ||
|
|
||
| template < | ||
| typename Graph, | ||
| typename WeightMap, | ||
| typename PersonalizationMap, | ||
| typename RankMap, | ||
| typename RankMap2 > | ||
| 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, | ||
| 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. | ||
| for (auto v : boost::make_iterator_range(vertices(g))) | ||
| { | ||
| typename property_traits< RankMap >::value_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); | ||
| } | ||
| } // end namespace detail | ||
|
|
||
| template < | ||
| typename Graph, | ||
| typename WeightMap, | ||
| typename PersonalizationMap, | ||
| typename RankMap, | ||
| typename Done, | ||
| typename RankMap2 > | ||
| 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; | ||
|
|
||
| rank_type personalization_norm(0); | ||
| 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. | ||
|
Becheler marked this conversation as resolved.
Outdated
|
||
| 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 | ||
| { | ||
| 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))) | ||
| { | ||
| put(rank_map, v, get(rank_map2, v)*personalization_norm); | ||
| put(personalization_map, v, get(personalization_map, v)*personalization_norm); | ||
| } | ||
| } | ||
| else | ||
| { | ||
| for (auto v : boost::make_iterator_range(vertices(g))) | ||
| { | ||
| put(rank_map, v, get(rank_map, v)*personalization_norm); | ||
| put(personalization_map, v, get(personalization_map, v)*personalization_norm); | ||
| } | ||
| } | ||
| return done; | ||
| } | ||
|
|
||
| template < | ||
| typename Graph, | ||
| typename WeightMap, | ||
| typename PersonalizationMap, | ||
| typename RankMap, | ||
| typename Done > | ||
| 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) | ||
|
Becheler marked this conversation as resolved.
Outdated
|
||
| { | ||
| typedef typename property_traits< RankMap >::value_type rank_type; | ||
|
Becheler marked this conversation as resolved.
Outdated
|
||
|
|
||
| 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 < | ||
| 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( | ||
| 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 | ||
|
Becheler marked this conversation as resolved.
Outdated
|
||
| 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); | ||
| } | ||
|
|
||
| } | ||
| } // end namespace boost::graph | ||
|
|
||
| #endif // BOOST_GRAPH_PERSONALIZED_PAGE_RANK_HPP | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.