use alloc::{vec, vec::Vec};
use super::UnitMeasure;
use crate::visit::{EdgeRef, IntoEdges, NodeCount, NodeIndexable};
#[cfg(feature = "rayon")]
use rayon::prelude::*;
#[track_caller]
pub fn page_rank<G, D>(graph: G, damping_factor: D, nb_iter: usize) -> Vec<D>
where
G: NodeCount + IntoEdges + NodeIndexable,
D: UnitMeasure + Copy,
{
let node_count = graph.node_count();
if node_count == 0 {
return vec![];
}
assert!(
D::zero() <= damping_factor && damping_factor <= D::one(),
"Damping factor should be between 0 et 1."
);
let nb = D::from_usize(node_count);
let mut ranks = vec![D::one() / nb; node_count];
let nodeix = |i| graph.from_index(i);
let out_degrees: Vec<D> = (0..node_count)
.map(|i| graph.edges(nodeix(i)).map(|_| D::one()).sum::<D>())
.collect();
for _ in 0..nb_iter {
let pi = (0..node_count)
.enumerate()
.map(|(v, _)| {
ranks
.iter()
.enumerate()
.map(|(w, r)| {
let mut w_out_edges = graph.edges(nodeix(w));
if w_out_edges.any(|e| e.target() == nodeix(v)) {
damping_factor * *r / out_degrees[w]
} else if out_degrees[w] == D::zero() {
damping_factor * *r / nb } else {
(D::one() - damping_factor) * *r / nb }
})
.sum::<D>()
})
.collect::<Vec<D>>();
let sum = pi.iter().copied().sum::<D>();
ranks = pi.iter().map(|r| *r / sum).collect::<Vec<D>>();
}
ranks
}
#[allow(dead_code)]
fn out_edges_info<G, D>(graph: G, index_w: usize, index_v: usize) -> (D, bool)
where
G: NodeCount + IntoEdges + NodeIndexable + core::marker::Sync,
D: UnitMeasure + Copy + core::marker::Send + core::marker::Sync,
{
let node_w = graph.from_index(index_w);
let node_v = graph.from_index(index_v);
let mut out_edges = graph.edges(node_w);
let mut out_edge = out_edges.next();
let mut out_degree = D::zero();
let mut flag_points_to = false;
while let Some(edge) = out_edge {
out_degree = out_degree + D::one();
if edge.target() == node_v {
flag_points_to = true;
}
out_edge = out_edges.next();
}
(out_degree, flag_points_to)
}
#[cfg(feature = "rayon")]
pub fn parallel_page_rank<G, D>(
graph: G,
damping_factor: D,
nb_iter: usize,
tol: Option<D>,
) -> Vec<D>
where
G: NodeCount + IntoEdges + NodeIndexable + core::marker::Sync,
D: UnitMeasure + Copy + core::marker::Send + core::marker::Sync,
{
let node_count = graph.node_count();
if node_count == 0 {
return vec![];
}
assert!(
D::zero() <= damping_factor && damping_factor <= D::one(),
"Damping factor should be between 0 et 1."
);
let mut tolerance = D::default_tol();
if let Some(_tol) = tol {
tolerance = _tol;
}
let nb = D::from_usize(node_count);
let mut ranks: Vec<D> = (0..node_count)
.into_par_iter()
.map(|_| D::one() / nb)
.collect();
for _ in 0..nb_iter {
let pi = (0..node_count)
.into_par_iter()
.map(|v| {
ranks
.iter()
.enumerate()
.map(|(w, r)| {
let (out_deg, w_points_to_v) = out_edges_info(graph, w, v);
if w_points_to_v {
damping_factor * *r / out_deg
} else if out_deg == D::zero() {
damping_factor * *r / nb } else {
(D::one() - damping_factor) * *r / nb }
})
.sum::<D>()
})
.collect::<Vec<D>>();
let sum = pi.par_iter().map(|score| *score).sum::<D>();
let new_ranks = pi.par_iter().map(|r| *r / sum).collect::<Vec<D>>();
let squared_norm_2 = new_ranks
.par_iter()
.zip(&ranks)
.map(|(new, old)| (*new - *old) * (*new - *old))
.sum::<D>();
if squared_norm_2 <= tolerance {
return ranks;
} else {
ranks = new_ranks;
}
}
ranks
}