1use crate::{prelude::*, DEFAULT_PARALLELISM};
2
3use atomic_float::AtomicF64;
4use fso_graph_builder::SharedMut;
5use log::info;
6use rayon::prelude::*;
7
8use std::sync::atomic::Ordering;
9use std::thread::available_parallelism;
10use std::time::Instant;
11
12const CHUNK_SIZE: usize = 16384;
13
14#[derive(Copy, Clone, Debug)]
15#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
16#[cfg_attr(feature = "clap", derive(clap::Args))]
17pub struct PageRankConfig {
18 #[cfg_attr(feature = "clap", clap(long, default_value_t = PageRankConfig::DEFAULT_MAX_ITERATIONS))]
20 pub max_iterations: usize,
21
22 #[cfg_attr(feature = "clap", clap(long, default_value_t = PageRankConfig::DEFAULT_TOLERANCE))]
25 pub tolerance: f64,
26
27 #[cfg_attr(feature = "clap", clap(long, default_value_t = PageRankConfig::DEFAULT_DAMPING_FACTOR))]
31 pub damping_factor: f32,
32}
33
34impl Default for PageRankConfig {
35 fn default() -> Self {
36 Self {
37 max_iterations: Self::DEFAULT_MAX_ITERATIONS,
38 tolerance: Self::DEFAULT_TOLERANCE,
39 damping_factor: Self::DEFAULT_DAMPING_FACTOR,
40 }
41 }
42}
43
44impl PageRankConfig {
45 pub const DEFAULT_MAX_ITERATIONS: usize = 20;
46 pub const DEFAULT_TOLERANCE: f64 = 1E-4;
47 pub const DEFAULT_DAMPING_FACTOR: f32 = 0.85;
48
49 pub fn new(max_iterations: usize, tolerance: f64, damping_factor: f32) -> Self {
50 Self {
51 max_iterations,
52 tolerance,
53 damping_factor,
54 }
55 }
56}
57
58pub fn page_rank<NI, G>(graph: &G, config: PageRankConfig) -> (Vec<f32>, usize, f64)
59where
60 NI: Idx,
61 G: Graph<NI> + DirectedDegrees<NI> + DirectedNeighbors<NI> + Sync,
62{
63 let PageRankConfig {
64 max_iterations,
65 tolerance,
66 damping_factor,
67 } = config;
68
69 let node_count = graph.node_count().index();
70 let init_score = 1_f32 / node_count as f32;
71 let base_score = (1.0_f32 - damping_factor) / node_count as f32;
72
73 let mut out_scores = Vec::with_capacity(node_count);
74
75 (0..node_count)
76 .into_par_iter()
77 .map(NI::new)
78 .map(|node| init_score / graph.out_degree(node).index() as f32)
79 .collect_into_vec(&mut out_scores);
80
81 let mut scores = vec![init_score; node_count];
82
83 let scores_ptr = SharedMut::new(scores.as_mut_ptr());
84 let out_scores_ptr = SharedMut::new(out_scores.as_mut_ptr());
85
86 let mut iteration = 0;
87
88 loop {
89 let start = Instant::now();
90 let error = page_rank_iteration(
91 graph,
92 base_score,
93 damping_factor,
94 &out_scores_ptr,
95 &scores_ptr,
96 );
97
98 info!(
99 "Finished iteration {} with an error of {:.6} in {:?}",
100 iteration,
101 error,
102 start.elapsed()
103 );
104
105 iteration += 1;
106
107 if error < tolerance || iteration == max_iterations {
108 return (scores, iteration, error);
109 }
110 }
111}
112
113fn page_rank_iteration<NI, G>(
114 graph: &G,
115 base_score: f32,
116 damping_factor: f32,
117 out_scores: &SharedMut<f32>,
118 scores: &SharedMut<f32>,
119) -> f64
120where
121 NI: Idx,
122 G: Graph<NI> + DirectedDegrees<NI> + DirectedNeighbors<NI> + Sync,
123{
124 let next_chunk = Atomic::new(NI::zero());
125 let total_error = AtomicF64::new(0_f64);
126
127 std::thread::scope(|s| {
128 let num_threads = available_parallelism().map_or(DEFAULT_PARALLELISM, |p| p.get());
129
130 for _ in 0..num_threads {
131 s.spawn(|| {
132 let mut error = 0_f64;
133
134 loop {
135 let start = NI::fetch_add(&next_chunk, NI::new(CHUNK_SIZE), Ordering::AcqRel);
136 if start >= graph.node_count() {
137 break;
138 }
139
140 let end = (start + NI::new(CHUNK_SIZE)).min(graph.node_count());
141
142 for u in start.range(end) {
143 let incoming_total = graph
144 .in_neighbors(u)
145 .map(|v| unsafe { out_scores.add(v.index()).read() })
146 .sum::<f32>();
147
148 let old_score = unsafe { scores.add(u.index()).read() };
149 let new_score = base_score + damping_factor * incoming_total;
150
151 unsafe { scores.add(u.index()).write(new_score) };
152 let diff = (new_score - old_score) as f64;
153 error += f64::abs(diff);
154
155 unsafe {
156 out_scores
157 .add(u.index())
158 .write(new_score / graph.out_degree(u).index() as f32)
159 }
160 }
161 }
162 total_error.fetch_add(error, Ordering::SeqCst);
163 });
164 }
165 });
166
167 total_error.load(Ordering::SeqCst)
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173 use crate::prelude::{CsrLayout, GraphBuilder};
174
175 #[test]
176 fn test_pr_two_components() {
177 let gdl = "(a)-->()-->()<--(a),(b)-->()-->()<--(b)";
178
179 let graph: DirectedCsrGraph<usize> = GraphBuilder::new()
180 .csr_layout(CsrLayout::Sorted)
181 .gdl_str::<usize, _>(gdl)
182 .build()
183 .unwrap();
184
185 let (scores, _, _) = page_rank(&graph, PageRankConfig::default());
186
187 let expected: Vec<f32> = vec![
188 0.024999997,
189 0.035624996,
190 0.06590624,
191 0.024999997,
192 0.035624996,
193 0.06590624,
194 ];
195
196 assert_eq!(scores, expected);
197 }
198}