use std::cmp::{Ordering, Reverse};
use std::collections::{BTreeMap, BTreeSet, BinaryHeap, VecDeque};
use super::subgraph::Subgraph;
#[derive(Debug, Clone, Copy, PartialEq)]
struct OrdF64(f64);
impl Eq for OrdF64 {}
impl Ord for OrdF64 {
fn cmp(&self, other: &Self) -> Ordering {
self.0.total_cmp(&other.0)
}
}
impl PartialOrd for OrdF64 {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
pub fn dijkstra(graph: &Subgraph, start: &str) -> BTreeMap<String, f64> {
let mut dist = BTreeMap::new();
let mut heap = BinaryHeap::new();
if !graph.nodes.contains_key(start) {
return dist;
}
dist.insert(start.to_string(), 0.0);
heap.push(Reverse((OrdF64(0.0), start.to_string())));
while let Some(Reverse((OrdF64(d), node))) = heap.pop() {
if d > *dist.get(&node).unwrap_or(&f64::INFINITY) {
continue;
}
for edge in graph.out_edges(&node) {
let next = &edge.node;
let new_dist = d + edge.weight;
if new_dist < *dist.get(next).unwrap_or(&f64::INFINITY) {
dist.insert(next.clone(), new_dist);
heap.push(Reverse((OrdF64(new_dist), next.clone())));
}
}
}
dist
}
pub fn astar<F>(
graph: &Subgraph,
start: &str,
goal: &str,
heuristic: F,
) -> Option<(f64, Vec<String>)>
where
F: Fn(&str, &str) -> f64,
{
if !graph.nodes.contains_key(start) || !graph.nodes.contains_key(goal) {
return None;
}
let mut g_score: BTreeMap<String, f64> = BTreeMap::new();
let mut came_from: BTreeMap<String, String> = BTreeMap::new();
let mut heap = BinaryHeap::new();
g_score.insert(start.to_string(), 0.0);
heap.push(Reverse((OrdF64(heuristic(start, goal)), start.to_string())));
while let Some(Reverse((OrdF64(f_score), current))) = heap.pop() {
let current_g = g_score[¤t];
if current == goal {
return Some((current_g, reconstruct(&came_from, goal, graph.nodes.len())));
}
if f_score > current_g + heuristic(¤t, goal) {
continue;
}
for edge in graph.out_edges(¤t) {
let neighbor = &edge.node;
let tentative_g = current_g + edge.weight;
if tentative_g < *g_score.get(neighbor).unwrap_or(&f64::INFINITY) {
if neighbor.as_str() != start {
came_from.insert(neighbor.clone(), current.clone());
}
g_score.insert(neighbor.clone(), tentative_g);
let f = tentative_g + heuristic(neighbor, goal);
heap.push(Reverse((OrdF64(f), neighbor.clone())));
}
}
}
None
}
fn reconstruct(came_from: &BTreeMap<String, String>, goal: &str, limit: usize) -> Vec<String> {
let mut path = vec![goal.to_string()];
let mut curr = goal.to_string();
while let Some(prev) = came_from.get(&curr) {
if path.len() > limit {
break;
}
path.push(prev.clone());
curr = prev.clone();
}
path.reverse();
path
}
pub fn scc(graph: &Subgraph) -> Vec<Vec<String>> {
let mut visited = BTreeSet::new();
let mut order = Vec::new();
for node in graph.nodes.keys() {
if visited.contains(node) {
continue;
}
let mut stack = vec![(node.clone(), false)];
while let Some((curr, exhausted)) = stack.pop() {
if exhausted {
order.push(curr);
continue;
}
if visited.contains(&curr) {
continue;
}
visited.insert(curr.clone());
stack.push((curr.clone(), true));
for edge in graph.out_edges(&curr) {
if !visited.contains(&edge.node) {
stack.push((edge.node.clone(), false));
}
}
}
}
visited.clear();
let mut components = Vec::new();
for node in order.into_iter().rev() {
if visited.contains(&node) {
continue;
}
let mut comp = Vec::new();
let mut stack = vec![node];
while let Some(curr) = stack.pop() {
if visited.contains(&curr) {
continue;
}
visited.insert(curr.clone());
comp.push(curr.clone());
for edge in graph.in_edges(&curr) {
if !visited.contains(&edge.node) {
stack.push(edge.node.clone());
}
}
}
comp.sort();
components.push(comp);
}
components.sort();
components
}
pub fn k_core(graph: &Subgraph, k: usize) -> BTreeSet<String> {
let mut degree: BTreeMap<String, usize> = graph
.nodes
.keys()
.map(|n| (n.clone(), graph.degree(n)))
.collect();
let mut queue: VecDeque<String> = degree
.iter()
.filter(|(_, &d)| d < k)
.map(|(n, _)| n.clone())
.collect();
let mut removed = BTreeSet::new();
while let Some(node) = queue.pop_front() {
if removed.contains(&node) {
continue;
}
removed.insert(node.clone());
let neighbours = graph
.out_edges(&node)
.iter()
.chain(graph.in_edges(&node).iter());
for edge in neighbours {
if let Some(d) = degree.get_mut(&edge.node) {
*d -= 1;
if *d < k && !removed.contains(&edge.node) {
queue.push_back(edge.node.clone());
}
}
}
}
graph
.nodes
.keys()
.filter(|n| !removed.contains(*n))
.cloned()
.collect()
}
pub fn modularity(graph: &Subgraph, communities: &BTreeMap<String, usize>) -> f64 {
let m = graph.total_weight();
if m == 0.0 {
return 0.0;
}
let mut internal: BTreeMap<usize, f64> = BTreeMap::new();
let mut total_deg: BTreeMap<usize, f64> = BTreeMap::new();
for node in graph.nodes.keys() {
let Some(&c) = communities.get(node) else {
continue;
};
*total_deg.entry(c).or_insert(0.0) += graph.weighted_degree(node);
for edge in graph.out_edges(node) {
if communities.get(&edge.node) == Some(&c) {
*internal.entry(c).or_insert(0.0) += edge.weight;
}
}
}
total_deg
.iter()
.map(|(c, deg)| {
let inside = internal.get(c).copied().unwrap_or(0.0);
(inside / m) - (deg / (2.0 * m)).powi(2)
})
.sum()
}
const LOUVAIN_MAX_SWEEPS: usize = 100;
const LOUVAIN_MIN_GAIN: f64 = 1e-12;
pub fn louvain(graph: &Subgraph) -> BTreeMap<String, usize> {
let m = graph.total_weight();
let mut comm: BTreeMap<String, usize> = graph
.nodes
.keys()
.enumerate()
.map(|(i, n)| (n.clone(), i))
.collect();
if m == 0.0 {
return comm;
}
let mut sigma_tot: BTreeMap<usize, f64> = BTreeMap::new();
for node in graph.nodes.keys() {
*sigma_tot.entry(comm[node]).or_insert(0.0) += graph.weighted_degree(node);
}
for _ in 0..LOUVAIN_MAX_SWEEPS {
let mut moved = false;
for node in graph.nodes.keys() {
let curr_comm = comm[node];
let k_i = graph.weighted_degree(node);
*sigma_tot.get_mut(&curr_comm).unwrap() -= k_i;
let mut k_i_c: BTreeMap<usize, f64> = BTreeMap::new();
for edge in graph.out_edges(node).iter().chain(graph.in_edges(node)) {
if &edge.node == node {
continue; }
*k_i_c.entry(comm[&edge.node]).or_insert(0.0) += edge.weight;
}
let mut best_comm = curr_comm;
let mut best_gain = LOUVAIN_MIN_GAIN;
for (&c, k_i_in) in &k_i_c {
let tot = sigma_tot.get(&c).copied().unwrap_or(0.0);
let gain = (k_i_in / m) - (tot * k_i / (2.0 * m * m));
if gain > best_gain {
best_gain = gain;
best_comm = c;
}
}
*sigma_tot.entry(best_comm).or_insert(0.0) += k_i;
if best_comm != curr_comm {
comm.insert(node.clone(), best_comm);
moved = true;
}
}
if !moved {
break;
}
}
renumber(comm)
}
fn renumber(comm: BTreeMap<String, usize>) -> BTreeMap<String, usize> {
let mut dense: BTreeMap<usize, usize> = BTreeMap::new();
let mut next = 0;
comm.into_iter()
.map(|(node, c)| {
let id = *dense.entry(c).or_insert_with(|| {
let id = next;
next += 1;
id
});
(node, id)
})
.collect()
}