Skip to main content

oxilean_std/topology_ext/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use super::functions::*;
6use oxilean_kernel::{BinderInfo, Declaration, Environment, Expr, Level, Name};
7
8use std::collections::HashMap;
9
10/// Represents data of a fiber bundle E → B with fiber F.
11#[allow(dead_code)]
12#[derive(Debug, Clone)]
13pub struct FiberBundle {
14    /// Name of the total space.
15    pub total_space: String,
16    /// Name of the base space.
17    pub base_space: String,
18    /// Name of the fiber.
19    pub fiber: String,
20    /// Whether the bundle has a section.
21    pub has_section: bool,
22    /// Whether the bundle is trivial.
23    pub is_trivial: bool,
24    /// Structure group description.
25    pub structure_group: Option<String>,
26}
27#[allow(dead_code)]
28impl FiberBundle {
29    /// Creates a fiber bundle.
30    pub fn new(total: &str, base: &str, fiber: &str) -> Self {
31        FiberBundle {
32            total_space: total.to_string(),
33            base_space: base.to_string(),
34            fiber: fiber.to_string(),
35            has_section: false,
36            is_trivial: false,
37            structure_group: None,
38        }
39    }
40    /// Sets the structure group.
41    pub fn with_structure_group(mut self, g: &str) -> Self {
42        self.structure_group = Some(g.to_string());
43        self
44    }
45    /// Marks the bundle as trivial.
46    pub fn trivial(mut self) -> Self {
47        self.is_trivial = true;
48        self.has_section = true;
49        self
50    }
51    /// Returns the long exact sequence description.
52    pub fn les_description(&self) -> String {
53        format!(
54            "... → π_n({}) → π_n({}) → π_n({}) → π_{{n-1}}({}) → ...",
55            self.fiber, self.total_space, self.base_space, self.fiber
56        )
57    }
58    /// Checks if the bundle is principal (fiber = structure group).
59    pub fn is_principal(&self) -> bool {
60        match &self.structure_group {
61            Some(g) => *g == self.fiber,
62            None => false,
63        }
64    }
65}
66/// A finite abstract simplicial complex.
67///
68/// Stores a vertex set and a list of simplices (each simplex is a sorted
69/// list of vertex indices).  The structure does NOT automatically close under
70/// faces — callers must add all faces explicitly if needed.
71pub struct SimplicialComplex {
72    /// The set of vertex indices present in the complex.
73    pub vertices: Vec<usize>,
74    /// Each simplex is a sorted, duplicate-free list of vertex indices.
75    pub simplices: Vec<Vec<usize>>,
76}
77impl SimplicialComplex {
78    /// Create an empty simplicial complex with no vertices or simplices.
79    pub fn new() -> Self {
80        SimplicialComplex {
81            vertices: Vec::new(),
82            simplices: Vec::new(),
83        }
84    }
85    /// Add a simplex (given as a list of vertex indices) to the complex.
86    ///
87    /// The simplex is sorted and deduplicated before storage.  Any new
88    /// vertices are also recorded in `self.vertices`.
89    pub fn add_simplex(&mut self, mut simplex: Vec<usize>) {
90        simplex.sort_unstable();
91        simplex.dedup();
92        for &v in &simplex {
93            if !self.vertices.contains(&v) {
94                self.vertices.push(v);
95            }
96        }
97        self.vertices.sort_unstable();
98        if !self.simplices.contains(&simplex) {
99            self.simplices.push(simplex);
100        }
101    }
102    /// Return the list of simplices of a given dimension `dim`.
103    ///
104    /// A simplex has dimension `k` when it contains exactly `k+1` vertices.
105    pub(super) fn simplices_of_dim(&self, dim: usize) -> Vec<&Vec<usize>> {
106        self.simplices
107            .iter()
108            .filter(|s| s.len() == dim + 1)
109            .collect()
110    }
111    /// Compute the boundary matrix ∂_dim : C_dim → C_{dim-1}.
112    ///
113    /// Rows are indexed by (dim-1)-simplices; columns by dim-simplices.
114    /// Entry (i, j) is the sign ±1 that vertex elimination gives, or 0.
115    ///
116    /// Returns a matrix with `rows = #{(dim-1)-simplices}` and
117    /// `cols = #{dim-simplices}`.  Returns an empty matrix when `dim == 0`.
118    pub fn boundary_matrix(&self, dim: usize) -> Vec<Vec<i64>> {
119        if dim == 0 {
120            return Vec::new();
121        }
122        let lower = self.simplices_of_dim(dim - 1);
123        let upper = self.simplices_of_dim(dim);
124        let rows = lower.len();
125        let cols = upper.len();
126        let mut mat = vec![vec![0i64; cols]; rows];
127        for (col, sigma) in upper.iter().enumerate() {
128            for i in 0..sigma.len() {
129                let mut face = sigma[..i].to_vec();
130                face.extend_from_slice(&sigma[i + 1..]);
131                if let Some(row) = lower.iter().position(|f| **f == face) {
132                    mat[row][col] = if i % 2 == 0 { 1 } else { -1 };
133                }
134            }
135        }
136        mat
137    }
138    /// Compute the Euler characteristic χ = ∑_k (-1)^k * #{k-simplices}.
139    pub fn euler_characteristic(&self) -> i64 {
140        let max_dim = self.simplices.iter().map(|s| s.len()).max().unwrap_or(0);
141        let mut chi = 0i64;
142        for k in 0..max_dim {
143            let count = self.simplices_of_dim(k).len() as i64;
144            if k % 2 == 0 {
145                chi += count;
146            } else {
147                chi -= count;
148            }
149        }
150        chi
151    }
152    /// Compute Betti numbers β_0, …, β_{max_dim}.
153    ///
154    /// β_k = rank(H_k) = dim(ker ∂_k) - dim(im ∂_{k+1}).
155    ///
156    /// Uses a simplified rank computation over ℤ (Gaussian elimination over ℚ
157    /// would give exact ranks; here we use column reduction mod 2 for speed).
158    pub fn betti_numbers(&self, max_dim: usize) -> Vec<usize> {
159        homology_ranks(self, max_dim)
160    }
161}
162/// A persistence diagram point (birth, death).
163#[allow(dead_code)]
164#[derive(Debug, Clone, PartialEq)]
165pub struct PersistencePoint {
166    /// Birth time.
167    pub birth: f64,
168    /// Death time (f64::INFINITY for essential classes).
169    pub death: f64,
170    /// Homological dimension.
171    pub dimension: usize,
172}
173#[allow(dead_code)]
174impl PersistencePoint {
175    /// Creates a new persistence point.
176    pub fn new(birth: f64, death: f64, dimension: usize) -> Self {
177        PersistencePoint {
178            birth,
179            death,
180            dimension,
181        }
182    }
183    /// Returns the persistence (lifetime) of this class.
184    pub fn persistence(&self) -> f64 {
185        self.death - self.birth
186    }
187    /// Returns true if this is an essential (infinite lifetime) class.
188    pub fn is_essential(&self) -> bool {
189        self.death == f64::INFINITY
190    }
191    /// Returns the midpoint of the persistence interval.
192    pub fn midpoint(&self) -> f64 {
193        if self.is_essential() {
194            self.birth
195        } else {
196            (self.birth + self.death) / 2.0
197        }
198    }
199}
200/// A table of topological invariants computed for a simplicial complex.
201#[derive(Debug, Clone)]
202pub struct TopologicalInvariantTable {
203    /// Number of vertices, edges, triangles, tetrahedra, …
204    pub cell_counts: Vec<usize>,
205    /// Euler characteristic.
206    pub euler_characteristic: i64,
207    /// Betti numbers β_0, β_1, …
208    pub betti_numbers: Vec<usize>,
209}
210impl TopologicalInvariantTable {
211    /// Compute the invariant table for the given simplicial complex up to
212    /// the specified maximum dimension.
213    pub fn compute(complex: &SimplicialComplex, max_dim: usize) -> Self {
214        let cell_counts: Vec<usize> = (0..=max_dim)
215            .map(|k| complex.simplices_of_dim(k).len())
216            .collect();
217        let euler_characteristic = complex.euler_characteristic();
218        let betti_numbers = homology_ranks(complex, max_dim);
219        TopologicalInvariantTable {
220            cell_counts,
221            euler_characteristic,
222            betti_numbers,
223        }
224    }
225    /// Pretty-print the table to a String.
226    pub fn display(&self) -> String {
227        let mut out = String::new();
228        out.push_str("Topological Invariants\n");
229        out.push_str("======================\n");
230        for (k, &c) in self.cell_counts.iter().enumerate() {
231            out.push_str(&format!("  #{k}-cells    = {c}\n"));
232        }
233        out.push_str(&format!("  χ (Euler)   = {}\n", self.euler_characteristic));
234        for (k, &b) in self.betti_numbers.iter().enumerate() {
235            out.push_str(&format!("  β_{k}         = {b}\n"));
236        }
237        out
238    }
239}
240/// Computes characteristic class numbers for a vector bundle represented
241/// as its Chern character data.
242///
243/// Uses the splitting principle: the total Stiefel–Whitney class (mod 2) and
244/// Pontryagin class are computed from the eigenvalues of the curvature matrix.
245pub struct CharacteristicClassComputer {
246    /// Formal Chern roots (eigenvalues of curvature / 2πi), stored as `f64`.
247    pub chern_roots: Vec<f64>,
248}
249impl CharacteristicClassComputer {
250    /// Create from a list of formal Chern roots.
251    pub fn new(chern_roots: Vec<f64>) -> Self {
252        CharacteristicClassComputer { chern_roots }
253    }
254    /// Total Chern class c(E) = ∏(1 + x_i) expanded as elementary symmetric polynomials.
255    ///
256    /// Returns the coefficients c_0, c_1, …, c_n of the total Chern class.
257    pub fn total_chern_class(&self) -> Vec<f64> {
258        let n = self.chern_roots.len();
259        let mut c = vec![0.0f64; n + 1];
260        c[0] = 1.0;
261        for &x in &self.chern_roots {
262            for k in (1..=n).rev() {
263                c[k] += x * c[k - 1];
264            }
265        }
266        c
267    }
268    /// Chern character ch(E) = ∑ exp(x_i): returns partial sums up to degree `max_deg`.
269    pub fn chern_character(&self, max_deg: usize) -> Vec<f64> {
270        let mut ch = vec![0.0f64; max_deg + 1];
271        for &x in &self.chern_roots {
272            let mut power = 1.0f64;
273            let mut factorial = 1.0f64;
274            for k in 0..=max_deg {
275                if k > 0 {
276                    power *= x;
277                    factorial *= k as f64;
278                }
279                ch[k] += power / factorial;
280            }
281        }
282        ch
283    }
284    /// Euler characteristic (integral of the top Chern class over a manifold of the
285    /// same dimension).  For a rank-r bundle over a 2r-manifold this is c_r.
286    pub fn euler_number(&self) -> f64 {
287        let c = self.total_chern_class();
288        *c.last().unwrap_or(&0.0)
289    }
290}
291/// A page of a spectral sequence E_r^{p,q}.
292#[allow(dead_code)]
293#[derive(Debug, Clone)]
294pub struct SpectralSequencePage {
295    /// Page number r.
296    pub r: usize,
297    /// Data: sparse map from (p, q) to group description.
298    pub data: std::collections::HashMap<(i32, i32), String>,
299}
300#[allow(dead_code)]
301impl SpectralSequencePage {
302    /// Creates a new page.
303    pub fn new(r: usize) -> Self {
304        SpectralSequencePage {
305            r,
306            data: std::collections::HashMap::new(),
307        }
308    }
309    /// Sets E_r^{p,q} = group.
310    pub fn set(&mut self, p: i32, q: i32, group: &str) {
311        self.data.insert((p, q), group.to_string());
312    }
313    /// Returns E_r^{p,q} as a string.
314    pub fn get(&self, p: i32, q: i32) -> &str {
315        self.data.get(&(p, q)).map(|s| s.as_str()).unwrap_or("0")
316    }
317    /// Returns the total degree n entry: sum of E_r^{p,q} with p+q = n.
318    pub fn total_degree(&self, n: i32) -> Vec<&str> {
319        self.data
320            .iter()
321            .filter(|&(&(p, q), _)| p + q == n)
322            .map(|(_, v)| v.as_str())
323            .collect()
324    }
325    /// Returns the E_r differential d_r: E_r^{p,q} → E_r^{p+r, q-r+1}.
326    pub fn differential_target(&self, p: i32, q: i32) -> (i32, i32) {
327        (p + self.r as i32, q - self.r as i32 + 1)
328    }
329}
330/// A ball tree for an ultrametric space (non-Archimedean metric).
331///
332/// In an ultrametric space every ball is both open and closed, and any two balls
333/// are either disjoint or one contains the other.  This structure exploits that
334/// property to build a hierarchy of balls.
335pub struct UltrametricBallTree {
336    /// All points as `f64` values on the p-adic "number line" (approximation).
337    pub points: Vec<f64>,
338    /// The prime base for the ultrametric (p-adic context).
339    pub prime: u32,
340}
341impl UltrametricBallTree {
342    /// Create a new ball tree for the given points.
343    pub fn new(points: Vec<f64>, prime: u32) -> Self {
344        UltrametricBallTree { points, prime }
345    }
346    /// p-adic valuation of a rational approximation.
347    ///
348    /// Returns the largest k such that p^k divides the numerator of x (after
349    /// rounding to nearest integer).  Returns 0 for 0.
350    pub fn padic_valuation(&self, x: f64) -> i32 {
351        let n = x.round().abs() as u64;
352        if n == 0 {
353            return i32::MAX;
354        }
355        let p = self.prime as u64;
356        let mut val = 0i32;
357        let mut m = n;
358        while m % p == 0 {
359            val += 1;
360            m /= p;
361        }
362        val
363    }
364    /// The ultrametric distance d(x, y) = p^{-v_p(x-y)}.
365    pub fn ultrametric_dist(&self, x: f64, y: f64) -> f64 {
366        let diff = x - y;
367        if diff.abs() < f64::EPSILON {
368            return 0.0;
369        }
370        let v = self.padic_valuation(diff);
371        if v == i32::MAX {
372            0.0
373        } else {
374            (self.prime as f64).powi(-v)
375        }
376    }
377    /// Check the strong triangle inequality: d(x,z) ≤ max(d(x,y), d(y,z)).
378    pub fn check_ultrametric_inequality(&self, x: f64, y: f64, z: f64) -> bool {
379        let dxz = self.ultrametric_dist(x, z);
380        let dxy = self.ultrametric_dist(x, y);
381        let dyz = self.ultrametric_dist(y, z);
382        dxz <= dxy.max(dyz) + f64::EPSILON
383    }
384    /// Find all points in the ball B(center, radius) under the ultrametric.
385    pub fn ball(&self, center: f64, radius: f64) -> Vec<f64> {
386        self.points
387            .iter()
388            .filter(|&&p| self.ultrametric_dist(center, p) <= radius)
389            .copied()
390            .collect()
391    }
392}
393/// Represents a CW complex with cell attachments.
394#[allow(dead_code)]
395#[derive(Debug, Clone)]
396pub struct CWComplex {
397    /// Name of the complex.
398    pub name: String,
399    /// Cells by dimension: cells[k] = number of k-cells.
400    pub cells: Vec<u32>,
401}
402#[allow(dead_code)]
403impl CWComplex {
404    /// Creates a CW complex.
405    pub fn new(name: &str) -> Self {
406        CWComplex {
407            name: name.to_string(),
408            cells: Vec::new(),
409        }
410    }
411    /// Adds cells in dimension k.
412    pub fn add_cells(mut self, dim: usize, count: u32) -> Self {
413        while self.cells.len() <= dim {
414            self.cells.push(0);
415        }
416        self.cells[dim] += count;
417        self
418    }
419    /// Creates S^n.
420    pub fn sphere(n: usize) -> Self {
421        let mut cw = CWComplex::new(&format!("S^{n}"));
422        cw = cw.add_cells(0, 1);
423        cw = cw.add_cells(n, 1);
424        cw
425    }
426    /// Creates T^2 (torus).
427    pub fn torus() -> Self {
428        CWComplex::new("T^2")
429            .add_cells(0, 1)
430            .add_cells(1, 2)
431            .add_cells(2, 1)
432    }
433    /// Creates RP^2 (real projective plane).
434    pub fn rp2() -> Self {
435        CWComplex::new("RP^2")
436            .add_cells(0, 1)
437            .add_cells(1, 1)
438            .add_cells(2, 1)
439    }
440    /// Euler characteristic from cells (alternating sum).
441    pub fn euler_characteristic(&self) -> i64 {
442        self.cells
443            .iter()
444            .enumerate()
445            .map(|(k, &c)| {
446                let sign: i64 = if k % 2 == 0 { 1 } else { -1 };
447                sign * c as i64
448            })
449            .sum()
450    }
451    /// Dimension of the complex.
452    pub fn dimension(&self) -> Option<usize> {
453        self.cells
454            .iter()
455            .enumerate()
456            .rev()
457            .find(|(_, &c)| c > 0)
458            .map(|(k, _)| k)
459    }
460}
461/// A Morse function on a CW complex (discrete version).
462#[allow(dead_code)]
463#[derive(Debug, Clone)]
464pub struct DiscreteMorseFunction {
465    /// Number of cells in each dimension.
466    pub cells: Vec<usize>,
467    /// Critical cells in each dimension.
468    pub critical_cells: Vec<Vec<usize>>,
469    /// Gradient vector field: (cell_dim, cell_idx) -> (higher_dim, cell_idx).
470    pub gradient_pairs: Vec<(usize, usize, usize, usize)>,
471}
472#[allow(dead_code)]
473impl DiscreteMorseFunction {
474    /// Creates a discrete Morse function with given cell counts.
475    pub fn new(cells: Vec<usize>) -> Self {
476        let n = cells.len();
477        DiscreteMorseFunction {
478            cells,
479            critical_cells: vec![Vec::new(); n],
480            gradient_pairs: Vec::new(),
481        }
482    }
483    /// Marks cell (dim, idx) as critical.
484    pub fn mark_critical(&mut self, dim: usize, idx: usize) {
485        if dim < self.critical_cells.len() {
486            self.critical_cells[dim].push(idx);
487        }
488    }
489    /// Adds a gradient pair: cell of dim paired with cell of dim+1.
490    pub fn add_gradient_pair(&mut self, dim: usize, lower_idx: usize, upper_idx: usize) {
491        if dim + 1 < self.cells.len() {
492            self.gradient_pairs
493                .push((dim, lower_idx, dim + 1, upper_idx));
494        }
495    }
496    /// Returns the number of critical cells in each dimension.
497    pub fn morse_inequalities_lhs(&self) -> Vec<usize> {
498        self.critical_cells.iter().map(|v| v.len()).collect()
499    }
500    /// Checks weak Morse inequality: #critical_k >= Betti_k.
501    pub fn check_weak_morse_inequality(&self, betti: &[usize]) -> bool {
502        let critical = self.morse_inequalities_lhs();
503        betti
504            .iter()
505            .enumerate()
506            .all(|(k, &b)| critical.get(k).copied().unwrap_or(0) >= b)
507    }
508    /// Returns the Euler characteristic from critical cells.
509    pub fn euler_characteristic(&self) -> i64 {
510        self.critical_cells
511            .iter()
512            .enumerate()
513            .map(|(k, cells)| {
514                let sign: i64 = if k % 2 == 0 { 1 } else { -1 };
515                sign * cells.len() as i64
516            })
517            .sum()
518    }
519}
520/// Checks (approximately) whether a function f : {0,…,n-1} → {0,…,m-1}
521/// is uniformly continuous with respect to two finite metric spaces, on
522/// a finite sample.
523///
524/// Uniform continuity here means: ∀ε>0, ∃δ>0 such that
525/// d_X(x,y) < δ ⟹ d_Y(f(x), f(y)) < ε.
526///
527/// On a finite sample we just verify the condition for the supplied ε and δ.
528pub struct UniformContinuityChecker<'a> {
529    /// Source metric space.
530    pub source: &'a MetricSpace,
531    /// Target metric space.
532    pub target: &'a MetricSpace,
533    /// The function as a mapping from source index to target index.
534    pub mapping: Vec<usize>,
535}
536impl<'a> UniformContinuityChecker<'a> {
537    /// Create a new checker.
538    pub fn new(source: &'a MetricSpace, target: &'a MetricSpace, mapping: Vec<usize>) -> Self {
539        UniformContinuityChecker {
540            source,
541            target,
542            mapping,
543        }
544    }
545    /// Check whether `f` is uniformly continuous for the given ε and δ on the
546    /// finite sample.
547    pub fn is_uniformly_continuous(&self, epsilon: f64, delta: f64) -> bool {
548        let n = self.source.n;
549        for i in 0..n {
550            for j in 0..n {
551                if self.source.dist[i][j] < delta {
552                    let fi = self.mapping[i];
553                    let fj = self.mapping[j];
554                    if self.target.dist[fi][fj] >= epsilon {
555                        return false;
556                    }
557                }
558            }
559        }
560        true
561    }
562}
563/// A finite discrete metric space (for computational use).
564///
565/// Distances are stored as a symmetric matrix.  The main operations are
566/// checking whether a sample of points looks "Cauchy" within some tolerance,
567/// and computing ε-nets.
568pub struct MetricSpace {
569    /// Number of points.
570    pub n: usize,
571    /// Distance matrix (n × n), symmetric with zeros on the diagonal.
572    pub dist: Vec<Vec<f64>>,
573}
574impl MetricSpace {
575    /// Construct a new metric space from a distance matrix.
576    ///
577    /// Panics if `dist` is not square.
578    pub fn new(dist: Vec<Vec<f64>>) -> Self {
579        let n = dist.len();
580        for row in &dist {
581            assert_eq!(row.len(), n, "distance matrix must be square");
582        }
583        MetricSpace { n, dist }
584    }
585    /// Return the distance between points `i` and `j`.
586    pub fn distance(&self, i: usize, j: usize) -> f64 {
587        self.dist[i][j]
588    }
589    /// Check whether the whole point set is totally bounded (has a finite ε-net)
590    /// by computing the greedy ε-net and checking whether every point is within ε
591    /// of some net point.
592    pub fn is_totally_bounded(&self, epsilon: f64) -> bool {
593        if self.n == 0 {
594            return true;
595        }
596        let net = self.greedy_epsilon_net(epsilon);
597        (0..self.n).all(|i| net.iter().any(|&j| self.dist[i][j] <= epsilon))
598    }
599    /// Greedy construction of an ε-net: a minimal set of points such that
600    /// every other point is within distance ε of some net point.
601    pub fn greedy_epsilon_net(&self, epsilon: f64) -> Vec<usize> {
602        let mut net: Vec<usize> = Vec::new();
603        for i in 0..self.n {
604            if !net.iter().any(|&j| self.dist[i][j] <= epsilon) {
605                net.push(i);
606            }
607        }
608        net
609    }
610    /// Heuristic "completeness" check on a finite metric space.
611    ///
612    /// A finite metric space is always trivially complete (every Cauchy
613    /// sequence is eventually constant).  This function checks whether the
614    /// space looks "ε-dense" — a proxy for completeness of a sampled space.
615    pub fn looks_complete(&self, epsilon: f64) -> bool {
616        self.is_totally_bounded(epsilon)
617    }
618}
619/// Limit of a pro-object: a cofiltered inverse system of finite sets.
620///
621/// Stores a sequence of finite sets (levels) and projection maps between them.
622/// The limit is computed as the subset of the product consistent with all projections.
623pub struct ProObjectLimit {
624    /// Each level is a finite set represented as `Vec<u32>`.
625    pub levels: Vec<Vec<u32>>,
626    /// projection[i] maps level i+1 → level i: `projection[i][j]` is the index in level i
627    /// that element j of level i+1 maps to.
628    pub projections: Vec<Vec<usize>>,
629}
630impl ProObjectLimit {
631    /// Create a new pro-object from levels and projections.
632    pub fn new(levels: Vec<Vec<u32>>, projections: Vec<Vec<usize>>) -> Self {
633        ProObjectLimit {
634            levels,
635            projections,
636        }
637    }
638    /// Compute the inverse limit: tuples (x_0, x_1, …, x_{n-1}) consistent with
639    /// all projections.
640    ///
641    /// A tuple is consistent if for each level i: projection[i][x_{i+1}] == x_i.
642    pub fn compute_limit(&self) -> Vec<Vec<usize>> {
643        if self.levels.is_empty() {
644            return vec![];
645        }
646        let mut threads: Vec<Vec<usize>> = (0..self.levels[0].len()).map(|i| vec![i]).collect();
647        for (level_idx, proj) in self.projections.iter().enumerate() {
648            let next_level_size = if level_idx + 1 < self.levels.len() {
649                self.levels[level_idx + 1].len()
650            } else {
651                break;
652            };
653            let mut new_threads: Vec<Vec<usize>> = Vec::new();
654            for j in 0..next_level_size {
655                let parent = proj[j];
656                for thread in &threads {
657                    if *thread
658                        .last()
659                        .expect("threads are always non-empty: initialized with vec![i]")
660                        == parent
661                    {
662                        let mut new_thread = thread.clone();
663                        new_thread.push(j);
664                        new_threads.push(new_thread);
665                    }
666                }
667            }
668            threads = new_threads;
669        }
670        threads
671    }
672    /// Return the cardinality of the computed inverse limit.
673    pub fn limit_size(&self) -> usize {
674        self.compute_limit().len()
675    }
676}
677/// Checks shape equivalence of two finite CW complexes by comparing Betti numbers.
678///
679/// Two spaces are shape-equivalent if all their Čech cohomology groups agree.
680/// For finite CW complexes this reduces to comparing homology (Betti numbers)
681/// in all degrees, plus the Euler characteristic as a quick pre-check.
682pub struct ShapeEquivalenceChecker {
683    /// Betti numbers of the first space.
684    pub betti_x: Vec<usize>,
685    /// Betti numbers of the second space.
686    pub betti_y: Vec<usize>,
687}
688impl ShapeEquivalenceChecker {
689    /// Create from two simplicial complexes (compared up to `max_dim`).
690    pub fn from_complexes(cx: &SimplicialComplex, cy: &SimplicialComplex, max_dim: usize) -> Self {
691        ShapeEquivalenceChecker {
692            betti_x: homology_ranks(cx, max_dim),
693            betti_y: homology_ranks(cy, max_dim),
694        }
695    }
696    /// Euler characteristic from a Betti number list.
697    fn euler_from_betti(betti: &[usize]) -> i64 {
698        betti
699            .iter()
700            .enumerate()
701            .map(|(k, &b)| if k % 2 == 0 { b as i64 } else { -(b as i64) })
702            .sum()
703    }
704    /// Quick necessary check: Euler characteristics must agree.
705    pub fn euler_agrees(&self) -> bool {
706        Self::euler_from_betti(&self.betti_x) == Self::euler_from_betti(&self.betti_y)
707    }
708    /// Full necessary check: all Betti numbers agree.
709    pub fn betti_agree(&self) -> bool {
710        let len = self.betti_x.len().max(self.betti_y.len());
711        for k in 0..len {
712            let bx = self.betti_x.get(k).copied().unwrap_or(0);
713            let by = self.betti_y.get(k).copied().unwrap_or(0);
714            if bx != by {
715                return false;
716            }
717        }
718        true
719    }
720    /// Return `true` if the heuristic shape-equivalence check passes.
721    pub fn are_shape_equivalent(&self) -> bool {
722        self.betti_agree()
723    }
724}
725/// Represents knot invariant data.
726#[allow(dead_code)]
727#[derive(Debug, Clone)]
728pub struct KnotInvariant {
729    /// Name of the knot (e.g., "trefoil", "figure-eight").
730    pub name: String,
731    /// Alexander polynomial coefficients (by degree).
732    pub alexander_poly: Vec<i64>,
733    /// Jones polynomial coefficients (by degree, Laurent in q^{1/2}).
734    pub jones_data: Vec<(i32, i64)>,
735    /// Signature of the knot.
736    pub signature: i64,
737    /// Determinant: |Δ(-1)|.
738    pub determinant: u64,
739}
740#[allow(dead_code)]
741impl KnotInvariant {
742    /// Creates a knot invariant record.
743    pub fn new(name: &str) -> Self {
744        KnotInvariant {
745            name: name.to_string(),
746            alexander_poly: Vec::new(),
747            jones_data: Vec::new(),
748            signature: 0,
749            determinant: 1,
750        }
751    }
752    /// Sets the Alexander polynomial.
753    pub fn with_alexander(mut self, coeffs: Vec<i64>) -> Self {
754        self.alexander_poly = coeffs;
755        self
756    }
757    /// Sets the signature.
758    pub fn with_signature(mut self, sig: i64) -> Self {
759        self.signature = sig;
760        self
761    }
762    /// Sets the determinant.
763    pub fn with_determinant(mut self, det: u64) -> Self {
764        self.determinant = det;
765        self
766    }
767    /// Evaluates Alexander polynomial at t=1 (should be ±1 for a knot).
768    pub fn alexander_at_one(&self) -> i64 {
769        self.alexander_poly.iter().sum()
770    }
771    /// Returns the degree of the Alexander polynomial.
772    pub fn alexander_degree(&self) -> usize {
773        self.alexander_poly.len().saturating_sub(1)
774    }
775    /// Checks if the knot is potentially fibered (Alexander poly is monic).
776    pub fn potentially_fibered(&self) -> bool {
777        if let (Some(&first), Some(&last)) =
778            (self.alexander_poly.first(), self.alexander_poly.last())
779        {
780            first.abs() == 1 && last.abs() == 1
781        } else {
782            false
783        }
784    }
785}
786/// A cell in a CW complex.
787#[derive(Debug, Clone)]
788pub struct Cell {
789    /// Dimension of the cell (0 = vertex, 1 = edge, 2 = face, …).
790    pub dim: usize,
791    /// A human-readable label.
792    pub label: String,
793    /// Indices (into the cells vector) of the boundary cells.
794    pub attaching_map: Vec<usize>,
795}
796/// A persistence diagram: a collection of (birth, death) pairs.
797#[allow(dead_code)]
798#[derive(Debug, Clone, Default)]
799pub struct PersistenceDiagram {
800    /// The persistence points.
801    pub points: Vec<PersistencePoint>,
802}
803#[allow(dead_code)]
804impl PersistenceDiagram {
805    /// Creates an empty persistence diagram.
806    pub fn new() -> Self {
807        PersistenceDiagram { points: Vec::new() }
808    }
809    /// Adds a persistence point.
810    pub fn add(&mut self, p: PersistencePoint) {
811        self.points.push(p);
812    }
813    /// Returns points in the given dimension.
814    pub fn in_dimension(&self, dim: usize) -> Vec<&PersistencePoint> {
815        self.points.iter().filter(|p| p.dimension == dim).collect()
816    }
817    /// Returns Betti numbers by dimension (count of essential classes).
818    pub fn betti(&self, dim: usize) -> usize {
819        self.points
820            .iter()
821            .filter(|p| p.dimension == dim && p.is_essential())
822            .count()
823    }
824    /// Computes bottleneck distance to another diagram (approximate, greedy).
825    pub fn bottleneck_distance_approx(&self, other: &PersistenceDiagram) -> f64 {
826        let mut max_dist: f64 = 0.0;
827        for p in &self.points {
828            let d = other
829                .points
830                .iter()
831                .map(|q| ((p.birth - q.birth).abs()).max((p.death - q.death).abs()))
832                .fold(f64::INFINITY, f64::min);
833            max_dist = max_dist.max(d);
834        }
835        max_dist
836    }
837    /// Returns the total persistence (sum of lifetimes for finite points).
838    pub fn total_persistence(&self) -> f64 {
839        self.points
840            .iter()
841            .filter(|p| !p.is_essential())
842            .map(|p| p.persistence())
843            .sum()
844    }
845    /// Filters points with persistence above a threshold.
846    pub fn significant_features(&self, threshold: f64) -> Vec<&PersistencePoint> {
847        self.points
848            .iter()
849            .filter(|p| p.persistence() > threshold || p.is_essential())
850            .collect()
851    }
852}
853/// Builder for a finite CW complex.
854///
855/// Cells are added in order; attaching maps refer to previously added cells
856/// by index.
857pub struct CWComplexBuilder {
858    /// All cells, ordered by insertion.
859    pub cells: Vec<Cell>,
860}
861impl CWComplexBuilder {
862    /// Create an empty CW complex builder.
863    pub fn new() -> Self {
864        CWComplexBuilder { cells: Vec::new() }
865    }
866    /// Add a cell of dimension `dim` with the given label and attaching map.
867    ///
868    /// Returns the index of the new cell.
869    pub fn add_cell(
870        &mut self,
871        dim: usize,
872        label: impl Into<String>,
873        attaching: Vec<usize>,
874    ) -> usize {
875        let idx = self.cells.len();
876        self.cells.push(Cell {
877            dim,
878            label: label.into(),
879            attaching_map: attaching,
880        });
881        idx
882    }
883    /// Count cells in each dimension up to `max_dim`.
884    pub fn cell_counts(&self, max_dim: usize) -> Vec<usize> {
885        let mut counts = vec![0usize; max_dim + 1];
886        for cell in &self.cells {
887            if cell.dim <= max_dim {
888                counts[cell.dim] += 1;
889            }
890        }
891        counts
892    }
893    /// Compute the Euler characteristic from the cell counts.
894    pub fn euler_characteristic(&self) -> i64 {
895        let max_dim = self.cells.iter().map(|c| c.dim).max().unwrap_or(0);
896        let counts = self.cell_counts(max_dim);
897        counts
898            .iter()
899            .enumerate()
900            .map(|(k, &c)| if k % 2 == 0 { c as i64 } else { -(c as i64) })
901            .sum()
902    }
903}
904/// The Banach–Mazur game (a.k.a. Baire category game) played on the unit
905/// interval [0, 1] discretised into `grid_size` equally spaced points.
906///
907/// Player I (the "meager" player) picks a closed interval; Player II (the
908/// "dense" player) picks a sub-interval.  Player II wins if the intersection
909/// of all played intervals is non-empty.
910///
911/// This simulation runs a fixed number of rounds where each player halves
912/// the remaining interval.
913#[derive(Debug)]
914pub struct BaireCategoryGame {
915    /// Number of grid points (resolution of the discretised interval).
916    pub grid_size: usize,
917    /// Current left endpoint (grid index).
918    pub left: usize,
919    /// Current right endpoint (grid index).
920    pub right: usize,
921    /// Number of rounds played so far.
922    pub round: usize,
923}
924impl BaireCategoryGame {
925    /// Start a new game on [0, grid_size-1].
926    pub fn new(grid_size: usize) -> Self {
927        assert!(grid_size >= 2, "grid must have at least 2 points");
928        BaireCategoryGame {
929            grid_size,
930            left: 0,
931            right: grid_size - 1,
932            round: 0,
933        }
934    }
935    /// Player I selects the left third of the remaining interval.
936    pub fn player_one_move(&mut self) {
937        let width = self.right - self.left;
938        let new_right = self.left + width / 3;
939        if new_right > self.left {
940            self.right = new_right;
941        }
942        self.round += 1;
943    }
944    /// Player II selects the middle third of the remaining interval.
945    pub fn player_two_move(&mut self) {
946        let width = self.right - self.left;
947        let third = width / 3;
948        self.left += third;
949        self.right = self.right.saturating_sub(third);
950        self.round += 1;
951    }
952    /// Check whether the current interval is non-empty (Player II is still winning).
953    pub fn player_two_winning(&self) -> bool {
954        self.left <= self.right
955    }
956    /// Run `rounds` pairs of moves (I then II) and return whether Player II wins.
957    pub fn simulate(&mut self, rounds: usize) -> bool {
958        for _ in 0..rounds {
959            self.player_one_move();
960            if !self.player_two_winning() {
961                return false;
962            }
963            self.player_two_move();
964            if !self.player_two_winning() {
965                return false;
966            }
967        }
968        self.player_two_winning()
969    }
970}