1 use crate::visit::{EdgeRef, IntoEdges, NodeCount, NodeIndexable};
2
3 #[cfg(feature = "rayon")]
4 use rayon::prelude::*;
5
6 use super::UnitMeasure;
7 /// \[Generic\] Page Rank algorithm.
8 ///
9 /// Computes the ranks of every node in a graph using the [Page Rank algorithm][pr].
10 ///
11 /// Returns a `Vec` container mapping each node index to its rank.
12 ///
13 /// # Panics
14 /// The damping factor should be a number of type `f32` or `f64` between 0 and 1 (0 and 1 included). Otherwise, it panics.
15 ///
16 /// # Complexity
17 /// Time complexity is **O(N|V|²|E|)**.
18 /// Space complexity is **O(|V| + |E|)**
19 /// where **N** is the number of iterations, **|V|** the number of vertices (i.e nodes) and **|E|** the number of edges.
20 ///
21 /// [pr]: https://en.wikipedia.org/wiki/PageRank
22 ///
23 /// # Example
24 /// ```rust
25 /// use petgraph::Graph;
26 /// use petgraph::algo::page_rank;
27 /// let mut g: Graph<(), usize> = Graph::new();
28 /// assert_eq!(page_rank(&g, 0.5_f64, 1), vec![]); // empty graphs have no node ranks.
29 /// let a = g.add_node(());
30 /// let b = g.add_node(());
31 /// let c = g.add_node(());
32 /// let d = g.add_node(());
33 /// let e = g.add_node(());
34 /// g.extend_with_edges(&[(0, 1), (0, 3), (1, 2), (1, 3)]);
35 /// // With the following dot representation.
36 /// //digraph {
37 /// // 0 [ label = "()" ]
38 /// // 1 [ label = "()" ]
39 /// // 2 [ label = "()" ]
40 /// // 3 [ label = "()" ]
41 /// // 4 [ label = "()" ]
42 /// // 0 -> 1 [ label = "0.0" ]
43 /// // 0 -> 3 [ label = "0.0" ]
44 /// // 1 -> 2 [ label = "0.0" ]
45 /// // 1 -> 3 [ label = "0.0" ]
46 /// //}
47 /// let damping_factor = 0.7_f32;
48 /// let number_iterations = 10;
49 /// let output_ranks = page_rank(&g, damping_factor, number_iterations);
50 /// let expected_ranks = vec![0.14685437, 0.20267677, 0.22389607, 0.27971846, 0.14685437];
51 /// assert_eq!(expected_ranks, output_ranks);
52 /// ```
page_rank<G, D>(graph: G, damping_factor: D, nb_iter: usize) -> Vec<D> where G: NodeCount + IntoEdges + NodeIndexable, D: UnitMeasure + Copy,53 pub fn page_rank<G, D>(graph: G, damping_factor: D, nb_iter: usize) -> Vec<D>
54 where
55 G: NodeCount + IntoEdges + NodeIndexable,
56 D: UnitMeasure + Copy,
57 {
58 let node_count = graph.node_count();
59 if node_count == 0 {
60 return vec![];
61 }
62 assert!(
63 D::zero() <= damping_factor && damping_factor <= D::one(),
64 "Damping factor should be between 0 et 1."
65 );
66 let nb = D::from_usize(node_count);
67 let mut ranks = vec![D::one() / nb; node_count];
68 let nodeix = |i| graph.from_index(i);
69 let out_degrees: Vec<D> = (0..node_count)
70 .map(|i| graph.edges(nodeix(i)).map(|_| D::one()).sum::<D>())
71 .collect();
72
73 for _ in 0..nb_iter {
74 let pi = (0..node_count)
75 .enumerate()
76 .map(|(v, _)| {
77 ranks
78 .iter()
79 .enumerate()
80 .map(|(w, r)| {
81 let mut w_out_edges = graph.edges(nodeix(w));
82 if w_out_edges.any(|e| e.target() == nodeix(v)) {
83 damping_factor * *r / out_degrees[w]
84 } else if out_degrees[w] == D::zero() {
85 damping_factor * *r / nb // stochastic matrix condition
86 } else {
87 (D::one() - damping_factor) * *r / nb // random jumps
88 }
89 })
90 .sum::<D>()
91 })
92 .collect::<Vec<D>>();
93 let sum = pi.iter().copied().sum::<D>();
94 ranks = pi.iter().map(|r| *r / sum).collect::<Vec<D>>();
95 }
96 ranks
97 }
98
99 #[allow(dead_code)]
out_edges_info<G, D>(graph: G, index_w: usize, index_v: usize) -> (D, bool) where G: NodeCount + IntoEdges + NodeIndexable + std::marker::Sync, D: UnitMeasure + Copy + std::marker::Send + std::marker::Sync,100 fn out_edges_info<G, D>(graph: G, index_w: usize, index_v: usize) -> (D, bool)
101 where
102 G: NodeCount + IntoEdges + NodeIndexable + std::marker::Sync,
103 D: UnitMeasure + Copy + std::marker::Send + std::marker::Sync,
104 {
105 let node_w = graph.from_index(index_w);
106 let node_v = graph.from_index(index_v);
107 let mut out_edges = graph.edges(node_w);
108 let mut out_edge = out_edges.next();
109 let mut out_degree = D::zero();
110 let mut flag_points_to = false;
111 while let Some(edge) = out_edge {
112 out_degree = out_degree + D::one();
113 if edge.target() == node_v {
114 flag_points_to = true;
115 }
116 out_edge = out_edges.next();
117 }
118 (out_degree, flag_points_to)
119 }
120 /// \[Generic\] Parallel Page Rank algorithm.
121 ///
122 /// See [`page_rank`].
123 #[cfg(feature = "rayon")]
parallel_page_rank<G, D>( graph: G, damping_factor: D, nb_iter: usize, tol: Option<D>, ) -> Vec<D> where G: NodeCount + IntoEdges + NodeIndexable + std::marker::Sync, D: UnitMeasure + Copy + std::marker::Send + std::marker::Sync,124 pub fn parallel_page_rank<G, D>(
125 graph: G,
126 damping_factor: D,
127 nb_iter: usize,
128 tol: Option<D>,
129 ) -> Vec<D>
130 where
131 G: NodeCount + IntoEdges + NodeIndexable + std::marker::Sync,
132 D: UnitMeasure + Copy + std::marker::Send + std::marker::Sync,
133 {
134 let node_count = graph.node_count();
135 if node_count == 0 {
136 return vec![];
137 }
138 assert!(
139 D::zero() <= damping_factor && damping_factor <= D::one(),
140 "Damping factor should be between 0 et 1."
141 );
142 let mut tolerance = D::default_tol();
143 if let Some(_tol) = tol {
144 tolerance = _tol;
145 }
146 let nb = D::from_usize(node_count);
147 let mut ranks: Vec<D> = (0..node_count)
148 .into_par_iter()
149 .map(|i| D::one() / nb)
150 .collect();
151 for _ in 0..nb_iter {
152 let pi = (0..node_count)
153 .into_par_iter()
154 .map(|v| {
155 ranks
156 .iter()
157 .enumerate()
158 .map(|(w, r)| {
159 let (out_deg, w_points_to_v) = out_edges_info(graph, w, v);
160 if w_points_to_v {
161 damping_factor * *r / out_deg
162 } else if out_deg == D::zero() {
163 damping_factor * *r / nb // stochastic matrix condition
164 } else {
165 (D::one() - damping_factor) * *r / nb // random jumps
166 }
167 })
168 .sum::<D>()
169 })
170 .collect::<Vec<D>>();
171 let sum = pi.par_iter().map(|score| *score).sum::<D>();
172 let new_ranks = pi.par_iter().map(|r| *r / sum).collect::<Vec<D>>();
173 let squared_norm_2 = new_ranks
174 .par_iter()
175 .zip(&ranks)
176 .map(|(new, old)| (*new - *old) * (*new - *old))
177 .sum::<D>();
178 if squared_norm_2 <= tolerance {
179 return ranks;
180 } else {
181 ranks = new_ranks;
182 }
183 }
184 ranks
185 }
186