Skip to main content

oxiphysics_core/graph/
functions.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5#![allow(clippy::needless_range_loop, clippy::ptr_arg)]
6use std::cmp::Ordering;
7use std::collections::{BinaryHeap, VecDeque};
8
9use super::types::{AStarState, Graph, State, UnionFind};
10
11/// Returns nodes visited in BFS order starting from `start`.
12#[allow(dead_code)]
13pub fn bfs(graph: &Graph, start: usize) -> Vec<usize> {
14    let adj = graph.adjacency_list();
15    let mut visited = vec![false; graph.n_nodes];
16    let mut order = Vec::new();
17    let mut queue = VecDeque::new();
18    visited[start] = true;
19    queue.push_back(start);
20    while let Some(node) = queue.pop_front() {
21        order.push(node);
22        for &(nbr, _) in &adj[node] {
23            if !visited[nbr] {
24                visited[nbr] = true;
25                queue.push_back(nbr);
26            }
27        }
28    }
29    order
30}
31/// Returns nodes visited in DFS order starting from `start` (iterative).
32#[allow(dead_code)]
33pub fn dfs(graph: &Graph, start: usize) -> Vec<usize> {
34    let adj = graph.adjacency_list();
35    let mut visited = vec![false; graph.n_nodes];
36    let mut order = Vec::new();
37    let mut stack = vec![start];
38    while let Some(node) = stack.pop() {
39        if visited[node] {
40            continue;
41        }
42        visited[node] = true;
43        order.push(node);
44        for &(nbr, _) in adj[node].iter().rev() {
45            if !visited[nbr] {
46                stack.push(nbr);
47            }
48        }
49    }
50    order
51}
52/// Returns the connected components of the graph (treating all edges as undirected).
53///
54/// Uses union-find. Each component is a sorted list of node indices.
55#[allow(dead_code)]
56pub fn connected_components(graph: &Graph) -> Vec<Vec<usize>> {
57    let mut uf = UnionFind::new(graph.n_nodes);
58    for &(a, b, _) in &graph.edges {
59        uf.union(a, b);
60    }
61    let mut comp: std::collections::HashMap<usize, Vec<usize>> = std::collections::HashMap::new();
62    for i in 0..graph.n_nodes {
63        let root = uf.find(i);
64        comp.entry(root).or_default().push(i);
65    }
66    let mut result: Vec<Vec<usize>> = comp.into_values().collect();
67    for c in &mut result {
68        c.sort_unstable();
69    }
70    result.sort_unstable_by_key(|c| c[0]);
71    result
72}
73/// Returns `true` if the graph is connected (treating all edges as undirected).
74#[allow(dead_code)]
75pub fn is_connected(graph: &Graph) -> bool {
76    if graph.n_nodes == 0 {
77        return true;
78    }
79    connected_components(graph).len() == 1
80}
81/// Topological sort using Kahn's algorithm.
82///
83/// Returns `None` if the graph contains a cycle.
84#[allow(dead_code)]
85pub fn topological_sort(graph: &Graph) -> Option<Vec<usize>> {
86    let n = graph.n_nodes;
87    let mut in_degree = vec![0usize; n];
88    let adj = graph.adjacency_list();
89    for node in 0..n {
90        for &(nbr, _) in &adj[node] {
91            in_degree[nbr] += 1;
92        }
93    }
94    let mut queue: VecDeque<usize> = (0..n).filter(|&i| in_degree[i] == 0).collect();
95    let mut order = Vec::with_capacity(n);
96    while let Some(node) = queue.pop_front() {
97        order.push(node);
98        for &(nbr, _) in &adj[node] {
99            in_degree[nbr] -= 1;
100            if in_degree[nbr] == 0 {
101                queue.push_back(nbr);
102            }
103        }
104    }
105    if order.len() == n { Some(order) } else { None }
106}
107/// Dijkstra's shortest-path algorithm from `start`.
108///
109/// Returns a vector `dist` where `dist[i]` is the shortest distance from `start` to node `i`.
110/// Unreachable nodes have distance `f64::INFINITY`.
111///
112/// **Panics** if any edge weight is negative.
113#[allow(dead_code)]
114pub fn dijkstra(graph: &Graph, start: usize) -> Vec<f64> {
115    let adj = graph.adjacency_list();
116    let mut dist = vec![f64::INFINITY; graph.n_nodes];
117    dist[start] = 0.0;
118    let mut heap = BinaryHeap::new();
119    heap.push(State {
120        cost: 0.0,
121        node: start,
122    });
123    while let Some(State { cost, node }) = heap.pop() {
124        if cost > dist[node] {
125            continue;
126        }
127        for &(nbr, w) in &adj[node] {
128            let next = cost + w;
129            if next < dist[nbr] {
130                dist[nbr] = next;
131                heap.push(State {
132                    cost: next,
133                    node: nbr,
134                });
135            }
136        }
137    }
138    dist
139}
140/// Bellman-Ford shortest-path algorithm from `start`.
141///
142/// Returns `None` if a negative-weight cycle is reachable from `start`.
143/// Otherwise returns the distance vector (same format as `dijkstra`).
144#[allow(dead_code)]
145pub fn bellman_ford(graph: &Graph, start: usize) -> Option<Vec<f64>> {
146    let n = graph.n_nodes;
147    let mut dist = vec![f64::INFINITY; n];
148    dist[start] = 0.0;
149    for _ in 0..n.saturating_sub(1) {
150        for &(u, v, w) in &graph.edges {
151            if dist[u].is_finite() && dist[u] + w < dist[v] {
152                dist[v] = dist[u] + w;
153            }
154        }
155    }
156    for &(u, v, w) in &graph.edges {
157        if dist[u].is_finite() && dist[u] + w < dist[v] {
158            return None;
159        }
160    }
161    Some(dist)
162}
163/// Reconstructs the path to `end` from a `prev` array produced by a shortest-path algorithm.
164///
165/// Returns the path as a vector of node indices from source to `end`.
166/// Returns an empty vector if `end` is unreachable (i.e., `prev[end]` chain doesn't reach a node
167/// with `prev[node] == None` that was the source).
168#[allow(dead_code)]
169pub fn path_reconstruct(prev: &[Option<usize>], end: usize) -> Vec<usize> {
170    let mut path = Vec::new();
171    let mut cur = end;
172    loop {
173        path.push(cur);
174        match prev[cur] {
175            None => break,
176            Some(p) => cur = p,
177        }
178    }
179    path.reverse();
180    path
181}
182/// Kruskal's MST algorithm for an undirected graph.
183///
184/// Returns the MST as a list of `(a, b, weight)` edge triples.
185/// For disconnected graphs, returns a spanning forest.
186#[allow(dead_code)]
187pub fn kruskal_mst(graph: &Graph) -> Vec<(usize, usize, f64)> {
188    let mut sorted_edges = graph.edges.clone();
189    sorted_edges.sort_by(|a, b| a.2.partial_cmp(&b.2).unwrap_or(Ordering::Equal));
190    let mut uf = UnionFind::new(graph.n_nodes);
191    let mut mst = Vec::new();
192    for (u, v, w) in sorted_edges {
193        if uf.union(u, v) {
194            mst.push((u, v, w));
195        }
196    }
197    mst
198}
199/// Returns the total weight of an MST (or any edge list).
200#[allow(dead_code)]
201pub fn mst_total_weight(mst_edges: &[(usize, usize, f64)]) -> f64 {
202    mst_edges.iter().map(|&(_, _, w)| w).sum()
203}
204/// Creates a contact graph from a list of overlapping body pairs.
205///
206/// Each overlap becomes an undirected edge with weight `1.0`.
207/// The node count is inferred as `max_node_index + 1`.
208#[allow(dead_code)]
209pub fn contact_graph_from_overlaps(overlaps: &[(usize, usize)]) -> Graph {
210    let n = overlaps
211        .iter()
212        .flat_map(|&(a, b)| [a, b])
213        .max()
214        .map(|m| m + 1)
215        .unwrap_or(0);
216    let mut g = Graph::new(n);
217    for &(a, b) in overlaps {
218        g.add_undirected_edge(a, b, 1.0);
219    }
220    g
221}
222/// Returns the number of connected components (physics "islands").
223#[allow(dead_code)]
224pub fn island_count(graph: &Graph) -> usize {
225    connected_components(graph).len()
226}
227/// Computes the graph Laplacian matrix `L = D - A` for an undirected graph.
228///
229/// `A[i][j]` is the sum of weights of edges between node `i` and node `j`.
230/// `D[i][i]` is the weighted degree of node `i`.
231#[allow(dead_code)]
232pub fn laplacian_matrix(graph: &Graph) -> Vec<Vec<f64>> {
233    let n = graph.n_nodes;
234    let mut l = vec![vec![0.0f64; n]; n];
235    for &(u, v, w) in &graph.edges {
236        l[u][v] -= w;
237        l[u][u] += w;
238    }
239    l
240}
241/// A* shortest path from `start` to `goal` with a heuristic `h(node) -> f64`.
242///
243/// Returns the distance and predecessor array, or `None` if no path exists.
244/// Use `path_reconstruct` on the returned `prev` to get the path.
245pub fn astar(
246    graph: &Graph,
247    start: usize,
248    goal: usize,
249    heuristic: &dyn Fn(usize) -> f64,
250) -> Option<(f64, Vec<Option<usize>>)> {
251    let adj = graph.adjacency_list();
252    let mut dist = vec![f64::INFINITY; graph.n_nodes];
253    let mut prev: Vec<Option<usize>> = vec![None; graph.n_nodes];
254    dist[start] = 0.0;
255    let mut heap: BinaryHeap<AStarState> = BinaryHeap::new();
256    heap.push(AStarState {
257        f: dist[start] + heuristic(start),
258        g: 0.0,
259        node: start,
260    });
261    while let Some(AStarState { f: _, g, node }) = heap.pop() {
262        if g > dist[node] {
263            continue;
264        }
265        if node == goal {
266            return Some((dist[goal], prev));
267        }
268        for &(nbr, w) in &adj[node] {
269            let new_g = g + w;
270            if new_g < dist[nbr] {
271                dist[nbr] = new_g;
272                prev[nbr] = Some(node);
273                heap.push(AStarState {
274                    f: new_g + heuristic(nbr),
275                    g: new_g,
276                    node: nbr,
277                });
278            }
279        }
280    }
281    if dist[goal].is_finite() {
282        Some((dist[goal], prev))
283    } else {
284        None
285    }
286}
287/// Floyd-Warshall all-pairs shortest paths.
288///
289/// Returns a matrix `dist[i][j]` = shortest path from `i` to `j`.
290/// `f64::INFINITY` means unreachable.
291/// **Does not** detect negative cycles; behaviour on negative cycles is undefined.
292pub fn floyd_warshall(graph: &Graph) -> Vec<Vec<f64>> {
293    let n = graph.n_nodes;
294    let mut dist = vec![vec![f64::INFINITY; n]; n];
295    for i in 0..n {
296        dist[i][i] = 0.0;
297    }
298    for &(u, v, w) in &graph.edges {
299        if w < dist[u][v] {
300            dist[u][v] = w;
301        }
302    }
303    for k in 0..n {
304        for i in 0..n {
305            for j in 0..n {
306                if dist[i][k].is_finite() && dist[k][j].is_finite() {
307                    let via = dist[i][k] + dist[k][j];
308                    if via < dist[i][j] {
309                        dist[i][j] = via;
310                    }
311                }
312            }
313        }
314    }
315    dist
316}
317/// Kosaraju's algorithm for strongly connected components (SCCs).
318///
319/// Returns a list of SCCs, each as a sorted list of node indices.
320/// SCCs are returned in reverse topological order (sinks first).
321pub fn kosaraju_scc(graph: &Graph) -> Vec<Vec<usize>> {
322    let n = graph.n_nodes;
323    let adj = graph.adjacency_list();
324    let mut visited = vec![false; n];
325    let mut finish_stack: Vec<usize> = Vec::with_capacity(n);
326    fn dfs1(
327        node: usize,
328        adj: &[Vec<(usize, f64)>],
329        visited: &mut Vec<bool>,
330        stack: &mut Vec<usize>,
331    ) {
332        let mut call_stack = vec![(node, 0usize)];
333        while let Some((v, idx)) = call_stack.last_mut() {
334            let v = *v;
335            if !visited[v] {
336                visited[v] = true;
337            }
338            if *idx < adj[v].len() {
339                let (nbr, _) = adj[v][*idx];
340                *idx += 1;
341                if !visited[nbr] {
342                    call_stack.push((nbr, 0));
343                }
344            } else {
345                stack.push(v);
346                call_stack.pop();
347            }
348        }
349    }
350    for i in 0..n {
351        if !visited[i] {
352            dfs1(i, &adj, &mut visited, &mut finish_stack);
353        }
354    }
355    let mut radj: Vec<Vec<(usize, f64)>> = vec![Vec::new(); n];
356    for &(u, v, w) in &graph.edges {
357        radj[v].push((u, w));
358    }
359    let mut visited2 = vec![false; n];
360    let mut sccs: Vec<Vec<usize>> = Vec::new();
361    while let Some(start) = finish_stack.pop() {
362        if visited2[start] {
363            continue;
364        }
365        let mut comp = Vec::new();
366        let mut stack = vec![start];
367        while let Some(v) = stack.pop() {
368            if visited2[v] {
369                continue;
370            }
371            visited2[v] = true;
372            comp.push(v);
373            for &(nbr, _) in &radj[v] {
374                if !visited2[nbr] {
375                    stack.push(nbr);
376                }
377            }
378        }
379        comp.sort_unstable();
380        sccs.push(comp);
381    }
382    sccs
383}
384/// Tarjan's algorithm for strongly connected components.
385///
386/// Returns a list of SCCs in reverse topological order.
387pub fn tarjan_scc(graph: &Graph) -> Vec<Vec<usize>> {
388    let n = graph.n_nodes;
389    let adj = graph.adjacency_list();
390    let mut index = 0usize;
391    let mut indices = vec![usize::MAX; n];
392    let mut lowlinks = vec![0usize; n];
393    let mut on_stack = vec![false; n];
394    let mut stack: Vec<usize> = Vec::new();
395    let mut sccs: Vec<Vec<usize>> = Vec::new();
396    let mut call_stack: Vec<(usize, usize)> = Vec::new();
397    for start in 0..n {
398        if indices[start] != usize::MAX {
399            continue;
400        }
401        call_stack.push((start, 0));
402        while let Some(&mut (v, ref mut adj_idx)) = call_stack.last_mut() {
403            if indices[v] == usize::MAX {
404                indices[v] = index;
405                lowlinks[v] = index;
406                index += 1;
407                stack.push(v);
408                on_stack[v] = true;
409            }
410            let mut pushed = false;
411            while *adj_idx < adj[v].len() {
412                let (w, _) = adj[v][*adj_idx];
413                *adj_idx += 1;
414                if indices[w] == usize::MAX {
415                    call_stack.push((w, 0));
416                    pushed = true;
417                    break;
418                } else if on_stack[w] {
419                    lowlinks[v] = lowlinks[v].min(indices[w]);
420                }
421            }
422            if !pushed {
423                let (v, _) = call_stack
424                    .pop()
425                    .expect("call_stack is non-empty in backtrack");
426                if let Some(&mut (parent, _)) = call_stack.last_mut() {
427                    lowlinks[parent] = lowlinks[parent].min(lowlinks[v]);
428                }
429                if lowlinks[v] == indices[v] {
430                    let mut comp = Vec::new();
431                    loop {
432                        let w = stack.pop().expect("stack is non-empty in SCC extraction");
433                        on_stack[w] = false;
434                        comp.push(w);
435                        if w == v {
436                            break;
437                        }
438                    }
439                    comp.sort_unstable();
440                    sccs.push(comp);
441                }
442            }
443        }
444    }
445    sccs
446}
447/// Prim's minimum spanning tree algorithm.
448///
449/// Returns the MST as a list of `(a, b, weight)` edge triples.
450/// Assumes an undirected (or symmetrically-stored directed) graph.
451pub fn prim_mst(graph: &Graph) -> Vec<(usize, usize, f64)> {
452    if graph.n_nodes == 0 {
453        return Vec::new();
454    }
455    let adj = graph.adjacency_list();
456    let mut in_tree = vec![false; graph.n_nodes];
457    let mut key = vec![f64::INFINITY; graph.n_nodes];
458    let mut parent: Vec<Option<usize>> = vec![None; graph.n_nodes];
459    key[0] = 0.0;
460    for _ in 0..graph.n_nodes {
461        let u = (0..graph.n_nodes)
462            .filter(|&v| !in_tree[v])
463            .min_by(|&a, &b| key[a].partial_cmp(&key[b]).unwrap_or(Ordering::Equal));
464        let u = match u {
465            Some(v) => v,
466            None => break,
467        };
468        in_tree[u] = true;
469        for &(v, w) in &adj[u] {
470            if !in_tree[v] && w < key[v] {
471                key[v] = w;
472                parent[v] = Some(u);
473            }
474        }
475    }
476    let mut mst = Vec::new();
477    for v in 0..graph.n_nodes {
478        if let Some(u) = parent[v] {
479            mst.push((u, v, key[v]));
480        }
481    }
482    mst
483}
484/// Edmonds-Karp max-flow algorithm (BFS augmenting paths).
485///
486/// Returns the maximum flow value from `source` to `sink`.
487/// Works on non-negative edge capacities.
488pub fn max_flow_edmonds_karp(graph: &Graph, source: usize, sink: usize) -> f64 {
489    let n = graph.n_nodes;
490    let mut cap = vec![vec![0.0f64; n]; n];
491    for &(u, v, w) in &graph.edges {
492        cap[u][v] += w;
493    }
494    let mut flow = 0.0f64;
495    loop {
496        let mut prev = vec![usize::MAX; n];
497        let mut visited = vec![false; n];
498        visited[source] = true;
499        let mut queue = VecDeque::new();
500        queue.push_back(source);
501        while let Some(u) = queue.pop_front() {
502            if u == sink {
503                break;
504            }
505            for v in 0..n {
506                if !visited[v] && cap[u][v] > 1e-12 {
507                    visited[v] = true;
508                    prev[v] = u;
509                    queue.push_back(v);
510                }
511            }
512        }
513        if !visited[sink] {
514            break;
515        }
516        let mut path_flow = f64::INFINITY;
517        let mut v = sink;
518        while v != source {
519            let u = prev[v];
520            path_flow = path_flow.min(cap[u][v]);
521            v = u;
522        }
523        let mut v = sink;
524        while v != source {
525            let u = prev[v];
526            cap[u][v] -= path_flow;
527            cap[v][u] += path_flow;
528            v = u;
529        }
530        flow += path_flow;
531    }
532    flow
533}
534/// Dijkstra with both distances and predecessor array.
535///
536/// Returns `(dist, prev)` where `prev[i]` is the predecessor of node `i`
537/// on the shortest path from `start`.
538pub fn dijkstra_with_path(graph: &Graph, start: usize) -> (Vec<f64>, Vec<Option<usize>>) {
539    let adj = graph.adjacency_list();
540    let mut dist = vec![f64::INFINITY; graph.n_nodes];
541    let mut prev: Vec<Option<usize>> = vec![None; graph.n_nodes];
542    dist[start] = 0.0;
543    let mut heap = BinaryHeap::new();
544    heap.push(State {
545        cost: 0.0,
546        node: start,
547    });
548    while let Some(State { cost, node }) = heap.pop() {
549        if cost > dist[node] {
550            continue;
551        }
552        for &(nbr, w) in &adj[node] {
553            let next = cost + w;
554            if next < dist[nbr] {
555                dist[nbr] = next;
556                prev[nbr] = Some(node);
557                heap.push(State {
558                    cost: next,
559                    node: nbr,
560                });
561            }
562        }
563    }
564    (dist, prev)
565}
566/// Returns `true` if the directed graph is a DAG (no directed cycles).
567pub fn is_dag(graph: &Graph) -> bool {
568    topological_sort(graph).is_some()
569}
570/// Counts directed cycles (simple) in the graph using DFS coloring.
571///
572/// Returns the number of back edges encountered.
573pub fn count_back_edges(graph: &Graph) -> usize {
574    let adj = graph.adjacency_list();
575    let n = graph.n_nodes;
576    let mut color = vec![0u8; n];
577    let mut back_edges = 0usize;
578    fn dfs_color(v: usize, adj: &[Vec<(usize, f64)>], color: &mut Vec<u8>, back: &mut usize) {
579        color[v] = 1;
580        for &(w, _) in &adj[v] {
581            if color[w] == 1 {
582                *back += 1;
583            } else if color[w] == 0 {
584                dfs_color(w, adj, color, back);
585            }
586        }
587        color[v] = 2;
588    }
589    for i in 0..n {
590        if color[i] == 0 {
591            dfs_color(i, &adj, &mut color, &mut back_edges);
592        }
593    }
594    back_edges
595}
596/// Returns the diameter of the graph: the longest shortest path.
597///
598/// Computed using BFS from every node (unweighted).
599pub fn graph_diameter_bfs(graph: &Graph) -> usize {
600    let mut diameter = 0usize;
601    for start in 0..graph.n_nodes {
602        let bfs_dist = bfs_distances(graph, start);
603        for d in bfs_dist {
604            if d != usize::MAX && d > diameter {
605                diameter = d;
606            }
607        }
608    }
609    diameter
610}
611/// BFS distances from `start` (unweighted).  `usize::MAX` = unreachable.
612pub fn bfs_distances(graph: &Graph, start: usize) -> Vec<usize> {
613    let adj = graph.adjacency_list();
614    let mut dist = vec![usize::MAX; graph.n_nodes];
615    dist[start] = 0;
616    let mut queue = VecDeque::new();
617    queue.push_back(start);
618    while let Some(v) = queue.pop_front() {
619        for &(w, _) in &adj[v] {
620            if dist[w] == usize::MAX {
621                dist[w] = dist[v] + 1;
622                queue.push_back(w);
623            }
624        }
625    }
626    dist
627}
628/// Degree centrality: returns `degree(v) / (n-1)` for each node.
629pub fn degree_centrality(graph: &Graph) -> Vec<f64> {
630    let n = graph.n_nodes;
631    if n <= 1 {
632        return vec![0.0; n];
633    }
634    (0..n)
635        .map(|v| graph.degree(v) as f64 / (n - 1) as f64)
636        .collect()
637}
638/// Returns adjacency matrix as a flat `n×n` Vec`f64`.
639pub fn adjacency_matrix(graph: &Graph) -> Vec<Vec<f64>> {
640    let n = graph.n_nodes;
641    let mut mat = vec![vec![0.0f64; n]; n];
642    for &(u, v, w) in &graph.edges {
643        mat[u][v] = w;
644    }
645    mat
646}
647/// Convert a distance matrix to a sparse graph (threshold edges).
648pub fn distance_matrix_to_graph(dist: &[Vec<f64>], threshold: f64) -> Graph {
649    let n = dist.len();
650    let mut g = Graph::new(n);
651    for i in 0..n {
652        for j in 0..n {
653            if i != j && dist[i][j] < threshold {
654                g.add_edge(i, j, dist[i][j]);
655            }
656        }
657    }
658    g
659}
660/// Returns `true` if the undirected graph (edges treated as undirected) is bipartite.
661///
662/// Uses a 2-colouring BFS: if any edge connects same-coloured vertices, the graph
663/// is not bipartite.
664pub fn is_bipartite(graph: &Graph) -> bool {
665    let adj = graph.adjacency_list();
666    let mut color = vec![usize::MAX; graph.n_nodes];
667    for start in 0..graph.n_nodes {
668        if color[start] != usize::MAX {
669            continue;
670        }
671        color[start] = 0;
672        let mut queue = VecDeque::new();
673        queue.push_back(start);
674        while let Some(u) = queue.pop_front() {
675            for &(v, _w) in &adj[u] {
676                if color[v] == usize::MAX {
677                    color[v] = 1 - color[u];
678                    queue.push_back(v);
679                } else if color[v] == color[u] {
680                    return false;
681                }
682            }
683        }
684    }
685    true
686}
687/// Returns the 2-colouring as `Some(colors)` if bipartite, `None` otherwise.
688///
689/// `colors[i]` is 0 or 1 for each node `i`.
690pub fn bipartite_coloring(graph: &Graph) -> Option<Vec<usize>> {
691    let adj = graph.adjacency_list();
692    let mut color = vec![usize::MAX; graph.n_nodes];
693    for start in 0..graph.n_nodes {
694        if color[start] != usize::MAX {
695            continue;
696        }
697        color[start] = 0;
698        let mut queue = VecDeque::new();
699        queue.push_back(start);
700        while let Some(u) = queue.pop_front() {
701            for &(v, _w) in &adj[u] {
702                if color[v] == usize::MAX {
703                    color[v] = 1 - color[u];
704                    queue.push_back(v);
705                } else if color[v] == color[u] {
706                    return None;
707                }
708            }
709        }
710    }
711    Some(color)
712}
713/// Greedy graph colouring.
714///
715/// Assigns the smallest available colour (integer ≥ 0) to each node in order
716/// `0..n_nodes`.  Returns a vector of length `n_nodes` where each entry is the
717/// colour index.  The number of colours used is `result.iter().max() + 1`.
718///
719/// This is a simple greedy heuristic and does **not** guarantee the chromatic
720/// number in general graphs.
721pub fn greedy_coloring(graph: &Graph) -> Vec<usize> {
722    let adj = graph.adjacency_list();
723    let n = graph.n_nodes;
724    let mut color: Vec<Option<usize>> = vec![None; n];
725    for u in 0..n {
726        let mut used: std::collections::HashSet<usize> = std::collections::HashSet::new();
727        for &(v, _w) in &adj[u] {
728            if let Some(c) = color[v] {
729                used.insert(c);
730            }
731        }
732        let mut c = 0usize;
733        while used.contains(&c) {
734            c += 1;
735        }
736        color[u] = Some(c);
737    }
738    color.into_iter().map(|c| c.unwrap_or(0)).collect()
739}
740/// Returns the number of colours used by a colouring vector.
741pub fn chromatic_number_upper_bound(coloring: &[usize]) -> usize {
742    coloring.iter().copied().max().map(|m| m + 1).unwrap_or(0)
743}
744/// Kahn's algorithm for topological sorting (BFS / in-degree approach).
745///
746/// Returns `Some(order)` for a DAG, or `None` if the graph contains a cycle.
747/// Equivalent to `topological_sort` but implemented via in-degree counting.
748pub fn kahn_topological_sort(graph: &Graph) -> Option<Vec<usize>> {
749    let n = graph.n_nodes;
750    let mut in_degree = vec![0usize; n];
751    let adj = graph.adjacency_list();
752    for u in 0..n {
753        for &(v, _w) in &adj[u] {
754            in_degree[v] += 1;
755        }
756    }
757    let mut queue: VecDeque<usize> = (0..n).filter(|&u| in_degree[u] == 0).collect();
758    let mut order = Vec::with_capacity(n);
759    while let Some(u) = queue.pop_front() {
760        order.push(u);
761        for &(v, _w) in &adj[u] {
762            in_degree[v] -= 1;
763            if in_degree[v] == 0 {
764                queue.push_back(v);
765            }
766        }
767    }
768    if order.len() == n { Some(order) } else { None }
769}
770/// Returns `true` if the (undirected) graph has an Eulerian circuit.
771///
772/// Conditions: the graph is connected (ignoring isolated nodes) AND every node
773/// has even degree.
774pub fn has_eulerian_circuit(graph: &Graph) -> bool {
775    for u in 0..graph.n_nodes {
776        if !graph.degree(u).is_multiple_of(2) {
777            return false;
778        }
779    }
780    let non_isolated: Vec<usize> = (0..graph.n_nodes)
781        .filter(|&u| graph.degree(u) > 0)
782        .collect();
783    if non_isolated.is_empty() {
784        return true;
785    }
786    let start = non_isolated[0];
787    let visited = {
788        let adj = graph.adjacency_list();
789        let mut vis = vec![false; graph.n_nodes];
790        let mut stack = vec![start];
791        vis[start] = true;
792        while let Some(u) = stack.pop() {
793            for &(v, _w) in &adj[u] {
794                if !vis[v] {
795                    vis[v] = true;
796                    stack.push(v);
797                }
798            }
799        }
800        vis
801    };
802    non_isolated.iter().all(|&u| visited[u])
803}
804/// Returns `true` if the (undirected) graph has an Eulerian path (but not a circuit).
805///
806/// Conditions: exactly 2 nodes have odd degree AND the graph is connected.
807pub fn has_eulerian_path(graph: &Graph) -> bool {
808    let odd_degree_count = (0..graph.n_nodes)
809        .filter(|&u| !graph.degree(u).is_multiple_of(2))
810        .count();
811    odd_degree_count == 2 && {
812        let non_isolated: Vec<usize> = (0..graph.n_nodes)
813            .filter(|&u| graph.degree(u) > 0)
814            .collect();
815        if non_isolated.is_empty() {
816            return false;
817        }
818        let start = non_isolated[0];
819        let adj = graph.adjacency_list();
820        let mut vis = vec![false; graph.n_nodes];
821        let mut stack = vec![start];
822        vis[start] = true;
823        while let Some(u) = stack.pop() {
824            for &(v, _w) in &adj[u] {
825                if !vis[v] {
826                    vis[v] = true;
827                    stack.push(v);
828                }
829            }
830        }
831        non_isolated.iter().all(|&u| vis[u])
832    }
833}
834/// Computes approximate betweenness centrality using Dijkstra for each source.
835///
836/// For each pair `(s, t)`, counts how many shortest paths pass through each
837/// intermediate node and normalises by `(n-1)*(n-2)/2`.  This is the
838/// unnormalised vertex betweenness centrality.
839pub fn betweenness_centrality(graph: &Graph) -> Vec<f64> {
840    let n = graph.n_nodes;
841    let mut bc = vec![0.0f64; n];
842    for s in 0..n {
843        let (_dist, prev) = dijkstra_with_path(graph, s);
844        for t in 0..n {
845            if t == s {
846                continue;
847            }
848            let path = path_reconstruct(&prev, t);
849            if path.len() < 3 {
850                continue;
851            }
852            for &v in &path[1..path.len() - 1] {
853                bc[v] += 1.0;
854            }
855        }
856    }
857    let norm = if n > 2 {
858        ((n - 1) * (n - 2)) as f64
859    } else {
860        1.0
861    };
862    for v in bc.iter_mut() {
863        *v /= norm;
864    }
865    bc
866}
867/// Computes closeness centrality for every node.
868///
869/// Closeness = `(n_reachable - 1) / sum_of_distances` for reachable nodes,
870/// normalised by `(n_reachable - 1) / (n - 1)` (Wasserman-Faust variant).
871pub fn closeness_centrality(graph: &Graph) -> Vec<f64> {
872    let n = graph.n_nodes;
873    let mut cc = vec![0.0f64; n];
874    for u in 0..n {
875        let dist = dijkstra(graph, u);
876        let (sum, reachable) = dist
877            .iter()
878            .enumerate()
879            .fold((0.0f64, 0usize), |(s, r), (v, &d)| {
880                if v != u && d.is_finite() {
881                    (s + d, r + 1)
882                } else {
883                    (s, r)
884                }
885            });
886        if reachable == 0 || sum == 0.0 {
887            cc[u] = 0.0;
888        } else {
889            let raw = (reachable as f64) / sum;
890            cc[u] = raw * (reachable as f64) / ((n - 1) as f64);
891        }
892    }
893    cc
894}
895/// Augmenting-path maximum bipartite matching.
896///
897/// `left` and `right` are the node indices for the two sides of the bipartite
898/// graph.  Returns a list of matched pairs `(l, r)`.
899pub fn bipartite_matching(graph: &Graph, left: &[usize], right: &[usize]) -> Vec<(usize, usize)> {
900    let adj = graph.adjacency_list();
901    let mut right_idx = vec![usize::MAX; graph.n_nodes];
902    for (i, &r) in right.iter().enumerate() {
903        right_idx[r] = i;
904    }
905    let nr = right.len();
906    let mut match_l: Vec<Option<usize>> = vec![None; graph.n_nodes];
907    let mut match_r: Vec<Option<usize>> = vec![None; nr];
908    fn augment(
909        u: usize,
910        adj: &[Vec<(usize, f64)>],
911        right_idx: &[usize],
912        match_r: &mut Vec<Option<usize>>,
913        visited: &mut Vec<bool>,
914    ) -> bool {
915        for &(v, _w) in &adj[u] {
916            let ri = right_idx[v];
917            if ri == usize::MAX || visited[ri] {
918                continue;
919            }
920            visited[ri] = true;
921            let can =
922                match_r[ri].is_none_or(|prev_l| augment(prev_l, adj, right_idx, match_r, visited));
923            if can {
924                match_r[ri] = Some(u);
925                return true;
926            }
927        }
928        false
929    }
930    for &l in left {
931        let mut visited = vec![false; nr];
932        if augment(l, &adj, &right_idx, &mut match_r, &mut visited) {
933            for (ri, mr) in match_r.iter().enumerate() {
934                if *mr == Some(l) {
935                    match_l[l] = Some(ri);
936                }
937            }
938        }
939    }
940    let mut pairs = Vec::new();
941    for &l in left {
942        if let Some(ri) = match_l[l] {
943            pairs.push((l, right[ri]));
944        }
945    }
946    pairs
947}
948/// Graph density: `|E| / (|V| * (|V| - 1))` for directed graphs.
949pub fn graph_density(graph: &Graph) -> f64 {
950    let n = graph.n_nodes;
951    if n <= 1 {
952        return 0.0;
953    }
954    graph.edge_count() as f64 / (n * (n - 1)) as f64
955}
956/// Local clustering coefficient for node `u` (undirected interpretation).
957///
958/// = (number of edges among neighbours of u) / (degree * (degree - 1) / 2)
959pub fn local_clustering(graph: &Graph, u: usize) -> f64 {
960    let adj = graph.adjacency_list();
961    let neighbours: Vec<usize> = adj[u].iter().map(|&(v, _)| v).collect();
962    let k = neighbours.len();
963    if k < 2 {
964        return 0.0;
965    }
966    let neighbour_set: std::collections::HashSet<usize> = neighbours.iter().copied().collect();
967    let mut edges_among = 0usize;
968    for &v in &neighbours {
969        for &(w, _) in &adj[v] {
970            if neighbour_set.contains(&w) && w != u {
971                edges_among += 1;
972            }
973        }
974    }
975    edges_among as f64 / (k * (k - 1)) as f64
976}
977/// Average clustering coefficient across all nodes.
978pub fn average_clustering(graph: &Graph) -> f64 {
979    if graph.n_nodes == 0 {
980        return 0.0;
981    }
982    let total: f64 = (0..graph.n_nodes).map(|u| local_clustering(graph, u)).sum();
983    total / graph.n_nodes as f64
984}
985#[cfg(test)]
986mod tests {
987    use super::*;
988    fn triangle_graph() -> Graph {
989        let mut g = Graph::new(3);
990        g.add_undirected_edge(0, 1, 1.0);
991        g.add_undirected_edge(1, 2, 2.0);
992        g.add_undirected_edge(0, 2, 4.0);
993        g
994    }
995    #[test]
996    fn test_new_graph() {
997        let g = Graph::new(5);
998        assert_eq!(g.node_count(), 5);
999        assert_eq!(g.edge_count(), 0);
1000    }
1001    #[test]
1002    fn test_add_directed_edge() {
1003        let mut g = Graph::new(3);
1004        g.add_edge(0, 1, 1.5);
1005        assert_eq!(g.edge_count(), 1);
1006    }
1007    #[test]
1008    fn test_add_undirected_edge_stores_two() {
1009        let mut g = Graph::new(3);
1010        g.add_undirected_edge(0, 1, 1.0);
1011        assert_eq!(g.edge_count(), 2);
1012    }
1013    #[test]
1014    fn test_degree() {
1015        let g = triangle_graph();
1016        assert_eq!(g.degree(0), 2);
1017        assert_eq!(g.degree(1), 2);
1018        assert_eq!(g.degree(2), 2);
1019    }
1020    #[test]
1021    fn test_adjacency_list() {
1022        let mut g = Graph::new(3);
1023        g.add_edge(0, 1, 2.0);
1024        g.add_edge(0, 2, 3.0);
1025        let adj = g.adjacency_list();
1026        assert_eq!(adj[0].len(), 2);
1027        assert_eq!(adj[1].len(), 0);
1028    }
1029    #[test]
1030    fn test_bfs_connected() {
1031        let g = triangle_graph();
1032        let order = bfs(&g, 0);
1033        assert_eq!(order.len(), 3);
1034        assert_eq!(order[0], 0);
1035    }
1036    #[test]
1037    fn test_bfs_single_node() {
1038        let g = Graph::new(1);
1039        let order = bfs(&g, 0);
1040        assert_eq!(order, vec![0]);
1041    }
1042    #[test]
1043    fn test_bfs_disconnected_visits_reachable() {
1044        let mut g = Graph::new(4);
1045        g.add_undirected_edge(0, 1, 1.0);
1046        let order = bfs(&g, 0);
1047        assert_eq!(order.len(), 2);
1048    }
1049    #[test]
1050    fn test_dfs_connected() {
1051        let g = triangle_graph();
1052        let order = dfs(&g, 0);
1053        assert_eq!(order.len(), 3);
1054        assert_eq!(order[0], 0);
1055    }
1056    #[test]
1057    fn test_dfs_single_node() {
1058        let g = Graph::new(1);
1059        assert_eq!(dfs(&g, 0), vec![0]);
1060    }
1061    #[test]
1062    fn test_connected_components_single() {
1063        let g = triangle_graph();
1064        let comps = connected_components(&g);
1065        assert_eq!(comps.len(), 1);
1066        assert_eq!(comps[0], vec![0, 1, 2]);
1067    }
1068    #[test]
1069    fn test_connected_components_two() {
1070        let mut g = Graph::new(4);
1071        g.add_undirected_edge(0, 1, 1.0);
1072        g.add_undirected_edge(2, 3, 1.0);
1073        let comps = connected_components(&g);
1074        assert_eq!(comps.len(), 2);
1075    }
1076    #[test]
1077    fn test_is_connected_true() {
1078        assert!(is_connected(&triangle_graph()));
1079    }
1080    #[test]
1081    fn test_is_connected_false() {
1082        let g = Graph::new(3);
1083        assert!(!is_connected(&g));
1084    }
1085    #[test]
1086    fn test_is_connected_single_node() {
1087        let g = Graph::new(1);
1088        assert!(is_connected(&g));
1089    }
1090    #[test]
1091    fn test_topological_sort_dag() {
1092        let mut g = Graph::new(4);
1093        g.add_edge(0, 1, 1.0);
1094        g.add_edge(0, 2, 1.0);
1095        g.add_edge(1, 3, 1.0);
1096        g.add_edge(2, 3, 1.0);
1097        let order = topological_sort(&g).expect("DAG should succeed");
1098        assert_eq!(order.len(), 4);
1099        let pos: Vec<usize> = {
1100            let mut p = vec![0usize; 4];
1101            for (i, &n) in order.iter().enumerate() {
1102                p[n] = i;
1103            }
1104            p
1105        };
1106        assert!(pos[0] < pos[1]);
1107        assert!(pos[0] < pos[2]);
1108        assert!(pos[1] < pos[3]);
1109    }
1110    #[test]
1111    fn test_topological_sort_cycle() {
1112        let mut g = Graph::new(3);
1113        g.add_edge(0, 1, 1.0);
1114        g.add_edge(1, 2, 1.0);
1115        g.add_edge(2, 0, 1.0);
1116        assert!(topological_sort(&g).is_none());
1117    }
1118    #[test]
1119    fn test_dijkstra_triangle() {
1120        let g = triangle_graph();
1121        let dist = dijkstra(&g, 0);
1122        assert_eq!(dist[0], 0.0);
1123        assert_eq!(dist[1], 1.0);
1124        assert_eq!(dist[2], 3.0);
1125    }
1126    #[test]
1127    fn test_dijkstra_unreachable() {
1128        let mut g = Graph::new(3);
1129        g.add_edge(0, 1, 1.0);
1130        let dist = dijkstra(&g, 0);
1131        assert_eq!(dist[2], f64::INFINITY);
1132    }
1133    #[test]
1134    fn test_bellman_ford_no_negative_cycle() {
1135        let g = triangle_graph();
1136        let dist = bellman_ford(&g, 0).expect("no negative cycle");
1137        assert_eq!(dist[0], 0.0);
1138        assert_eq!(dist[1], 1.0);
1139    }
1140    #[test]
1141    fn test_bellman_ford_negative_cycle() {
1142        let mut g = Graph::new(3);
1143        g.add_edge(0, 1, 1.0);
1144        g.add_edge(1, 2, -2.0);
1145        g.add_edge(2, 0, -1.0);
1146        assert!(bellman_ford(&g, 0).is_none());
1147    }
1148    #[test]
1149    fn test_path_reconstruct() {
1150        let prev: Vec<Option<usize>> = vec![None, Some(0), Some(1), Some(2)];
1151        let path = path_reconstruct(&prev, 3);
1152        assert_eq!(path, vec![0, 1, 2, 3]);
1153    }
1154    #[test]
1155    fn test_path_reconstruct_single() {
1156        let prev: Vec<Option<usize>> = vec![None];
1157        assert_eq!(path_reconstruct(&prev, 0), vec![0]);
1158    }
1159    #[test]
1160    fn test_kruskal_mst_triangle() {
1161        let g = triangle_graph();
1162        let mst = kruskal_mst(&g);
1163        assert_eq!(mst.len(), 2);
1164        let total = mst_total_weight(&mst);
1165        assert!((total - 3.0).abs() < 1e-12);
1166    }
1167    #[test]
1168    fn test_mst_total_weight_empty() {
1169        assert_eq!(mst_total_weight(&[]), 0.0);
1170    }
1171    #[test]
1172    fn test_contact_graph_from_overlaps() {
1173        let overlaps = vec![(0, 1), (1, 2), (0, 2)];
1174        let g = contact_graph_from_overlaps(&overlaps);
1175        assert_eq!(g.node_count(), 3);
1176        assert_eq!(g.edge_count(), 6);
1177    }
1178    #[test]
1179    fn test_contact_graph_empty() {
1180        let g = contact_graph_from_overlaps(&[]);
1181        assert_eq!(g.node_count(), 0);
1182    }
1183    #[test]
1184    fn test_island_count_one() {
1185        assert_eq!(island_count(&triangle_graph()), 1);
1186    }
1187    #[test]
1188    fn test_island_count_three() {
1189        let g = Graph::new(3);
1190        assert_eq!(island_count(&g), 3);
1191    }
1192    #[test]
1193    fn test_laplacian_row_sum_zero() {
1194        let g = triangle_graph();
1195        let l = laplacian_matrix(&g);
1196        for row in &l {
1197            let s: f64 = row.iter().sum();
1198            assert!(s.abs() < 1e-12, "row sum = {s}");
1199        }
1200    }
1201    #[test]
1202    fn test_laplacian_diagonal_equals_degree() {
1203        let g = triangle_graph();
1204        let l = laplacian_matrix(&g);
1205        assert!((l[0][0] - 5.0).abs() < 1e-12);
1206        assert!((l[1][1] - 3.0).abs() < 1e-12);
1207        assert!((l[2][2] - 6.0).abs() < 1e-12);
1208    }
1209    #[test]
1210    fn test_laplacian_off_diagonal_negative() {
1211        let g = triangle_graph();
1212        let l = laplacian_matrix(&g);
1213        assert!(l[0][1] < 0.0);
1214        assert!(l[1][0] < 0.0);
1215    }
1216    #[test]
1217    fn test_floyd_warshall_triangle() {
1218        let g = triangle_graph();
1219        let dist = floyd_warshall(&g);
1220        assert!((dist[0][1] - 1.0).abs() < 1e-12);
1221        assert!((dist[0][2] - 3.0).abs() < 1e-12);
1222    }
1223    #[test]
1224    fn test_floyd_warshall_diagonal_zero() {
1225        let g = triangle_graph();
1226        let dist = floyd_warshall(&g);
1227        for i in 0..3 {
1228            assert!(
1229                (dist[i][i] - 0.0).abs() < 1e-12,
1230                "dist[{}][{}]={}",
1231                i,
1232                i,
1233                dist[i][i]
1234            );
1235        }
1236    }
1237    #[test]
1238    fn test_floyd_warshall_unreachable() {
1239        let mut g = Graph::new(3);
1240        g.add_edge(0, 1, 1.0);
1241        let dist = floyd_warshall(&g);
1242        assert_eq!(dist[0][2], f64::INFINITY);
1243    }
1244    #[test]
1245    fn test_astar_finds_path() {
1246        let g = triangle_graph();
1247        let result = astar(&g, 0, 2, &|_| 0.0);
1248        assert!(result.is_some(), "A* should find a path");
1249        let (cost, _) = result.unwrap();
1250        assert!((cost - 3.0).abs() < 1e-12, "cost={}", cost);
1251    }
1252    #[test]
1253    fn test_astar_no_path() {
1254        let mut g = Graph::new(3);
1255        g.add_edge(0, 1, 1.0);
1256        let result = astar(&g, 0, 2, &|_| 0.0);
1257        assert!(
1258            result.is_none(),
1259            "A* should not find path to unreachable node"
1260        );
1261    }
1262    #[test]
1263    fn test_kosaraju_scc_single_cycle() {
1264        let mut g = Graph::new(3);
1265        g.add_edge(0, 1, 1.0);
1266        g.add_edge(1, 2, 1.0);
1267        g.add_edge(2, 0, 1.0);
1268        let sccs = kosaraju_scc(&g);
1269        assert_eq!(sccs.len(), 1, "one SCC expected for a cycle");
1270    }
1271    #[test]
1272    fn test_kosaraju_scc_dag() {
1273        let mut g = Graph::new(3);
1274        g.add_edge(0, 1, 1.0);
1275        g.add_edge(1, 2, 1.0);
1276        let sccs = kosaraju_scc(&g);
1277        assert_eq!(sccs.len(), 3, "DAG should have 3 SCCs");
1278    }
1279    #[test]
1280    fn test_prim_mst_triangle() {
1281        let g = triangle_graph();
1282        let mst = prim_mst(&g);
1283        let total = mst.iter().map(|&(_, _, w)| w).sum::<f64>();
1284        assert!((total - 3.0).abs() < 1e-12, "prim MST weight={}", total);
1285    }
1286    #[test]
1287    fn test_prim_mst_empty_graph() {
1288        let g = Graph::new(0);
1289        let mst = prim_mst(&g);
1290        assert!(mst.is_empty());
1291    }
1292    #[test]
1293    fn test_max_flow_simple_path() {
1294        let mut g = Graph::new(3);
1295        g.add_edge(0, 1, 3.0);
1296        g.add_edge(1, 2, 2.0);
1297        let flow = max_flow_edmonds_karp(&g, 0, 2);
1298        assert!((flow - 2.0).abs() < 1e-9, "flow={}", flow);
1299    }
1300    #[test]
1301    fn test_max_flow_two_parallel_paths() {
1302        let mut g = Graph::new(4);
1303        g.add_edge(0, 1, 2.0);
1304        g.add_edge(1, 3, 2.0);
1305        g.add_edge(0, 2, 1.0);
1306        g.add_edge(2, 3, 1.0);
1307        let flow = max_flow_edmonds_karp(&g, 0, 3);
1308        assert!((flow - 3.0).abs() < 1e-9, "flow={}", flow);
1309    }
1310    #[test]
1311    fn test_dijkstra_with_path_triangle() {
1312        let g = triangle_graph();
1313        let (dist, prev) = dijkstra_with_path(&g, 0);
1314        assert!((dist[2] - 3.0).abs() < 1e-12);
1315        let path = path_reconstruct(&prev, 2);
1316        assert_eq!(path[0], 0);
1317        assert_eq!(*path.last().unwrap(), 2);
1318    }
1319    #[test]
1320    fn test_is_dag_true() {
1321        let mut g = Graph::new(3);
1322        g.add_edge(0, 1, 1.0);
1323        g.add_edge(1, 2, 1.0);
1324        assert!(is_dag(&g));
1325    }
1326    #[test]
1327    fn test_is_dag_false() {
1328        let mut g = Graph::new(3);
1329        g.add_edge(0, 1, 1.0);
1330        g.add_edge(1, 2, 1.0);
1331        g.add_edge(2, 0, 1.0);
1332        assert!(!is_dag(&g));
1333    }
1334    #[test]
1335    fn test_degree_centrality_triangle() {
1336        let g = triangle_graph();
1337        let dc = degree_centrality(&g);
1338        for c in &dc {
1339            assert!((*c - 1.0).abs() < 1e-12, "c={}", c);
1340        }
1341    }
1342    #[test]
1343    fn test_adjacency_matrix_triangle() {
1344        let mut g = Graph::new(3);
1345        g.add_edge(0, 1, 2.0);
1346        g.add_edge(1, 2, 3.0);
1347        let mat = adjacency_matrix(&g);
1348        assert!((mat[0][1] - 2.0).abs() < 1e-12);
1349        assert!((mat[1][2] - 3.0).abs() < 1e-12);
1350        assert!((mat[0][0]).abs() < 1e-12);
1351    }
1352    #[test]
1353    fn test_graph_diameter_triangle() {
1354        let g = triangle_graph();
1355        let d = graph_diameter_bfs(&g);
1356        assert_eq!(d, 1, "diameter of complete triangle = 1");
1357    }
1358    #[test]
1359    fn test_count_back_edges_cycle() {
1360        let mut g = Graph::new(3);
1361        g.add_edge(0, 1, 1.0);
1362        g.add_edge(1, 2, 1.0);
1363        g.add_edge(2, 0, 1.0);
1364        let back = count_back_edges(&g);
1365        assert!(back > 0, "cycle should have back edges");
1366    }
1367    #[test]
1368    fn test_count_back_edges_dag() {
1369        let mut g = Graph::new(3);
1370        g.add_edge(0, 1, 1.0);
1371        g.add_edge(1, 2, 1.0);
1372        let back = count_back_edges(&g);
1373        assert_eq!(back, 0, "DAG should have no back edges");
1374    }
1375    #[test]
1376    fn test_is_bipartite_path_graph() {
1377        let mut g = Graph::new(4);
1378        g.add_undirected_edge(0, 1, 1.0);
1379        g.add_undirected_edge(1, 2, 1.0);
1380        g.add_undirected_edge(2, 3, 1.0);
1381        assert!(is_bipartite(&g));
1382    }
1383    #[test]
1384    fn test_is_bipartite_odd_cycle_false() {
1385        let g = triangle_graph();
1386        assert!(!is_bipartite(&g));
1387    }
1388    #[test]
1389    fn test_bipartite_coloring_consistent() {
1390        let mut g = Graph::new(4);
1391        g.add_undirected_edge(0, 1, 1.0);
1392        g.add_undirected_edge(1, 2, 1.0);
1393        g.add_undirected_edge(2, 3, 1.0);
1394        let colors = bipartite_coloring(&g).expect("should be bipartite");
1395        assert_ne!(colors[0], colors[1]);
1396        assert_ne!(colors[1], colors[2]);
1397        assert_ne!(colors[2], colors[3]);
1398    }
1399    #[test]
1400    fn test_bipartite_coloring_none_for_triangle() {
1401        assert!(bipartite_coloring(&triangle_graph()).is_none());
1402    }
1403    #[test]
1404    fn test_greedy_coloring_triangle_uses_3_colors() {
1405        let g = triangle_graph();
1406        let colors = greedy_coloring(&g);
1407        assert_eq!(colors.len(), 3);
1408        let k = chromatic_number_upper_bound(&colors);
1409        assert!(k >= 3, "K3 needs at least 3 colours, got {k}");
1410    }
1411    #[test]
1412    fn test_greedy_coloring_adjacent_different() {
1413        let mut g = Graph::new(4);
1414        g.add_undirected_edge(0, 1, 1.0);
1415        g.add_undirected_edge(1, 2, 1.0);
1416        g.add_undirected_edge(2, 3, 1.0);
1417        let colors = greedy_coloring(&g);
1418        assert_ne!(colors[0], colors[1]);
1419        assert_ne!(colors[1], colors[2]);
1420        assert_ne!(colors[2], colors[3]);
1421    }
1422    #[test]
1423    fn test_greedy_coloring_path_needs_2() {
1424        let mut g = Graph::new(4);
1425        g.add_undirected_edge(0, 1, 1.0);
1426        g.add_undirected_edge(1, 2, 1.0);
1427        g.add_undirected_edge(2, 3, 1.0);
1428        let colors = greedy_coloring(&g);
1429        let k = chromatic_number_upper_bound(&colors);
1430        assert!(k <= 2, "path needs at most 2 colours, got {k}");
1431    }
1432    #[test]
1433    fn test_kahn_dag() {
1434        let mut g = Graph::new(4);
1435        g.add_edge(0, 1, 1.0);
1436        g.add_edge(0, 2, 1.0);
1437        g.add_edge(1, 3, 1.0);
1438        g.add_edge(2, 3, 1.0);
1439        let order = kahn_topological_sort(&g).expect("DAG should succeed");
1440        assert_eq!(order.len(), 4);
1441        let pos: Vec<usize> = {
1442            let mut p = vec![0usize; 4];
1443            for (i, &n) in order.iter().enumerate() {
1444                p[n] = i;
1445            }
1446            p
1447        };
1448        assert!(pos[0] < pos[1]);
1449        assert!(pos[0] < pos[2]);
1450        assert!(pos[1] < pos[3]);
1451    }
1452    #[test]
1453    fn test_kahn_cycle_returns_none() {
1454        let mut g = Graph::new(3);
1455        g.add_edge(0, 1, 1.0);
1456        g.add_edge(1, 2, 1.0);
1457        g.add_edge(2, 0, 1.0);
1458        assert!(kahn_topological_sort(&g).is_none());
1459    }
1460    #[test]
1461    fn test_eulerian_circuit_even_degrees() {
1462        let mut g = Graph::new(4);
1463        g.add_undirected_edge(0, 1, 1.0);
1464        g.add_undirected_edge(1, 2, 1.0);
1465        g.add_undirected_edge(2, 3, 1.0);
1466        g.add_undirected_edge(3, 0, 1.0);
1467        assert!(
1468            has_eulerian_circuit(&g),
1469            "square graph should have Eulerian circuit"
1470        );
1471    }
1472    #[test]
1473    fn test_no_eulerian_circuit_triangle() {
1474        let g = triangle_graph();
1475        assert!(has_eulerian_circuit(&g));
1476    }
1477    #[test]
1478    fn test_eulerian_path_odd_degree_nodes() {
1479        let mut g = Graph::new(3);
1480        g.add_edge(0, 1, 1.0);
1481        g.add_edge(1, 2, 1.0);
1482        let odd_count = (0..3).filter(|&u| !g.degree(u).is_multiple_of(2)).count();
1483        assert_eq!(odd_count, 2);
1484    }
1485    #[test]
1486    fn test_betweenness_centrality_path() {
1487        let mut g = Graph::new(3);
1488        g.add_edge(0, 1, 1.0);
1489        g.add_edge(1, 2, 1.0);
1490        let bc = betweenness_centrality(&g);
1491        assert!(bc[1] > bc[0], "bc[1]={}, bc[0]={}", bc[1], bc[0]);
1492        assert!(bc[1] > bc[2], "bc[1]={}, bc[2]={}", bc[1], bc[2]);
1493    }
1494    #[test]
1495    fn test_betweenness_centrality_all_finite() {
1496        let g = triangle_graph();
1497        let bc = betweenness_centrality(&g);
1498        assert!(bc.iter().all(|v| v.is_finite()));
1499    }
1500    #[test]
1501    fn test_closeness_centrality_central_node() {
1502        let mut g = Graph::new(4);
1503        g.add_edge(0, 1, 1.0);
1504        g.add_edge(0, 2, 1.0);
1505        g.add_edge(0, 3, 1.0);
1506        let cc = closeness_centrality(&g);
1507        assert!(cc[0] >= cc[1], "cc[0]={} cc[1]={}", cc[0], cc[1]);
1508        assert!(cc[0] >= cc[2], "cc[0]={} cc[2]={}", cc[0], cc[2]);
1509    }
1510    #[test]
1511    fn test_graph_density_complete_directed() {
1512        let mut g = Graph::new(3);
1513        for i in 0..3 {
1514            for j in 0..3 {
1515                if i != j {
1516                    g.add_edge(i, j, 1.0);
1517                }
1518            }
1519        }
1520        let d = graph_density(&g);
1521        assert!((d - 1.0).abs() < 1e-12, "density={d}");
1522    }
1523    #[test]
1524    fn test_graph_density_empty() {
1525        let g = Graph::new(4);
1526        assert_eq!(graph_density(&g), 0.0);
1527    }
1528    #[test]
1529    fn test_bipartite_matching_simple() {
1530        let mut g = Graph::new(4);
1531        g.add_edge(0, 2, 1.0);
1532        g.add_edge(0, 3, 1.0);
1533        g.add_edge(1, 3, 1.0);
1534        let matching = bipartite_matching(&g, &[0, 1], &[2, 3]);
1535        assert_eq!(matching.len(), 2, "matching={:?}", matching);
1536    }
1537    #[test]
1538    fn test_bipartite_matching_no_edges() {
1539        let g = Graph::new(4);
1540        let matching = bipartite_matching(&g, &[0, 1], &[2, 3]);
1541        assert!(matching.is_empty());
1542    }
1543    #[test]
1544    fn test_local_clustering_complete_triangle() {
1545        let g = triangle_graph();
1546        let c = local_clustering(&g, 0);
1547        assert!((c - 1.0).abs() < 1e-10, "clustering={c}");
1548    }
1549    #[test]
1550    fn test_average_clustering_path_graph() {
1551        let mut g = Graph::new(3);
1552        g.add_undirected_edge(0, 1, 1.0);
1553        g.add_undirected_edge(1, 2, 1.0);
1554        let avg = average_clustering(&g);
1555        assert!(avg.is_finite() && avg >= 0.0);
1556    }
1557    #[test]
1558    fn test_dijkstra_single_node() {
1559        let g = Graph::new(1);
1560        let d = dijkstra(&g, 0);
1561        assert_eq!(d.len(), 1);
1562        assert_eq!(d[0], 0.0);
1563    }
1564    #[test]
1565    fn test_dijkstra_self_loop_ignored() {
1566        let mut g = Graph::new(2);
1567        g.add_edge(0, 0, 5.0);
1568        g.add_edge(0, 1, 2.0);
1569        let d = dijkstra(&g, 0);
1570        assert_eq!(d[0], 0.0);
1571        assert_eq!(d[1], 2.0);
1572    }
1573    #[test]
1574    fn test_bellman_ford_unreachable() {
1575        let mut g = Graph::new(3);
1576        g.add_edge(0, 1, 2.0);
1577        let d = bellman_ford(&g, 0).unwrap();
1578        assert!(d[2].is_infinite());
1579    }
1580    #[test]
1581    fn test_astar_same_result_as_dijkstra() {
1582        let g = triangle_graph();
1583        let dijk = dijkstra(&g, 0);
1584        let zero_h = |_: usize| 0.0;
1585        if let Some((d, _)) = astar(&g, 0, 2, &zero_h) {
1586            assert!((d - dijk[2]).abs() < 1e-12, "A* and Dijkstra disagree");
1587        }
1588    }
1589    #[test]
1590    fn test_floyd_warshall_symmetric_undirected() {
1591        let g = triangle_graph();
1592        let dist = floyd_warshall(&g);
1593        for i in 0..3 {
1594            for j in 0..3 {
1595                assert!((dist[i][j] - dist[j][i]).abs() < 1e-10);
1596            }
1597        }
1598    }
1599    #[test]
1600    fn test_topological_sort_empty_graph() {
1601        let g = Graph::new(0);
1602        let order = topological_sort(&g).expect("empty graph is a DAG");
1603        assert!(order.is_empty());
1604    }
1605    #[test]
1606    fn test_tarjan_single_scc() {
1607        let mut g = Graph::new(3);
1608        g.add_edge(0, 1, 1.0);
1609        g.add_edge(1, 2, 1.0);
1610        g.add_edge(2, 0, 1.0);
1611        let sccs = tarjan_scc(&g);
1612        assert_eq!(sccs.len(), 1);
1613        assert_eq!(sccs[0].len(), 3);
1614    }
1615    #[test]
1616    fn test_tarjan_dag_each_node_own_scc() {
1617        let mut g = Graph::new(3);
1618        g.add_edge(0, 1, 1.0);
1619        g.add_edge(1, 2, 1.0);
1620        let sccs = tarjan_scc(&g);
1621        assert_eq!(sccs.len(), 3, "DAG: each node is its own SCC");
1622    }
1623    #[test]
1624    fn test_kosaraju_multiple_sccs() {
1625        let mut g = Graph::new(4);
1626        g.add_edge(0, 1, 1.0);
1627        g.add_edge(1, 0, 1.0);
1628        g.add_edge(2, 3, 1.0);
1629        g.add_edge(3, 2, 1.0);
1630        let sccs = kosaraju_scc(&g);
1631        assert_eq!(sccs.len(), 2);
1632    }
1633    #[test]
1634    fn test_kruskal_prim_agree() {
1635        let mut g = Graph::new(4);
1636        g.add_undirected_edge(0, 1, 2.0);
1637        g.add_undirected_edge(1, 2, 3.0);
1638        g.add_undirected_edge(2, 3, 1.0);
1639        g.add_undirected_edge(0, 3, 4.0);
1640        let w_k = mst_total_weight(&kruskal_mst(&g));
1641        let w_p: f64 = prim_mst(&g).iter().map(|&(_, _, w)| w).sum();
1642        assert!((w_k - w_p).abs() < 1e-10, "Kruskal={w_k} Prim={w_p}");
1643    }
1644    #[test]
1645    fn test_max_flow_new_parallel() {
1646        let mut g = Graph::new(4);
1647        g.add_edge(0, 1, 3.0);
1648        g.add_edge(0, 2, 2.0);
1649        g.add_edge(1, 3, 2.0);
1650        g.add_edge(2, 3, 3.0);
1651        let flow = max_flow_edmonds_karp(&g, 0, 3);
1652        assert!((flow - 4.0).abs() < 1e-9, "flow={flow}");
1653    }
1654    #[test]
1655    fn test_max_flow_no_path() {
1656        let g = Graph::new(3);
1657        let flow = max_flow_edmonds_karp(&g, 0, 2);
1658        assert_eq!(flow, 0.0);
1659    }
1660    #[test]
1661    fn test_max_flow_bottleneck() {
1662        let mut g = Graph::new(3);
1663        g.add_edge(0, 1, 5.0);
1664        g.add_edge(1, 2, 2.0);
1665        let flow = max_flow_edmonds_karp(&g, 0, 2);
1666        assert!((flow - 2.0).abs() < 1e-9, "flow limited by bottleneck=2");
1667    }
1668    #[test]
1669    fn test_is_dag_linear_chain() {
1670        let mut g = Graph::new(3);
1671        g.add_edge(0, 1, 1.0);
1672        g.add_edge(1, 2, 1.0);
1673        assert!(is_dag(&g));
1674    }
1675    #[test]
1676    fn test_is_dag_three_cycle() {
1677        let mut g = Graph::new(3);
1678        g.add_edge(0, 1, 1.0);
1679        g.add_edge(1, 2, 1.0);
1680        g.add_edge(2, 0, 1.0);
1681        assert!(!is_dag(&g));
1682    }
1683    #[test]
1684    fn test_count_back_edges_linear() {
1685        let mut g = Graph::new(3);
1686        g.add_edge(0, 1, 1.0);
1687        g.add_edge(0, 2, 1.0);
1688        assert_eq!(count_back_edges(&g), 0);
1689    }
1690    #[test]
1691    fn test_count_back_edges_two_cycle() {
1692        let mut g = Graph::new(2);
1693        g.add_edge(0, 1, 1.0);
1694        g.add_edge(1, 0, 1.0);
1695        assert!(count_back_edges(&g) >= 1);
1696    }
1697    #[test]
1698    fn test_path_reconstruct_chain() {
1699        let prev: Vec<Option<usize>> = vec![None, Some(0), Some(1)];
1700        let path = path_reconstruct(&prev, 2);
1701        assert_eq!(path, vec![0, 1, 2]);
1702    }
1703    #[test]
1704    fn test_method_scc_single_cycle() {
1705        let mut g = Graph::new(3);
1706        g.add_edge(0, 1, 1.0);
1707        g.add_edge(1, 2, 1.0);
1708        g.add_edge(2, 0, 1.0);
1709        let sccs = g.strongly_connected_components();
1710        assert_eq!(sccs.len(), 1, "expected 1 SCC for a 3-cycle");
1711        let mut flat: Vec<usize> = sccs.into_iter().flatten().collect();
1712        flat.sort_unstable();
1713        assert_eq!(flat, vec![0, 1, 2]);
1714    }
1715    #[test]
1716    fn test_method_scc_dag_all_singleton() {
1717        let mut g = Graph::new(4);
1718        g.add_edge(0, 1, 1.0);
1719        g.add_edge(1, 2, 1.0);
1720        g.add_edge(2, 3, 1.0);
1721        let sccs = g.strongly_connected_components();
1722        assert_eq!(sccs.len(), 4);
1723    }
1724    #[test]
1725    fn test_method_mst_triangle() {
1726        let mut g = Graph::new(3);
1727        g.add_undirected_edge(0, 1, 1.0);
1728        g.add_undirected_edge(1, 2, 2.0);
1729        g.add_undirected_edge(0, 2, 5.0);
1730        let mst = g.minimum_spanning_tree();
1731        assert_eq!(mst.len(), 2);
1732        let total: f64 = mst.iter().map(|&(_, _, w)| w).sum();
1733        assert!((total - 3.0).abs() < 1e-9, "MST weight={}", total);
1734    }
1735    #[test]
1736    fn test_method_mst_star_graph() {
1737        let mut g = Graph::new(4);
1738        g.add_undirected_edge(0, 1, 1.0);
1739        g.add_undirected_edge(0, 2, 1.0);
1740        g.add_undirected_edge(0, 3, 1.0);
1741        let mst = g.minimum_spanning_tree();
1742        assert_eq!(mst.len(), 3);
1743    }
1744    #[test]
1745    fn test_method_max_flow_simple() {
1746        let mut g = Graph::new(3);
1747        g.add_edge(0, 1, 10.0);
1748        g.add_edge(1, 2, 5.0);
1749        let flow = g.max_flow(0, 2);
1750        assert!((flow - 5.0).abs() < 1e-9, "flow limited by bottleneck=5");
1751    }
1752    #[test]
1753    fn test_method_max_flow_no_path() {
1754        let g = Graph::new(4);
1755        let flow = g.max_flow(0, 3);
1756        assert_eq!(flow, 0.0);
1757    }
1758    #[test]
1759    fn test_method_betweenness_path_graph() {
1760        let mut g = Graph::new(4);
1761        g.add_edge(0, 1, 1.0);
1762        g.add_edge(1, 2, 1.0);
1763        g.add_edge(2, 3, 1.0);
1764        let bc = g.betweenness_centrality();
1765        assert!(bc[0].abs() < 1e-9, "bc[0]={}", bc[0]);
1766        assert!(bc[3].abs() < 1e-9, "bc[3]={}", bc[3]);
1767        assert!(bc[1] > 0.0, "bc[1]={}", bc[1]);
1768        assert!(bc[2] > 0.0, "bc[2]={}", bc[2]);
1769    }
1770    #[test]
1771    fn test_method_betweenness_single_node() {
1772        let g = Graph::new(1);
1773        let bc = g.betweenness_centrality();
1774        assert_eq!(bc.len(), 1);
1775        assert_eq!(bc[0], 0.0);
1776    }
1777    #[test]
1778    fn test_method_betweenness_hub_high_centrality() {
1779        let mut g = Graph::new(5);
1780        for i in 1..5 {
1781            g.add_undirected_edge(0, i, 1.0);
1782        }
1783        let bc = g.betweenness_centrality();
1784        for i in 1..5 {
1785            assert!(bc[0] >= bc[i], "hub bc={} leaf[{}] bc={}", bc[0], i, bc[i]);
1786        }
1787    }
1788}