Skip to main content

oxilean_std/graph/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use std::collections::{BinaryHeap, HashSet, VecDeque};
6
7/// Evaluates the Tutte polynomial T(G; x, y) at a given point using deletion-contraction.
8///
9/// This implementation uses memoization over edge subsets (exponential, for small graphs).
10/// T(G; x, y) encodes chromatic, reliability, and flow polynomials as specializations.
11#[allow(dead_code)]
12pub struct TuttePolynomialEval {
13    /// Edge list as (u, v) pairs.
14    edges: Vec<(usize, usize)>,
15    /// Number of vertices.
16    n: usize,
17}
18#[allow(dead_code)]
19impl TuttePolynomialEval {
20    /// Create from an undirected graph's edge list.
21    pub fn from_graph(graph: &UndirectedGraph) -> Self {
22        let mut edges = Vec::new();
23        for u in 0..graph.n {
24            for &v in &graph.adj[u] {
25                if u < v {
26                    edges.push((u, v));
27                }
28            }
29        }
30        Self { edges, n: graph.n }
31    }
32    /// Evaluate T(G; x, y) via deletion-contraction recursion (exponential time).
33    /// Base cases: empty edge set → x^(k-1) where k = components; single loops → y.
34    pub fn evaluate(&self, x: f64, y: f64) -> f64 {
35        self.tutte_rec(&self.edges.clone(), self.n, x, y)
36    }
37    fn components(n: usize, edges: &[(usize, usize)]) -> usize {
38        let max_v = edges.iter().flat_map(|&(u, v)| [u, v]).max().unwrap_or(0);
39        let sz = max_v.max(n.saturating_sub(1)) + 1;
40        let mut parent: Vec<usize> = (0..sz).collect();
41        fn find(parent: &mut Vec<usize>, a: usize) -> usize {
42            if parent[a] != a {
43                parent[a] = find(parent, parent[a]);
44            }
45            parent[a]
46        }
47        for &(u, v) in edges {
48            if u == v {
49                continue;
50            }
51            let ru = find(&mut parent, u);
52            let rv = find(&mut parent, v);
53            if ru != rv {
54                parent[ru] = rv;
55            }
56        }
57        let count_n = n.min(sz);
58        (0..count_n).filter(|&i| find(&mut parent, i) == i).count()
59    }
60    /// Count distinct vertices referenced in an edge list.
61    fn vertex_count(edges: &[(usize, usize)], base_n: usize) -> usize {
62        let mut seen: HashSet<usize> = HashSet::new();
63        for &(u, v) in edges {
64            seen.insert(u);
65            seen.insert(v);
66        }
67        seen.len().max(base_n)
68    }
69    fn tutte_rec(&self, edges: &[(usize, usize)], n: usize, x: f64, y: f64) -> f64 {
70        if edges.is_empty() {
71            return x.powi((n as i32) - 1);
72        }
73        let e = edges[0];
74        let rest: Vec<(usize, usize)> = edges[1..].to_vec();
75        let is_loop = e.0 == e.1;
76        if is_loop {
77            return y * self.tutte_rec(&rest, n, x, y);
78        }
79        let c_with = Self::components(n, edges);
80        let c_without = Self::components(n, &rest);
81        let is_bridge = c_without > c_with;
82        if is_bridge {
83            let contracted = self.contract_edge(&rest, e);
84            let new_n = n.saturating_sub(1).max(1);
85            return x * self.tutte_rec(&contracted, new_n, x, y);
86        }
87        let contracted = self.contract_edge(&rest, e);
88        let new_n = n.saturating_sub(1).max(1);
89        let del = self.tutte_rec(&rest, n, x, y);
90        let con = self.tutte_rec(&contracted, new_n, x, y);
91        del + con
92    }
93    /// Contract edge e = (u, v): merge v into u in all remaining edges.
94    fn contract_edge(&self, edges: &[(usize, usize)], e: (usize, usize)) -> Vec<(usize, usize)> {
95        let (u, v) = e;
96        edges
97            .iter()
98            .map(|&(a, b)| {
99                let a2 = if a == v { u } else { a };
100                let b2 = if b == v { u } else { b };
101                (a2, b2)
102            })
103            .collect()
104    }
105}
106/// Szemerédi regularity partition: partitions vertices into roughly equal parts
107/// and reports epsilon-regular pairs.
108///
109/// This is a simplified heuristic (not the full constructive proof), suitable for
110/// demonstration and testing purposes.
111#[allow(dead_code)]
112pub struct SzemerédiRegularityLemma {
113    /// The graph to partition.
114    pub graph: UndirectedGraph,
115    /// Regularity parameter ε.
116    pub epsilon: f64,
117}
118#[allow(dead_code)]
119impl SzemerédiRegularityLemma {
120    /// Create a new regularity lemma instance.
121    pub fn new(graph: UndirectedGraph, epsilon: f64) -> Self {
122        Self { graph, epsilon }
123    }
124    /// Edge density between two disjoint subsets A and B.
125    pub fn density(&self, a: &[usize], b: &[usize]) -> f64 {
126        let b_set: HashSet<usize> = b.iter().copied().collect();
127        let edges: usize = a
128            .iter()
129            .map(|&u| {
130                self.graph.adj[u]
131                    .iter()
132                    .filter(|v| b_set.contains(*v))
133                    .count()
134            })
135            .sum();
136        let denom = a.len() * b.len();
137        if denom == 0 {
138            0.0
139        } else {
140            edges as f64 / denom as f64
141        }
142    }
143    /// Check if (A, B) is an epsilon-regular pair.
144    /// A pair is ε-regular if for all A' ⊆ A, B' ⊆ B with |A'| ≥ ε|A|, |B'| ≥ ε|B|,
145    /// |d(A', B') - d(A, B)| ≤ ε. Here we check a simplified version using halved subsets.
146    pub fn is_regular_pair(&self, a: &[usize], b: &[usize]) -> bool {
147        let d = self.density(a, b);
148        let a_half: Vec<usize> = a[..a.len() / 2].to_vec();
149        let b_half: Vec<usize> = b[..b.len() / 2].to_vec();
150        let d_sub = self.density(&a_half, &b_half);
151        (d - d_sub).abs() <= self.epsilon
152    }
153    /// Partition vertices into k roughly equal parts and return the partition.
154    pub fn partition(&self, k: usize) -> Vec<Vec<usize>> {
155        let n = self.graph.n;
156        let k = k.max(1);
157        let mut parts: Vec<Vec<usize>> = vec![vec![]; k];
158        for v in 0..n {
159            parts[v % k].push(v);
160        }
161        parts
162    }
163    /// Run the regularity lemma: partition into k parts and count regular pairs.
164    /// Returns (partition, number_of_regular_pairs).
165    pub fn run(&self, k: usize) -> (Vec<Vec<usize>>, usize) {
166        let parts = self.partition(k);
167        let mut regular_pairs = 0usize;
168        for i in 0..parts.len() {
169            for j in (i + 1)..parts.len() {
170                if self.is_regular_pair(&parts[i], &parts[j]) {
171                    regular_pairs += 1;
172                }
173            }
174        }
175        (parts, regular_pairs)
176    }
177}
178/// Greedy treewidth heuristic using the min-fill elimination ordering.
179///
180/// The min-fill heuristic eliminates the vertex that adds the fewest fill edges
181/// (edges between non-adjacent neighbors). This gives an upper bound on treewidth.
182#[allow(dead_code)]
183pub struct TreewidthHeuristic {
184    /// Adjacency sets (mutable working copy).
185    adj: Vec<HashSet<usize>>,
186    /// Number of vertices.
187    n: usize,
188}
189#[allow(dead_code)]
190impl TreewidthHeuristic {
191    /// Create from an undirected graph.
192    pub fn new(graph: &UndirectedGraph) -> Self {
193        Self {
194            adj: graph.adj.clone(),
195            n: graph.n,
196        }
197    }
198    /// Count fill edges needed to eliminate vertex v: pairs of non-adjacent neighbors.
199    fn fill_count(&self, v: usize) -> usize {
200        let neighbors: Vec<usize> = self.adj[v].iter().copied().collect();
201        let mut fill = 0usize;
202        for i in 0..neighbors.len() {
203            for j in (i + 1)..neighbors.len() {
204                let a = neighbors[i];
205                let b = neighbors[j];
206                if !self.adj[a].contains(&b) {
207                    fill += 1;
208                }
209            }
210        }
211        fill
212    }
213    /// Run min-fill elimination. Returns (elimination_order, upper_bound_on_treewidth).
214    pub fn run(&mut self) -> (Vec<usize>, usize) {
215        let mut eliminated = vec![false; self.n];
216        let mut order = Vec::with_capacity(self.n);
217        let mut tw_bound = 0usize;
218        for _ in 0..self.n {
219            let v = (0..self.n)
220                .filter(|&u| !eliminated[u])
221                .min_by_key(|&u| self.fill_count(u))
222                .expect(
223                    "at least one non-eliminated vertex exists: loop runs n times for n vertices",
224                );
225            let deg = self.adj[v].len();
226            tw_bound = tw_bound.max(deg);
227            let neighbors: Vec<usize> = self.adj[v].iter().copied().collect();
228            for i in 0..neighbors.len() {
229                for j in (i + 1)..neighbors.len() {
230                    let a = neighbors[i];
231                    let b = neighbors[j];
232                    if a != b {
233                        self.adj[a].insert(b);
234                        self.adj[b].insert(a);
235                    }
236                }
237            }
238            for &u in &neighbors {
239                self.adj[u].remove(&v);
240            }
241            self.adj[v].clear();
242            eliminated[v] = true;
243            order.push(v);
244        }
245        (order, tw_bound)
246    }
247}
248/// A simple directed graph with Rust-level algorithms.
249#[derive(Clone, Debug)]
250pub struct DiGraph {
251    /// Number of vertices (0..n).
252    pub n: usize,
253    /// Adjacency list: adj[u] = list of (v, weight) neighbors.
254    pub adj: Vec<Vec<(usize, i64)>>,
255}
256impl DiGraph {
257    /// Create a new directed graph with n vertices.
258    pub fn new(n: usize) -> Self {
259        Self {
260            n,
261            adj: vec![vec![]; n],
262        }
263    }
264    /// Add a directed edge u → v with weight w.
265    pub fn add_edge(&mut self, u: usize, v: usize, w: i64) {
266        self.adj[u].push((v, w));
267    }
268    /// BFS from source s; returns distances (usize::MAX if unreachable).
269    pub fn bfs(&self, s: usize) -> Vec<usize> {
270        let mut dist = vec![usize::MAX; self.n];
271        let mut queue = VecDeque::new();
272        dist[s] = 0;
273        queue.push_back(s);
274        while let Some(u) = queue.pop_front() {
275            for &(v, _) in &self.adj[u] {
276                if dist[v] == usize::MAX {
277                    dist[v] = dist[u] + 1;
278                    queue.push_back(v);
279                }
280            }
281        }
282        dist
283    }
284    /// DFS from source s; returns DFS finish order.
285    pub fn dfs(&self, s: usize) -> Vec<usize> {
286        let mut visited = vec![false; self.n];
287        let mut order = Vec::new();
288        self.dfs_visit(s, &mut visited, &mut order);
289        order
290    }
291    fn dfs_visit(&self, u: usize, visited: &mut Vec<bool>, order: &mut Vec<usize>) {
292        if visited[u] {
293            return;
294        }
295        visited[u] = true;
296        for &(v, _) in &self.adj[u] {
297            self.dfs_visit(v, visited, order);
298        }
299        order.push(u);
300    }
301    /// Topological sort (Kahn's algorithm). Returns None if cycle exists.
302    pub fn topo_sort(&self) -> Option<Vec<usize>> {
303        let mut in_degree = vec![0usize; self.n];
304        for u in 0..self.n {
305            for &(v, _) in &self.adj[u] {
306                in_degree[v] += 1;
307            }
308        }
309        let mut queue: VecDeque<usize> = (0..self.n).filter(|&u| in_degree[u] == 0).collect();
310        let mut order = Vec::new();
311        while let Some(u) = queue.pop_front() {
312            order.push(u);
313            for &(v, _) in &self.adj[u] {
314                in_degree[v] -= 1;
315                if in_degree[v] == 0 {
316                    queue.push_back(v);
317                }
318            }
319        }
320        if order.len() == self.n {
321            Some(order)
322        } else {
323            None
324        }
325    }
326    /// Strongly connected components (Kosaraju's algorithm).
327    pub fn scc(&self) -> Vec<Vec<usize>> {
328        let mut visited = vec![false; self.n];
329        let mut finish_order = Vec::new();
330        for u in 0..self.n {
331            if !visited[u] {
332                self.dfs_visit(u, &mut visited, &mut finish_order);
333            }
334        }
335        let mut transposed = DiGraph::new(self.n);
336        for u in 0..self.n {
337            for &(v, w) in &self.adj[u] {
338                transposed.add_edge(v, u, w);
339            }
340        }
341        let mut visited2 = vec![false; self.n];
342        let mut components = Vec::new();
343        for &u in finish_order.iter().rev() {
344            if !visited2[u] {
345                let mut comp = Vec::new();
346                transposed.dfs_visit(u, &mut visited2, &mut comp);
347                components.push(comp);
348            }
349        }
350        components
351    }
352    /// Dijkstra's shortest paths from source s (non-negative weights only).
353    /// Returns (dist, parent) where dist[v] = shortest distance, parent[v] = predecessor.
354    pub fn dijkstra(&self, s: usize) -> (Vec<i64>, Vec<Option<usize>>) {
355        use super::functions::*;
356        use std::cmp::Reverse;
357        let inf = i64::MAX / 2;
358        let mut dist = vec![inf; self.n];
359        let mut parent = vec![None; self.n];
360        dist[s] = 0;
361        let mut heap = BinaryHeap::new();
362        heap.push(Reverse((0i64, s)));
363        while let Some(Reverse((d, u))) = heap.pop() {
364            if d > dist[u] {
365                continue;
366            }
367            for &(v, w) in &self.adj[u] {
368                let nd = dist[u] + w;
369                if nd < dist[v] {
370                    dist[v] = nd;
371                    parent[v] = Some(u);
372                    heap.push(Reverse((nd, v)));
373                }
374            }
375        }
376        (dist, parent)
377    }
378    /// Bellman-Ford shortest paths from source s (allows negative weights).
379    /// Returns None if negative cycle is reachable.
380    pub fn bellman_ford(&self, s: usize) -> Option<Vec<i64>> {
381        let inf = i64::MAX / 2;
382        let mut dist = vec![inf; self.n];
383        dist[s] = 0;
384        for _ in 0..self.n - 1 {
385            let mut changed = false;
386            for u in 0..self.n {
387                if dist[u] == inf {
388                    continue;
389                }
390                for &(v, w) in &self.adj[u] {
391                    if dist[u] + w < dist[v] {
392                        dist[v] = dist[u] + w;
393                        changed = true;
394                    }
395                }
396            }
397            if !changed {
398                break;
399            }
400        }
401        for u in 0..self.n {
402            if dist[u] == inf {
403                continue;
404            }
405            for &(v, w) in &self.adj[u] {
406                if dist[u] + w < dist[v] {
407                    return None;
408                }
409            }
410        }
411        Some(dist)
412    }
413    /// Floyd-Warshall all-pairs shortest paths.
414    /// Returns distance matrix (i64::MAX/2 = unreachable).
415    pub fn floyd_warshall(&self) -> Vec<Vec<i64>> {
416        let inf = i64::MAX / 2;
417        let mut d = vec![vec![inf; self.n]; self.n];
418        for u in 0..self.n {
419            d[u][u] = 0;
420            for &(v, w) in &self.adj[u] {
421                d[u][v] = d[u][v].min(w);
422            }
423        }
424        for k in 0..self.n {
425            for i in 0..self.n {
426                for j in 0..self.n {
427                    if d[i][k] < inf && d[k][j] < inf {
428                        d[i][j] = d[i][j].min(d[i][k] + d[k][j]);
429                    }
430                }
431            }
432        }
433        d
434    }
435    /// Is this graph a DAG?
436    pub fn is_dag(&self) -> bool {
437        self.topo_sort().is_some()
438    }
439    /// Count vertices reachable from s.
440    pub fn reachable_count(&self, s: usize) -> usize {
441        let dist = self.bfs(s);
442        dist.iter().filter(|&&d| d != usize::MAX).count()
443    }
444}
445/// An undirected simple graph (represented as adjacency sets for simplicity).
446#[derive(Clone, Debug, Default)]
447pub struct UndirectedGraph {
448    /// Number of vertices.
449    pub n: usize,
450    /// Adjacency sets: adj[u] = set of neighbors
451    pub adj: Vec<HashSet<usize>>,
452}
453impl UndirectedGraph {
454    /// Create with n vertices.
455    pub fn new(n: usize) -> Self {
456        Self {
457            n,
458            adj: vec![HashSet::new(); n],
459        }
460    }
461    /// Add undirected edge.
462    pub fn add_edge(&mut self, u: usize, v: usize) {
463        if u != v {
464            self.adj[u].insert(v);
465            self.adj[v].insert(u);
466        }
467    }
468    /// BFS from source s.
469    pub fn bfs(&self, s: usize) -> Vec<usize> {
470        let mut dist = vec![usize::MAX; self.n];
471        let mut queue = VecDeque::new();
472        dist[s] = 0;
473        queue.push_back(s);
474        while let Some(u) = queue.pop_front() {
475            for &v in &self.adj[u] {
476                if dist[v] == usize::MAX {
477                    dist[v] = dist[u] + 1;
478                    queue.push_back(v);
479                }
480            }
481        }
482        dist
483    }
484    /// Is the graph connected?
485    pub fn is_connected(&self) -> bool {
486        if self.n == 0 {
487            return true;
488        }
489        let dist = self.bfs(0);
490        dist.iter().all(|&d| d != usize::MAX)
491    }
492    /// Number of connected components.
493    pub fn num_components(&self) -> usize {
494        let mut visited = vec![false; self.n];
495        let mut count = 0;
496        for u in 0..self.n {
497            if !visited[u] {
498                count += 1;
499                let mut stack = vec![u];
500                while let Some(v) = stack.pop() {
501                    if visited[v] {
502                        continue;
503                    }
504                    visited[v] = true;
505                    for &w in &self.adj[v] {
506                        if !visited[w] {
507                            stack.push(w);
508                        }
509                    }
510                }
511            }
512        }
513        count
514    }
515    /// Is the graph bipartite? Returns Some(coloring) or None.
516    pub fn bipartite_coloring(&self) -> Option<Vec<usize>> {
517        let mut color = vec![usize::MAX; self.n];
518        for s in 0..self.n {
519            if color[s] != usize::MAX {
520                continue;
521            }
522            color[s] = 0;
523            let mut queue = VecDeque::new();
524            queue.push_back(s);
525            while let Some(u) = queue.pop_front() {
526                for &v in &self.adj[u] {
527                    if color[v] == usize::MAX {
528                        color[v] = 1 - color[u];
529                        queue.push_back(v);
530                    } else if color[v] == color[u] {
531                        return None;
532                    }
533                }
534            }
535        }
536        Some(color)
537    }
538    /// Is the graph bipartite?
539    pub fn is_bipartite(&self) -> bool {
540        self.bipartite_coloring().is_some()
541    }
542    /// Check if all vertex degrees are even (necessary for Eulerian circuit).
543    pub fn all_degrees_even(&self) -> bool {
544        (0..self.n).all(|u| self.adj[u].len() % 2 == 0)
545    }
546    /// Check if graph has an Eulerian circuit (connected + all even degrees).
547    pub fn has_eulerian_circuit(&self) -> bool {
548        self.is_connected() && self.all_degrees_even()
549    }
550    /// Greedy graph coloring (returns number of colors used and coloring).
551    pub fn greedy_coloring(&self) -> (usize, Vec<usize>) {
552        let mut color = vec![usize::MAX; self.n];
553        let mut max_color = 0;
554        for u in 0..self.n {
555            let neighbor_colors: HashSet<usize> = self.adj[u]
556                .iter()
557                .filter(|&&v| color[v] != usize::MAX)
558                .map(|&v| color[v])
559                .collect();
560            let mut c = 0;
561            while neighbor_colors.contains(&c) {
562                c += 1;
563            }
564            color[u] = c;
565            max_color = max_color.max(c);
566        }
567        (max_color + 1, color)
568    }
569    /// Check if a given coloring is proper.
570    pub fn is_proper_coloring(&self, coloring: &[usize]) -> bool {
571        for u in 0..self.n {
572            for &v in &self.adj[u] {
573                if coloring[u] == coloring[v] {
574                    return false;
575                }
576            }
577        }
578        true
579    }
580    /// Degree of vertex u.
581    pub fn degree(&self, u: usize) -> usize {
582        self.adj[u].len()
583    }
584    /// Number of edges.
585    pub fn edge_count(&self) -> usize {
586        (0..self.n).map(|u| self.adj[u].len()).sum::<usize>() / 2
587    }
588    /// Minimum spanning tree (Prim's algorithm, unweighted → any spanning tree).
589    /// Returns edges of the spanning tree.
590    pub fn spanning_tree(&self) -> Vec<(usize, usize)> {
591        if self.n == 0 {
592            return vec![];
593        }
594        let mut in_tree = vec![false; self.n];
595        let mut edges = Vec::new();
596        in_tree[0] = true;
597        for _ in 0..self.n - 1 {
598            let mut found = false;
599            for u in 0..self.n {
600                if !in_tree[u] {
601                    continue;
602                }
603                for &v in &self.adj[u] {
604                    if !in_tree[v] {
605                        in_tree[v] = true;
606                        edges.push((u, v));
607                        found = true;
608                        break;
609                    }
610                }
611                if found {
612                    break;
613                }
614            }
615        }
616        edges
617    }
618    /// Create complete graph K_n.
619    pub fn complete(n: usize) -> Self {
620        let mut g = Self::new(n);
621        for u in 0..n {
622            for v in (u + 1)..n {
623                g.add_edge(u, v);
624            }
625        }
626        g
627    }
628    /// Create path graph P_n (0-1-2-...-n-1).
629    pub fn path(n: usize) -> Self {
630        let mut g = Self::new(n);
631        for i in 0..n.saturating_sub(1) {
632            g.add_edge(i, i + 1);
633        }
634        g
635    }
636    /// Create cycle graph C_n.
637    pub fn cycle(n: usize) -> Self {
638        let mut g = Self::path(n);
639        if n >= 3 {
640            g.add_edge(n - 1, 0);
641        }
642        g
643    }
644    /// Create complete bipartite graph K_{m,n}.
645    pub fn complete_bipartite(m: usize, n: usize) -> Self {
646        let mut g = Self::new(m + n);
647        for u in 0..m {
648            for v in m..(m + n) {
649                g.add_edge(u, v);
650            }
651        }
652        g
653    }
654}
655/// Checks expansion properties of a graph using an approximation of the Cheeger constant.
656///
657/// The Cheeger constant h(G) = min_{S: 0<|S|≤n/2} |∂S| / |S|.
658/// This checker approximates it by trying all subsets up to a given size limit.
659#[allow(dead_code)]
660pub struct ExpanderChecker {
661    /// The underlying undirected graph.
662    pub graph: UndirectedGraph,
663}
664#[allow(dead_code)]
665impl ExpanderChecker {
666    /// Create from an existing undirected graph.
667    pub fn new(graph: UndirectedGraph) -> Self {
668        Self { graph }
669    }
670    /// Compute the edge boundary |∂S| = edges with exactly one endpoint in S.
671    pub fn edge_boundary(&self, subset: &[usize]) -> usize {
672        let in_set: HashSet<usize> = subset.iter().copied().collect();
673        let mut boundary = 0usize;
674        for &u in &in_set {
675            for &v in &self.graph.adj[u] {
676                if !in_set.contains(&v) {
677                    boundary += 1;
678                }
679            }
680        }
681        boundary
682    }
683    /// Approximate Cheeger constant: try all subsets of size 1..=n/2 (only feasible for small n).
684    /// Returns the approximate h(G) as a rational approximation (boundary/size).
685    pub fn approximate_cheeger(&self) -> f64 {
686        let n = self.graph.n;
687        if n == 0 {
688            return 0.0;
689        }
690        let half = n / 2;
691        let mut min_ratio = f64::INFINITY;
692        for u in 0..n {
693            let boundary = self.edge_boundary(&[u]);
694            let size = 1usize;
695            let ratio = boundary as f64 / size as f64;
696            if ratio < min_ratio {
697                min_ratio = ratio;
698            }
699        }
700        if half >= 2 {
701            for u in 0..n {
702                for v in (u + 1)..n {
703                    if u == v {
704                        continue;
705                    }
706                    let boundary = self.edge_boundary(&[u, v]);
707                    let ratio = boundary as f64 / 2.0;
708                    if ratio < min_ratio {
709                        min_ratio = ratio;
710                    }
711                }
712            }
713        }
714        if min_ratio.is_infinite() {
715            0.0
716        } else {
717            min_ratio
718        }
719    }
720    /// Returns true if the graph is an h-vertex expander: h(G) ≥ threshold.
721    pub fn is_expander(&self, threshold: f64) -> bool {
722        self.approximate_cheeger() >= threshold
723    }
724}
725/// Samples a simple graph from a graphon W : [0,1]² → [0,1].
726///
727/// A graphon is represented as a symmetric function. We discretize by n sample points.
728#[allow(dead_code)]
729pub struct GraphonSampler {
730    /// Number of vertices to sample.
731    pub n: usize,
732    /// The graphon function W(x, y) ∈ [0, 1]; must satisfy W(x,y) = W(y,x).
733    pub graphon: Box<dyn Fn(f64, f64) -> f64>,
734}
735#[allow(dead_code)]
736impl GraphonSampler {
737    /// Create a new sampler for a given graphon function and vertex count.
738    pub fn new(n: usize, graphon: Box<dyn Fn(f64, f64) -> f64>) -> Self {
739        Self { n, graphon }
740    }
741    /// Sample a graph: vertex i gets label (i+0.5)/n, edge (i,j) included with prob W(xi, xj).
742    /// Uses a deterministic threshold for reproducibility (threshold = 0.5).
743    pub fn sample_deterministic(&self) -> UndirectedGraph {
744        let mut g = UndirectedGraph::new(self.n);
745        for i in 0..self.n {
746            let xi = (i as f64 + 0.5) / self.n as f64;
747            for j in (i + 1)..self.n {
748                let xj = (j as f64 + 0.5) / self.n as f64;
749                let w = (self.graphon)(xi, xj);
750                if w > 0.5 {
751                    g.add_edge(i, j);
752                }
753            }
754        }
755        g
756    }
757    /// Sample by thresholding at a given value p ∈ [0, 1].
758    pub fn sample_at_threshold(&self, p: f64) -> UndirectedGraph {
759        let mut g = UndirectedGraph::new(self.n);
760        for i in 0..self.n {
761            let xi = (i as f64 + 0.5) / self.n as f64;
762            for j in (i + 1)..self.n {
763                let xj = (j as f64 + 0.5) / self.n as f64;
764                let w = (self.graphon)(xi, xj);
765                if w > p {
766                    g.add_edge(i, j);
767                }
768            }
769        }
770        g
771    }
772}