use petgraph::{Direction::Outgoing, visit::IntoNeighborsDirected};
use std::hash::Hash;
use hashbrown::HashMap;
use rand::prelude::*;
use rand::rngs::SysRng;
use rand_pcg::Pcg64;
pub fn generate_random_path<G>(
graph: G,
source: G::NodeId,
length: usize,
seed: Option<u64>,
) -> Vec<G::NodeId>
where
G: IntoNeighborsDirected,
G::NodeId: Eq + Hash,
{
let mut rng: Pcg64 = match seed {
Some(seed) => Pcg64::seed_from_u64(seed),
None => Pcg64::try_from_rng(&mut SysRng).unwrap(),
};
let mut degrees: HashMap<G::NodeId, usize> = HashMap::new();
let mut get_degree_lazy = |u: G::NodeId| {
*degrees
.entry(u)
.or_insert_with(|| graph.neighbors_directed(u, Outgoing).count())
};
let mut path = Vec::with_capacity(length + 1);
let mut current_node = source;
path.push(source);
for _ in 0..length {
let degree = get_degree_lazy(current_node);
if degree == 0 {
return path;
}
let idx = rng.random_range(..degree);
let neighbor = graph
.neighbors_directed(current_node, Outgoing)
.nth(idx)
.unwrap();
path.push(neighbor);
current_node = neighbor;
}
path
}
#[cfg(test)]
mod tests {
use crate::traversal::generate_random_path;
use hashbrown::HashMap;
use petgraph::graph::{DiGraph, UnGraph};
#[test]
fn test_degree_zero_shorter_path() {
let mut graph: DiGraph<(), ()> = DiGraph::with_capacity(2, 1);
let a = graph.add_node(());
let b = graph.add_node(());
graph.add_edge(a, b, ());
assert_eq!(generate_random_path(&graph, a, 10, None), vec![a, b]);
}
#[test]
fn test_alternating_path_digraph() {
let mut graph: DiGraph<(), ()> = DiGraph::with_capacity(2, 1);
let a = graph.add_node(());
let b = graph.add_node(());
graph.add_edge(a, b, ());
graph.add_edge(b, a, ());
assert!(generate_random_path(&graph, a, 3, None) == vec![a, b, a, b]);
}
#[test]
fn test_alternating_path_graph() {
let mut graph: UnGraph<(), ()> = UnGraph::with_capacity(2, 1);
let a = graph.add_node(());
let b = graph.add_node(());
graph.add_edge(a, b, ());
assert!(generate_random_path(&graph, a, 3, None) == vec![a, b, a, b]);
}
#[test]
fn test_path_visit_frequency() {
let mut graph: UnGraph<(), ()> = UnGraph::with_capacity(7, 8);
let a = graph.add_node(());
let b = graph.add_node(());
let c = graph.add_node(());
let d = graph.add_node(());
let e = graph.add_node(());
let f = graph.add_node(());
let g = graph.add_node(());
graph.extend_with_edges([(a, b), (b, c), (c, d), (e, f), (c, e), (c, f), (f, g)]);
let path_length = 5_000;
let mut frequencies = generate_random_path(&graph, a, path_length, Some(5))
.iter()
.copied()
.fold(HashMap::new(), |mut map, val| {
map.entry(val).and_modify(|frq| *frq += 1_f64).or_insert(1.);
map
});
for (_, k) in frequencies.iter_mut() {
*k /= path_length as f64 + 1.;
}
let tol = 1e-2;
assert!((frequencies.get(&a).unwrap() - 1. / 14.).abs() < tol);
assert!((frequencies.get(&b).unwrap() - 2. / 14.).abs() < tol);
assert!((frequencies.get(&c).unwrap() - 4. / 14.).abs() < tol);
assert!((frequencies.get(&d).unwrap() - 1. / 14.).abs() < tol);
assert!((frequencies.get(&e).unwrap() - 2. / 14.).abs() < tol);
assert!((frequencies.get(&f).unwrap() - 3. / 14.).abs() < tol);
assert!((frequencies.get(&g).unwrap() - 1. / 14.).abs() < tol);
}
}