petgraph/algo/
page_rank.rs1use alloc::{vec, vec::Vec};
2
3use super::UnitMeasure;
4use crate::visit::{EdgeRef, IntoEdges, NodeCount, NodeIndexable};
5
6#[cfg(feature = "rayon")]
7use rayon::prelude::*;
8
9#[track_caller]
63pub fn page_rank<G, D>(graph: G, damping_factor: D, nb_iter: usize) -> Vec<D>
64where
65 G: NodeCount + IntoEdges + NodeIndexable,
66 D: UnitMeasure + Copy,
67{
68 let node_count = graph.node_count();
69 if node_count == 0 {
70 return vec![];
71 }
72 assert!(
73 D::zero() <= damping_factor && damping_factor <= D::one(),
74 "Damping factor should be between 0 et 1."
75 );
76 let nb = D::from_usize(node_count);
77 let mut ranks = vec![D::one() / nb; node_count];
78 let nodeix = |i| graph.from_index(i);
79 let out_degrees: Vec<D> = (0..node_count)
80 .map(|i| graph.edges(nodeix(i)).map(|_| D::one()).sum::<D>())
81 .collect();
82
83 for _ in 0..nb_iter {
84 let pi = (0..node_count)
85 .enumerate()
86 .map(|(v, _)| {
87 ranks
88 .iter()
89 .enumerate()
90 .map(|(w, r)| {
91 let mut w_out_edges = graph.edges(nodeix(w));
92 if w_out_edges.any(|e| e.target() == nodeix(v)) {
93 damping_factor * *r / out_degrees[w]
94 } else if out_degrees[w] == D::zero() {
95 damping_factor * *r / nb } else {
97 (D::one() - damping_factor) * *r / nb }
99 })
100 .sum::<D>()
101 })
102 .collect::<Vec<D>>();
103 let sum = pi.iter().copied().sum::<D>();
104 ranks = pi.iter().map(|r| *r / sum).collect::<Vec<D>>();
105 }
106 ranks
107}
108
109#[allow(dead_code)]
110fn out_edges_info<G, D>(graph: G, index_w: usize, index_v: usize) -> (D, bool)
111where
112 G: NodeCount + IntoEdges + NodeIndexable + core::marker::Sync,
113 D: UnitMeasure + Copy + core::marker::Send + core::marker::Sync,
114{
115 let node_w = graph.from_index(index_w);
116 let node_v = graph.from_index(index_v);
117 let mut out_edges = graph.edges(node_w);
118 let mut out_edge = out_edges.next();
119 let mut out_degree = D::zero();
120 let mut flag_points_to = false;
121 while let Some(edge) = out_edge {
122 out_degree = out_degree + D::one();
123 if edge.target() == node_v {
124 flag_points_to = true;
125 }
126 out_edge = out_edges.next();
127 }
128 (out_degree, flag_points_to)
129}
130#[cfg(feature = "rayon")]
134pub fn parallel_page_rank<G, D>(
135 graph: G,
136 damping_factor: D,
137 nb_iter: usize,
138 tol: Option<D>,
139) -> Vec<D>
140where
141 G: NodeCount + IntoEdges + NodeIndexable + core::marker::Sync,
142 D: UnitMeasure + Copy + core::marker::Send + core::marker::Sync,
143{
144 let node_count = graph.node_count();
145 if node_count == 0 {
146 return vec![];
147 }
148 assert!(
149 D::zero() <= damping_factor && damping_factor <= D::one(),
150 "Damping factor should be between 0 et 1."
151 );
152 let mut tolerance = D::default_tol();
153 if let Some(_tol) = tol {
154 tolerance = _tol;
155 }
156 let nb = D::from_usize(node_count);
157 let mut ranks: Vec<D> = (0..node_count)
158 .into_par_iter()
159 .map(|_| D::one() / nb)
160 .collect();
161 for _ in 0..nb_iter {
162 let pi = (0..node_count)
163 .into_par_iter()
164 .map(|v| {
165 ranks
166 .iter()
167 .enumerate()
168 .map(|(w, r)| {
169 let (out_deg, w_points_to_v) = out_edges_info(graph, w, v);
170 if w_points_to_v {
171 damping_factor * *r / out_deg
172 } else if out_deg == D::zero() {
173 damping_factor * *r / nb } else {
175 (D::one() - damping_factor) * *r / nb }
177 })
178 .sum::<D>()
179 })
180 .collect::<Vec<D>>();
181 let sum = pi.par_iter().map(|score| *score).sum::<D>();
182 let new_ranks = pi.par_iter().map(|r| *r / sum).collect::<Vec<D>>();
183 let squared_norm_2 = new_ranks
184 .par_iter()
185 .zip(&ranks)
186 .map(|(new, old)| (*new - *old) * (*new - *old))
187 .sum::<D>();
188 if squared_norm_2 <= tolerance {
189 return ranks;
190 } else {
191 ranks = new_ranks;
192 }
193 }
194 ranks
195}