use std::cmp::{Ordering, Reverse};
use std::collections::{BTreeMap, BTreeSet, BinaryHeap, VecDeque};
use super::dense::Dense;
use super::subgraph::Subgraph;
const CLOSURE: &str = "Subgraph closure invariant violated on entry: adjacency \
references a node that is not in `nodes`. Every algorithm \
here assumes closure and none re-checks it — see \
`Subgraph`'s type docs and `drop_dangling_adjacency`.";
#[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> {
debug_assert!(graph.is_closed(), "{CLOSURE}");
let g = Dense::of(graph);
let Some(source) = g.index_of(start) else {
return BTreeMap::new();
};
let mut dist = vec![f64::INFINITY; g.len()];
let mut heap = BinaryHeap::new();
dist[source] = 0.0;
heap.push(Reverse((OrdF64(0.0), source)));
while let Some(Reverse((OrdF64(d), node))) = heap.pop() {
if d > dist[node] {
continue;
}
for &(next, weight) in g.out(node) {
let next = next as usize;
let new_dist = d + weight;
if new_dist < dist[next] {
dist[next] = new_dist;
heap.push(Reverse((OrdF64(new_dist), next)));
}
}
}
g.label_finite(&dist)
}
pub fn astar<F>(
graph: &Subgraph,
start: &str,
goal: &str,
heuristic: F,
) -> Option<(f64, Vec<String>)>
where
F: Fn(&str, &str) -> f64,
{
debug_assert!(graph.is_closed(), "{CLOSURE}");
if !graph.contains_node(start) || !graph.contains_node(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.node_count())));
}
if f_score > current_g + heuristic(¤t, goal) {
continue;
}
for edge in graph.out_edges(¤t) {
let neighbor = edge.node(graph);
let tentative_g = current_g + edge.weight();
if tentative_g < *g_score.get(neighbor).unwrap_or(&f64::INFINITY) {
if neighbor != start {
came_from.insert(neighbor.to_string(), current.clone());
}
g_score.insert(neighbor.to_string(), tentative_g);
let f = tentative_g + heuristic(neighbor, goal);
heap.push(Reverse((OrdF64(f), neighbor.to_string())));
}
}
}
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>> {
debug_assert!(graph.is_closed(), "{CLOSURE}");
let g = Dense::of(graph);
let mut visited = vec![false; g.len()];
let mut order: Vec<usize> = Vec::with_capacity(g.len());
for node in 0..g.len() {
if visited[node] {
continue;
}
let mut stack = vec![(node, false)];
while let Some((curr, exhausted)) = stack.pop() {
if exhausted {
order.push(curr);
continue;
}
if visited[curr] {
continue;
}
visited[curr] = true;
stack.push((curr, true));
for &(next, _) in g.out(curr) {
if !visited[next as usize] {
stack.push((next as usize, false));
}
}
}
}
visited.iter_mut().for_each(|v| *v = false);
let mut components: Vec<Vec<usize>> = Vec::new();
for node in order.into_iter().rev() {
if visited[node] {
continue;
}
let mut comp = Vec::new();
let mut stack = vec![node];
while let Some(curr) = stack.pop() {
if visited[curr] {
continue;
}
visited[curr] = true;
comp.push(curr);
for &(prev, _) in g.inn(curr) {
if !visited[prev as usize] {
stack.push(prev as usize);
}
}
}
comp.sort_unstable();
components.push(comp);
}
components.sort_unstable();
components
.into_iter()
.map(|comp| comp.into_iter().map(|u| g.id(u).to_string()).collect())
.collect()
}
pub fn k_core(graph: &Subgraph, k: usize) -> BTreeSet<String> {
debug_assert!(graph.is_closed(), "{CLOSURE}");
let g = Dense::of(graph);
let mut degree: Vec<usize> = (0..g.len()).map(|u| g.degree(u)).collect();
let mut queue: VecDeque<usize> = (0..g.len()).filter(|&u| degree[u] < k).collect();
let mut removed = vec![false; g.len()];
while let Some(node) = queue.pop_front() {
if removed[node] {
continue;
}
removed[node] = true;
let neighbours = g.out(node).iter().chain(g.inn(node).iter());
for &(other, _) in neighbours {
let other = other as usize;
degree[other] -= 1;
if degree[other] < k && !removed[other] {
queue.push_back(other);
}
}
}
(0..g.len())
.filter(|&u| !removed[u])
.map(|u| g.id(u).to_string())
.collect()
}
pub fn modularity(graph: &Subgraph, communities: &BTreeMap<String, usize>) -> f64 {
let g = Dense::of(graph);
let m = g.total_weight();
if m == 0.0 {
return 0.0;
}
let comm: Vec<Option<usize>> = g
.ids()
.iter()
.map(|id| communities.get(*id).copied())
.collect();
let weighted = g.weighted_degrees();
let mut internal: BTreeMap<usize, f64> = BTreeMap::new();
let mut total_deg: BTreeMap<usize, f64> = BTreeMap::new();
for node in 0..g.len() {
let Some(c) = comm[node] else {
continue;
};
*total_deg.entry(c).or_insert(0.0) += weighted[node];
for &(other, weight) in g.out(node) {
if comm[other as usize] == Some(c) {
*internal.entry(c).or_insert(0.0) += 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> {
debug_assert!(graph.is_closed(), "{CLOSURE}");
let g = Dense::of(graph);
let m = g.total_weight();
let mut comm: Vec<usize> = (0..g.len()).collect();
if m == 0.0 {
return g.label(&comm);
}
let weighted = g.weighted_degrees();
let mut sigma_tot: BTreeMap<usize, f64> = BTreeMap::new();
for (node, &c) in comm.iter().enumerate() {
*sigma_tot.entry(c).or_insert(0.0) += weighted[node];
}
for _ in 0..LOUVAIN_MAX_SWEEPS {
let mut moved = false;
for node in 0..g.len() {
let curr_comm = comm[node];
let k_i = weighted[node];
*sigma_tot.get_mut(&curr_comm).unwrap() -= k_i;
let mut k_i_c: BTreeMap<usize, f64> = BTreeMap::new();
for &(other, weight) in g.out(node).iter().chain(g.inn(node)) {
let other = other as usize;
if other == node {
continue; }
*k_i_c.entry(comm[other]).or_insert(0.0) += 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[node] = best_comm;
moved = true;
}
}
if !moved {
break;
}
}
g.label(&renumber(&comm))
}
fn renumber(comm: &[usize]) -> Vec<usize> {
let mut dense: BTreeMap<usize, usize> = BTreeMap::new();
let mut next = 0;
comm.iter()
.map(|&c| {
*dense.entry(c).or_insert_with(|| {
let id = next;
next += 1;
id
})
})
.collect()
}