Skip to main content

oxilean_std/homological_computations/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use oxilean_kernel::{BinderInfo, Declaration, Environment, Expr, Level, Name};
6use std::collections::HashMap;
7
8use super::functions::*;
9
10/// Convergence data: E_∞^{p,q} ≅ Gr^p(H^{p+q}) for a filtered complex.
11#[derive(Debug, Clone)]
12pub struct Convergence {
13    /// The E_∞ page.
14    pub e_infty: SpectralSequencePage,
15    /// The limit filtration grades Gr^p(H^n) for each (p,n).
16    pub filtration_grades: HashMap<(i32, i32), usize>,
17}
18impl Convergence {
19    /// Create a convergence from E_∞.
20    pub fn new(e_infty: SpectralSequencePage) -> Self {
21        let filtration_grades = e_infty.entries.clone();
22        Self {
23            e_infty,
24            filtration_grades,
25        }
26    }
27    /// Compute the rank of H^n = ∑_p E_∞^{p, n-p}.
28    pub fn cohomology_rank(&self, n: i32) -> usize {
29        self.e_infty
30            .entries
31            .iter()
32            .filter(|(&(p, q), _)| p + q == n)
33            .map(|(_, &r)| r)
34            .sum()
35    }
36}
37/// A single local cohomology group H^n_I(M).
38#[derive(Debug, Clone, PartialEq, Eq)]
39pub struct LocalCohomologyGroup {
40    /// Cohomological degree.
41    pub degree: usize,
42    /// Free rank (over an appropriate ring).
43    pub rank: usize,
44}
45impl LocalCohomologyGroup {
46    /// Create a local cohomology group.
47    pub fn new(degree: usize, rank: usize) -> Self {
48        Self { degree, rank }
49    }
50    /// True iff the group vanishes.
51    pub fn is_zero(&self) -> bool {
52        self.rank == 0
53    }
54}
55/// A graded abelian group (free, with given rank).
56#[derive(Debug, Clone, PartialEq, Eq)]
57pub struct GradedGroup {
58    /// The rank of this free abelian group.
59    pub rank: usize,
60    /// Human-readable name.
61    pub name: String,
62}
63impl GradedGroup {
64    /// Create a graded group.
65    pub fn new(rank: usize, name: &str) -> Self {
66        Self {
67            rank,
68            name: name.to_string(),
69        }
70    }
71}
72/// Persistent homology interval (bar).
73#[allow(dead_code)]
74#[derive(Debug, Clone)]
75pub struct PersistenceInterval {
76    pub dimension: usize,
77    pub birth: f64,
78    pub death: f64,
79}
80#[allow(dead_code)]
81impl PersistenceInterval {
82    /// Create a persistence interval.
83    pub fn new(dim: usize, birth: f64, death: f64) -> Self {
84        Self {
85            dimension: dim,
86            birth,
87            death,
88        }
89    }
90    /// Persistence (lifetime).
91    pub fn persistence(&self) -> f64 {
92        self.death - self.birth
93    }
94    /// Is this an essential class (infinite lifetime)?
95    pub fn is_essential(&self) -> bool {
96        self.death == f64::INFINITY
97    }
98    /// Does the interval contain a given filtration value?
99    pub fn contains(&self, t: f64) -> bool {
100        t >= self.birth && t < self.death
101    }
102}
103/// Barcode: a collection of birth-death pairs.
104#[derive(Debug, Clone, Default)]
105pub struct PersistenceBarcode {
106    /// All birth-death intervals.
107    pub intervals: Vec<BirthDeathPair>,
108}
109impl PersistenceBarcode {
110    /// Create an empty barcode.
111    pub fn new() -> Self {
112        Self::default()
113    }
114    /// Add an interval.
115    pub fn add(&mut self, birth: f64, death: f64, degree: usize) {
116        self.intervals
117            .push(BirthDeathPair::new(birth, death, degree));
118    }
119    /// Return all intervals in degree `n`.
120    pub fn intervals_in_degree(&self, n: usize) -> Vec<&BirthDeathPair> {
121        self.intervals.iter().filter(|p| p.degree == n).collect()
122    }
123    /// Count intervals in degree `n`.
124    pub fn betti_number(&self, n: usize) -> usize {
125        self.intervals_in_degree(n).len()
126    }
127    /// Compute the bottleneck distance between two barcodes (degree n).
128    ///
129    /// Uses a greedy matching approximation: sort by persistence and match greedily.
130    pub fn bottleneck_distance(&self, other: &PersistenceBarcode, n: usize) -> f64 {
131        let mut a: Vec<f64> = self
132            .intervals_in_degree(n)
133            .iter()
134            .map(|p| p.persistence().min(1e9))
135            .collect();
136        let mut b: Vec<f64> = other
137            .intervals_in_degree(n)
138            .iter()
139            .map(|p| p.persistence().min(1e9))
140            .collect();
141        a.sort_by(|x, y| x.partial_cmp(y).unwrap_or(std::cmp::Ordering::Equal));
142        b.sort_by(|x, y| x.partial_cmp(y).unwrap_or(std::cmp::Ordering::Equal));
143        let mut dist = 0.0_f64;
144        let len = a.len().max(b.len());
145        for i in 0..len {
146            let ai = a.get(i).copied().unwrap_or(0.0);
147            let bi = b.get(i).copied().unwrap_or(0.0);
148            dist = dist.max((ai - bi).abs());
149        }
150        dist
151    }
152}
153/// Manages the pages of a spectral sequence together with their differentials.
154#[derive(Debug, Clone, Default)]
155pub struct SpectralSequencePageManager {
156    /// Current page number.
157    pub current_page: usize,
158    /// Pages E_2, E_3, … stored in order (index 0 = E_2).
159    pub pages: Vec<SpectralSequencePage>,
160    /// Differentials on each page.
161    pub differentials: Vec<Vec<DifferentialMap>>,
162}
163impl SpectralSequencePageManager {
164    /// Create a manager starting at E_2.
165    pub fn new() -> Self {
166        Self {
167            current_page: 2,
168            pages: vec![],
169            differentials: vec![],
170        }
171    }
172    /// Add the E_r page (r must equal current_page).
173    pub fn add_page(&mut self, entries: HashMap<(i32, i32), usize>) {
174        let r = self.current_page;
175        let mut page = SpectralSequencePage::new(r);
176        for ((p, q), rank) in entries {
177            page.set(p, q, rank);
178        }
179        self.pages.push(page);
180        self.differentials.push(vec![]);
181        self.current_page += 1;
182    }
183    /// Add a differential d_r: E_r^{(p,q)} → E_r^{(p+r, q-r+1)}.
184    pub fn add_differential(&mut self, p: i32, q: i32, image_rank: usize) {
185        let r = self.current_page.saturating_sub(1);
186        let d = DifferentialMap::new(r, p, q, image_rank);
187        if let Some(last) = self.differentials.last_mut() {
188            last.push(d);
189        }
190    }
191    /// Advance to the next page by taking homology of the current differentials.
192    pub fn advance(&mut self) {
193        let last_idx = self.pages.len().saturating_sub(1);
194        if self.pages.is_empty() {
195            return;
196        }
197        let current = &self.pages[last_idx];
198        let diffs = &self.differentials[last_idx];
199        let r = current.page;
200        let ri = r as i32;
201        let mut next = SpectralSequencePage::new(r + 1);
202        for (&(p, q), &rank) in &current.entries {
203            let incoming = diffs
204                .iter()
205                .find(|d| d.target == (p, q))
206                .map_or(0, |d| d.image_rank);
207            let outgoing = diffs
208                .iter()
209                .find(|d| d.source == (p, q))
210                .map_or(0, |d| d.image_rank);
211            let new_rank = rank.saturating_sub(outgoing).saturating_sub(incoming);
212            if new_rank > 0 {
213                next.set(p + ri, q - ri + 1, new_rank);
214            }
215        }
216        self.pages.push(next);
217        self.differentials.push(vec![]);
218        self.current_page += 1;
219    }
220    /// Get the E_r page (r is the actual page number, r ≥ 2).
221    pub fn page(&self, r: usize) -> Option<&SpectralSequencePage> {
222        r.checked_sub(2).and_then(|idx| self.pages.get(idx))
223    }
224    /// Check if the spectral sequence has collapsed (all differentials are zero).
225    pub fn has_collapsed(&self) -> bool {
226        self.differentials
227            .iter()
228            .all(|diffs| diffs.iter().all(|d| d.image_rank == 0))
229    }
230}
231/// Tor and Ext functor data.
232#[allow(dead_code)]
233#[derive(Debug, Clone)]
234pub struct TorExtData {
235    pub functor: String,
236    pub ring: String,
237    pub module1: String,
238    pub module2: String,
239    pub degree: usize,
240    pub value: String,
241}
242#[allow(dead_code)]
243impl TorExtData {
244    /// Tor_n^R(M, N).
245    pub fn tor(ring: &str, m: &str, n_mod: &str, degree: usize) -> Self {
246        Self {
247            functor: "Tor".to_string(),
248            ring: ring.to_string(),
249            module1: m.to_string(),
250            module2: n_mod.to_string(),
251            degree,
252            value: format!("Tor_{}^{}({},{})", degree, ring, m, n_mod),
253        }
254    }
255    /// Ext^n_R(M, N).
256    pub fn ext(ring: &str, m: &str, n_mod: &str, degree: usize) -> Self {
257        Self {
258            functor: "Ext".to_string(),
259            ring: ring.to_string(),
260            module1: m.to_string(),
261            module2: n_mod.to_string(),
262            degree,
263            value: format!("Ext^{}_{}({},{})", degree, ring, m, n_mod),
264        }
265    }
266    /// Balanced Tor: Tor_n^R(M,N) can be computed from either a resolution of M or N.
267    pub fn is_balanced(&self) -> bool {
268        self.functor == "Tor"
269    }
270}
271#[allow(dead_code)]
272#[derive(Debug, Clone)]
273pub struct TruncationFunctor {
274    pub category: String,
275    pub cutoff: i64,
276    pub is_cohomological: bool,
277}
278#[allow(dead_code)]
279impl TruncationFunctor {
280    pub fn new(cat: &str, cutoff: i64, cohom: bool) -> Self {
281        TruncationFunctor {
282            category: cat.to_string(),
283            cutoff,
284            is_cohomological: cohom,
285        }
286    }
287    pub fn truncation_description(&self) -> String {
288        if self.is_cohomological {
289            format!("τ_≥{}: kills H^i for i < {}", self.cutoff, self.cutoff)
290        } else {
291            format!("τ_≤{}: kills H^i for i > {}", self.cutoff, self.cutoff)
292        }
293    }
294    pub fn composed_truncation(&self, other_cutoff: i64) -> String {
295        format!(
296            "τ_≥{} ∘ τ_≤{}: concentrated in degrees [{}, {}]",
297            self.cutoff, other_cutoff, self.cutoff, other_cutoff
298        )
299    }
300}
301/// A single Tor group Tor_n^R(M, N).
302#[derive(Debug, Clone, PartialEq, Eq)]
303pub struct TorGroup {
304    /// Homological degree n.
305    pub degree: usize,
306    /// Free rank of the group.
307    pub rank: usize,
308    /// Torsion summands.
309    pub torsion: Vec<u64>,
310}
311impl TorGroup {
312    /// Create a Tor group.
313    pub fn new(degree: usize, rank: usize) -> Self {
314        Self {
315            degree,
316            rank,
317            torsion: vec![],
318        }
319    }
320    /// True iff the group is zero.
321    pub fn is_zero(&self) -> bool {
322        self.rank == 0 && self.torsion.is_empty()
323    }
324}
325/// Ext^n_R(M, N) computed from a projective resolution of M.
326#[derive(Debug, Clone)]
327pub struct ExtFunctor {
328    /// Name of module M.
329    pub module_m: String,
330    /// Name of module N.
331    pub module_n: String,
332    /// Computed cohomology groups Ext^0, Ext^1, …
333    pub values: Vec<ExtGroup>,
334}
335impl ExtFunctor {
336    /// Compute Ext from a projective resolution of M (apply Hom(−,N)).
337    pub fn compute(module_m: &str, module_n: &str, resolution: &ProjectiveResolution) -> Self {
338        let betti = resolution.betti_numbers();
339        let values = betti
340            .iter()
341            .enumerate()
342            .map(|(n, &r)| ExtGroup::new(n, r))
343            .collect();
344        Self {
345            module_m: module_m.to_string(),
346            module_n: module_n.to_string(),
347            values,
348        }
349    }
350    /// Retrieve Ext^n(M, N).
351    pub fn ext_at(&self, n: usize) -> Option<&ExtGroup> {
352        self.values.get(n)
353    }
354    /// Injective dimension of N = largest n with Ext^n(M, N) ≠ 0.
355    pub fn injective_dimension(&self) -> Option<usize> {
356        self.values.iter().rposition(|e| !e.is_zero())
357    }
358}
359/// The Lyndon-Hochschild-Serre spectral sequence for a group extension
360/// 1 → N → G → Q → 1.
361///
362/// E_2^{p,q} = H^p(Q, H^q(N, M)) ⇒ H^{p+q}(G, M).
363#[derive(Debug, Clone)]
364pub struct LyndonHochschildSerre {
365    /// Name of the normal subgroup N.
366    pub normal_subgroup: String,
367    /// Name of the quotient group Q.
368    pub quotient_group: String,
369    /// Name of the module M.
370    pub module: String,
371    /// The E_2 page.
372    pub e2_page: SpectralSequencePage,
373}
374impl LyndonHochschildSerre {
375    /// Create a Lyndon-Hochschild-Serre spectral sequence.
376    pub fn new(
377        normal_subgroup: &str,
378        quotient_group: &str,
379        module: &str,
380        e2_entries: HashMap<(i32, i32), usize>,
381    ) -> Self {
382        let mut e2_page = SpectralSequencePage::new(2);
383        for ((p, q), rank) in e2_entries {
384            e2_page.set(p, q, rank);
385        }
386        Self {
387            normal_subgroup: normal_subgroup.to_string(),
388            quotient_group: quotient_group.to_string(),
389            module: module.to_string(),
390            e2_page,
391        }
392    }
393    /// Compute the abutment H^n(G, M) = ∑_{p+q=n} E_∞^{p,q}.
394    ///
395    /// For the LHS spectral sequence, the E_2 page has
396    /// E_2^{p,q} = H^p(Q, H^q(N, M)).
397    pub fn abutment_rank(&self, n: i32) -> usize {
398        self.e2_page
399            .entries
400            .iter()
401            .filter(|(&(p, q), _)| p + q == n)
402            .map(|(_, &r)| r)
403            .sum()
404    }
405}
406#[allow(dead_code)]
407#[derive(Debug, Clone)]
408pub struct HochschildComplex {
409    pub algebra: String,
410    pub module: String,
411    pub dimension: usize,
412    pub is_free_algebra: bool,
413}
414#[allow(dead_code)]
415impl HochschildComplex {
416    pub fn new(alg: &str, mod_: &str, dim: usize) -> Self {
417        HochschildComplex {
418            algebra: alg.to_string(),
419            module: mod_.to_string(),
420            dimension: dim,
421            is_free_algebra: false,
422        }
423    }
424    pub fn hochschild_kostant_rosenberg(&self) -> String {
425        "HKR: for smooth commutative algebra A, HH_*(A) ≅ Ω^*_{A/k} (differential forms)"
426            .to_string()
427    }
428    pub fn cyclic_homology_connection(&self) -> String {
429        format!(
430            "HC_*({}): Connes' SBI exact sequence HC → HC → HH → (shift)",
431            self.algebra
432        )
433    }
434    pub fn loday_quillen_tsygan(&self) -> String {
435        "Loday-Quillen-Tsygan: HC(A) ≅ primitive elements of H*(gl(A))".to_string()
436    }
437    pub fn degeneration_at_e2(&self) -> bool {
438        self.is_free_algebra
439    }
440}
441/// A spectral sequence with multiple pages.
442#[derive(Debug, Clone, Default)]
443pub struct SpectralSequence {
444    /// Pages E_0, E_1, E_2, …
445    pub pages: Vec<SpectralSequencePage>,
446    /// Differentials on each page.
447    pub differentials: Vec<Vec<DifferentialMap>>,
448}
449impl SpectralSequence {
450    /// Create an empty spectral sequence.
451    pub fn new() -> Self {
452        Self::default()
453    }
454    /// Add a page.
455    pub fn add_page(&mut self, entries: HashMap<(i32, i32), usize>) {
456        let page = self.pages.len();
457        let mut p = SpectralSequencePage::new(page);
458        for ((px, q), rank) in entries {
459            p.set(px, q, rank);
460        }
461        self.pages.push(p);
462        self.differentials.push(vec![]);
463    }
464    /// Add a differential on page r.
465    pub fn add_differential(&mut self, r: usize, p: i32, q: i32, image_rank: usize) {
466        let d = DifferentialMap::new(r, p, q, image_rank);
467        if r < self.differentials.len() {
468            self.differentials[r].push(d);
469        }
470    }
471    /// Retrieve E_r^{p,q}.
472    pub fn e_term(&self, r: usize, p: i32, q: i32) -> Option<usize> {
473        self.pages.get(r).map(|pg| pg.get(p, q))
474    }
475    /// Compute the E_{r+1} page from E_r by taking cohomology of d_r.
476    ///
477    /// E_{r+1}^{p,q} = ker(d_r from (p,q)) / im(d_r into (p,q)).
478    pub fn compute_next_page(&self, r: usize) -> SpectralSequencePage {
479        if r >= self.pages.len() {
480            return SpectralSequencePage::new(r + 1);
481        }
482        let current = &self.pages[r];
483        let mut next = SpectralSequencePage::new(r + 1);
484        let ri = r as i32;
485        for (&(p, q), &rank) in &current.entries {
486            let incoming_im = if r < self.differentials.len() {
487                self.differentials[r]
488                    .iter()
489                    .find(|d| d.target == (p, q))
490                    .map_or(0, |d| d.image_rank)
491            } else {
492                0
493            };
494            let outgoing_im = if r < self.differentials.len() {
495                self.differentials[r]
496                    .iter()
497                    .find(|d| d.source == (p, q))
498                    .map_or(0, |d| d.image_rank)
499            } else {
500                0
501            };
502            let ker_rank = rank.saturating_sub(outgoing_im);
503            let new_rank = ker_rank.saturating_sub(incoming_im);
504            if new_rank > 0 {
505                next.set(p + ri, q - ri + 1, new_rank);
506            }
507        }
508        next
509    }
510}
511/// Künneth formula: computes Betti numbers of a product space X × Y.
512#[allow(dead_code)]
513#[derive(Debug, Clone)]
514pub struct KunnethFormula {
515    pub betti_x: Vec<i64>,
516    pub betti_y: Vec<i64>,
517}
518#[allow(dead_code)]
519impl KunnethFormula {
520    pub fn new(betti_x: Vec<i64>, betti_y: Vec<i64>) -> Self {
521        Self { betti_x, betti_y }
522    }
523    /// Betti numbers of X × Y: β_n(X×Y) = Σ_{i+j=n} β_i(X)*β_j(Y).
524    pub fn product_betti(&self) -> Vec<i64> {
525        let nx = self.betti_x.len();
526        let ny = self.betti_y.len();
527        let n = nx + ny - 1;
528        let mut result = vec![0i64; n];
529        for i in 0..nx {
530            for j in 0..ny {
531                result[i + j] += self.betti_x[i] * self.betti_y[j];
532            }
533        }
534        result
535    }
536    /// Euler characteristic of product: χ(X×Y) = χ(X)*χ(Y).
537    pub fn euler_product(&self) -> i64 {
538        let chi = |betti: &Vec<i64>| -> i64 {
539            betti
540                .iter()
541                .enumerate()
542                .map(|(k, &b)| if k % 2 == 0 { b } else { -b })
543                .sum()
544        };
545        chi(&self.betti_x) * chi(&self.betti_y)
546    }
547}
548/// Computes group cohomology via the bar resolution.
549///
550/// Given a finite group G of order |G|, this implements the bar complex
551/// C^n(G, M) = Map(G^n, M) with coboundary maps.
552#[derive(Debug, Clone)]
553pub struct GroupCohomologyBar {
554    /// Group name.
555    pub group_name: String,
556    /// Group order.
557    pub group_order: usize,
558    /// Module name.
559    pub module_name: String,
560    /// Computed cohomology ranks Hⁿ(G, M) for n = 0, 1, …
561    pub cohomology_ranks: Vec<usize>,
562}
563impl GroupCohomologyBar {
564    /// Create a new bar resolution computer.
565    pub fn new(group_name: &str, group_order: usize, module_name: &str) -> Self {
566        Self {
567            group_name: group_name.to_string(),
568            group_order,
569            module_name: module_name.to_string(),
570            cohomology_ranks: vec![],
571        }
572    }
573    /// Compute the rank of the bar cochain module C^n(G, M).
574    ///
575    /// C^n(G, M) = Map(G^n, M), so rank = |G|^n × rank(M).
576    pub fn cochain_rank(&self, n: usize, module_rank: usize) -> usize {
577        self.group_order.saturating_pow(n as u32) * module_rank
578    }
579    /// Set the computed cohomology ranks for n = 0, 1, … up to max_degree.
580    pub fn compute_cohomology(&mut self, module_rank: usize, max_degree: usize) {
581        self.cohomology_ranks = (0..=max_degree)
582            .map(|n| if n == 0 { module_rank.min(1) } else { 0 })
583            .collect();
584    }
585    /// Return H^n(G, M).
586    pub fn cohomology_at(&self, n: usize) -> usize {
587        self.cohomology_ranks.get(n).copied().unwrap_or(0)
588    }
589    /// Euler characteristic ∑_n (-1)^n dim H^n(G, M).
590    pub fn euler_characteristic(&self) -> i64 {
591        self.cohomology_ranks
592            .iter()
593            .enumerate()
594            .map(|(n, &r)| if n % 2 == 0 { r as i64 } else { -(r as i64) })
595            .sum()
596    }
597}
598#[allow(dead_code)]
599#[derive(Debug, Clone)]
600pub struct CyclicHomologyData {
601    pub algebra_type: String,
602    pub hc_0: String,
603    pub negative_cyclic: String,
604    pub periodic_cyclic: String,
605}
606#[allow(dead_code)]
607impl CyclicHomologyData {
608    pub fn for_polynomial_ring(vars: usize) -> Self {
609        CyclicHomologyData {
610            algebra_type: format!("k[x_1,...,x_{}]", vars),
611            hc_0: format!("Ω^0 = k[x_1,...,x_{}]", vars),
612            negative_cyclic: "HC^-_{*} related to de Rham".to_string(),
613            periodic_cyclic: "HP_* = de Rham cohomology (periodic)".to_string(),
614        }
615    }
616    pub fn connes_differential(&self) -> String {
617        "Connes B-operator: B: HC_n → HC_{n+1} (degree +1 boundary-like)".to_string()
618    }
619    pub fn primary_characteristic_class(&self) -> String {
620        "Chern character: K_0(A) → HC_0(A) ≅ A/[A,A] (trace map)".to_string()
621    }
622}
623/// Mayer–Vietoris sequence data for computing homology via excision.
624#[allow(dead_code)]
625#[derive(Debug, Clone)]
626pub struct MayerVietorisSequence {
627    /// Space X = A ∪ B, intersection C = A ∩ B.
628    pub space_name: String,
629    pub a_name: String,
630    pub b_name: String,
631    pub c_name: String,
632    /// Betti numbers of A, B, C, X.
633    pub betti_a: Vec<i64>,
634    pub betti_b: Vec<i64>,
635    pub betti_c: Vec<i64>,
636    pub betti_x: Vec<i64>,
637}
638#[allow(dead_code)]
639impl MayerVietorisSequence {
640    pub fn new(
641        space_name: &str,
642        a_name: &str,
643        b_name: &str,
644        c_name: &str,
645        betti_a: Vec<i64>,
646        betti_b: Vec<i64>,
647        betti_c: Vec<i64>,
648        betti_x: Vec<i64>,
649    ) -> Self {
650        Self {
651            space_name: space_name.to_string(),
652            a_name: a_name.to_string(),
653            b_name: b_name.to_string(),
654            c_name: c_name.to_string(),
655            betti_a,
656            betti_b,
657            betti_c,
658            betti_x,
659        }
660    }
661    /// Euler characteristic from Betti numbers of X: χ = Σ (-1)^k β_k.
662    pub fn euler_characteristic(&self) -> i64 {
663        self.betti_x
664            .iter()
665            .enumerate()
666            .map(|(k, &b)| if k % 2 == 0 { b } else { -b })
667            .sum()
668    }
669    /// Verify the Mayer–Vietoris Euler relation: χ(X) = χ(A) + χ(B) - χ(C).
670    pub fn verify_euler_relation(&self) -> bool {
671        let chi = |betti: &Vec<i64>| -> i64 {
672            betti
673                .iter()
674                .enumerate()
675                .map(|(k, &b)| if k % 2 == 0 { b } else { -b })
676                .sum()
677        };
678        chi(&self.betti_x) == chi(&self.betti_a) + chi(&self.betti_b) - chi(&self.betti_c)
679    }
680}
681/// Universal coefficient theorem data.
682#[allow(dead_code)]
683#[derive(Debug, Clone)]
684pub struct UniversalCoefficients {
685    /// Integral homology groups as (free_rank, torsion_coefficients).
686    pub integral_homology: Vec<(usize, Vec<u64>)>,
687    /// Coefficient ring label.
688    pub coeff_ring: String,
689}
690#[allow(dead_code)]
691impl UniversalCoefficients {
692    pub fn new(integral_homology: Vec<(usize, Vec<u64>)>, coeff_ring: &str) -> Self {
693        Self {
694            integral_homology,
695            coeff_ring: coeff_ring.to_string(),
696        }
697    }
698    /// Over a field (e.g., Q, Z/p), all torsion dies: β_k = free rank.
699    pub fn betti_over_field(&self) -> Vec<usize> {
700        self.integral_homology.iter().map(|(r, _)| *r).collect()
701    }
702    /// Torsion part H_k(X; Z)/free.
703    pub fn torsion_part(&self, k: usize) -> Option<&Vec<u64>> {
704        self.integral_homology.get(k).map(|(_, t)| t)
705    }
706}
707/// A sparse integer matrix represented as a list of (row, col, value) triples.
708#[derive(Debug, Clone, Default)]
709pub struct SimplexBoundaryMatrix {
710    /// Number of rows.
711    pub rows: usize,
712    /// Number of cols.
713    pub cols: usize,
714    /// Nonzero entries (row, col, value).
715    pub entries: Vec<(usize, usize, i64)>,
716}
717impl SimplexBoundaryMatrix {
718    /// Create a new zero boundary matrix.
719    pub fn new(rows: usize, cols: usize) -> Self {
720        Self {
721            rows,
722            cols,
723            entries: vec![],
724        }
725    }
726    /// Set entry (i, j) to value v.
727    pub fn set(&mut self, i: usize, j: usize, v: i64) {
728        self.entries.retain(|(r, c, _)| !(*r == i && *c == j));
729        if v != 0 {
730            self.entries.push((i, j, v));
731        }
732    }
733    /// Get entry (i, j).
734    pub fn get(&self, i: usize, j: usize) -> i64 {
735        self.entries
736            .iter()
737            .find(|(r, c, _)| *r == i && *c == j)
738            .map_or(0, |(_, _, v)| *v)
739    }
740    /// Convert to dense matrix.
741    pub fn to_dense(&self) -> Vec<Vec<i64>> {
742        let mut m = vec![vec![0i64; self.cols]; self.rows];
743        for &(r, c, v) in &self.entries {
744            if r < self.rows && c < self.cols {
745                m[r][c] = v;
746            }
747        }
748        m
749    }
750    /// Compute the Smith Normal Form diagonal entries.
751    ///
752    /// Returns the nonzero diagonal invariant factors.
753    pub fn smith_normal_form_diagonal(&self) -> Vec<i64> {
754        let mut m = self.to_dense();
755        let rows = self.rows;
756        let cols = self.cols;
757        let mut diag = vec![];
758        let mut pivot = 0usize;
759        for col in 0..cols {
760            if pivot >= rows {
761                break;
762            }
763            let found = (pivot..rows).find(|&r| m[r][col] != 0);
764            if found.is_none() {
765                continue;
766            }
767            let pr = found.expect("found is Some: checked by is_none guard above");
768            m.swap(pivot, pr);
769            let pv = m[pivot][col];
770            for r in (pivot + 1)..rows {
771                let factor = m[r][col];
772                if factor != 0 {
773                    for c in 0..cols {
774                        m[r][c] = m[r][c] * pv - m[pivot][c] * factor;
775                    }
776                }
777            }
778            if m[pivot][col] != 0 {
779                diag.push(m[pivot][col].abs());
780            }
781            pivot += 1;
782        }
783        diag
784    }
785    /// Compute the rank via SNF.
786    pub fn rank(&self) -> usize {
787        self.smith_normal_form_diagonal().len()
788    }
789    /// Compute the torsion coefficients (diagonal entries > 1).
790    pub fn torsion_coefficients(&self) -> Vec<i64> {
791        self.smith_normal_form_diagonal()
792            .into_iter()
793            .filter(|&d| d > 1)
794            .collect()
795    }
796}
797/// A single Ext group Ext^n_R(M, N).
798#[derive(Debug, Clone, PartialEq, Eq)]
799pub struct ExtGroup {
800    /// Cohomological degree n.
801    pub degree: usize,
802    /// Free rank.
803    pub rank: usize,
804    /// Torsion summands.
805    pub torsion: Vec<u64>,
806}
807impl ExtGroup {
808    /// Create an Ext group.
809    pub fn new(degree: usize, rank: usize) -> Self {
810        Self {
811            degree,
812            rank,
813            torsion: vec![],
814        }
815    }
816    /// True iff the Ext group is zero.
817    pub fn is_zero(&self) -> bool {
818        self.rank == 0 && self.torsion.is_empty()
819    }
820}
821/// Long exact sequence in homology.
822#[allow(dead_code)]
823#[derive(Debug, Clone)]
824pub struct LongExactSequence {
825    pub groups: Vec<String>,
826    pub connecting_homomorphism: String,
827}
828#[allow(dead_code)]
829impl LongExactSequence {
830    /// Long exact sequence from short exact sequence 0 -> A -> B -> C -> 0.
831    pub fn from_short(a: &str, b: &str, c: &str) -> Self {
832        let groups = vec![
833            format!("H_n({})", a),
834            format!("H_n({})", b),
835            format!("H_n({})", c),
836            format!("H_{{n-1}}({})", a),
837        ];
838        Self {
839            groups,
840            connecting_homomorphism: format!("delta: H_n({}) -> H_{{n-1}}({})", c, a),
841        }
842    }
843    /// Length (number of groups listed).
844    pub fn length(&self) -> usize {
845        self.groups.len()
846    }
847}
848/// An injective resolution 0 → M → I_0 → I_1 → …
849#[derive(Debug, Clone)]
850pub struct InjectiveResolution {
851    /// The module being resolved.
852    pub module_name: String,
853    /// The injective modules I_0, I_1, …
854    pub steps: Vec<ResolutionStep>,
855}
856impl InjectiveResolution {
857    /// Create a new injective resolution.
858    pub fn new(module_name: &str) -> Self {
859        Self {
860            module_name: module_name.to_string(),
861            steps: vec![],
862        }
863    }
864    /// Add a step.
865    pub fn add_step(&mut self, rank: usize, boundary: Vec<Vec<i64>>) {
866        let degree = self.steps.len();
867        self.steps.push(ResolutionStep {
868            degree,
869            rank,
870            boundary,
871        });
872    }
873    /// Injective dimension.
874    pub fn injective_dimension(&self) -> Option<usize> {
875        self.steps.iter().rposition(|s| s.rank > 0)
876    }
877}
878/// The n-th homology group H_n(C) = ker(d_n) / im(d_{n+1}).
879#[derive(Debug, Clone, PartialEq, Eq)]
880pub struct HomologyGroup {
881    /// The homological degree.
882    pub degree: i32,
883    /// Free rank.
884    pub rank: usize,
885    /// Torsion summands (elementary divisors > 1).
886    pub torsion: Vec<u64>,
887}
888impl HomologyGroup {
889    /// Create a new homology group.
890    pub fn new(degree: i32, rank: usize, torsion: Vec<u64>) -> Self {
891        Self {
892            degree,
893            rank,
894            torsion,
895        }
896    }
897    /// True iff the group is trivial.
898    pub fn is_trivial(&self) -> bool {
899        self.rank == 0 && self.torsion.is_empty()
900    }
901}
902/// Cellular homology computation via CW structure.
903#[allow(dead_code)]
904#[derive(Debug, Clone)]
905pub struct CWComplex {
906    /// Number of n-cells: cells[n] = count of n-cells.
907    pub cells: Vec<usize>,
908    /// Attaching map degrees: degrees[n][i] = degree of i-th attaching map in dimension n+1.
909    pub attaching_degrees: Vec<Vec<i32>>,
910}
911#[allow(dead_code)]
912impl CWComplex {
913    pub fn new(cells: Vec<usize>) -> Self {
914        let m = if cells.is_empty() { 0 } else { cells.len() - 1 };
915        Self {
916            cells,
917            attaching_degrees: vec![vec![]; m],
918        }
919    }
920    /// Euler characteristic from cell counts: χ = Σ (-1)^k |cells_k|.
921    pub fn euler_characteristic(&self) -> i64 {
922        self.cells
923            .iter()
924            .enumerate()
925            .map(|(k, &c)| if k % 2 == 0 { c as i64 } else { -(c as i64) })
926            .sum()
927    }
928    /// Tight Betti bound: β_k <= cells[k].
929    pub fn betti_upper_bound(&self) -> Vec<usize> {
930        self.cells.clone()
931    }
932}
933#[allow(dead_code)]
934#[derive(Debug, Clone)]
935pub struct ExtGroupComputation {
936    pub module_a: String,
937    pub module_b: String,
938    pub projective_resolution_length: usize,
939    pub computed_exts: Vec<String>,
940}
941#[allow(dead_code)]
942impl ExtGroupComputation {
943    pub fn new(a: &str, b: &str) -> Self {
944        ExtGroupComputation {
945            module_a: a.to_string(),
946            module_b: b.to_string(),
947            projective_resolution_length: 0,
948            computed_exts: vec![],
949        }
950    }
951    pub fn compute_ext_0(&self) -> String {
952        format!(
953            "Ext^0({}, {}) = Hom({}, {})",
954            self.module_a, self.module_b, self.module_a, self.module_b
955        )
956    }
957    pub fn compute_ext_1(&self) -> String {
958        format!(
959            "Ext^1({}, {}): obstruction to extension 0→{}→E→{}→0",
960            self.module_a, self.module_b, self.module_b, self.module_a
961        )
962    }
963    pub fn horseshoe_lemma(&self) -> String {
964        "Horseshoe lemma: given short exact sequence of modules, combine projective resolutions"
965            .to_string()
966    }
967    pub fn global_dimension(&self) -> String {
968        format!(
969            "gl.dim ≤ {}: ext vanishes above degree {}",
970            self.projective_resolution_length, self.projective_resolution_length
971        )
972    }
973}
974/// Computes persistent homology from a filtered simplicial complex.
975///
976/// Implements the standard persistence algorithm (Edelsbrunner-Letscher-Zomorodian).
977#[derive(Debug, Clone, Default)]
978pub struct PersistentHomologyComputer {
979    /// Simplices in filtration order: (filtration_value, dimension).
980    pub filtration: Vec<(f64, usize)>,
981}
982impl PersistentHomologyComputer {
983    /// Create a new computer.
984    pub fn new() -> Self {
985        Self::default()
986    }
987    /// Add a simplex to the filtration.
988    pub fn add_simplex(&mut self, filtration_value: f64, dimension: usize) {
989        self.filtration.push((filtration_value, dimension));
990        self.filtration.sort_by(|a, b| {
991            a.0.partial_cmp(&b.0)
992                .unwrap_or(std::cmp::Ordering::Equal)
993                .then(a.1.cmp(&b.1))
994        });
995    }
996    /// Compute the barcode using the standard persistence algorithm.
997    ///
998    /// This simplified version tracks when each homology class is born and dies
999    /// based on simplex dimension parity (illustrative model).
1000    pub fn compute_barcode(&self) -> PersistenceBarcode {
1001        let mut barcode = PersistenceBarcode::new();
1002        let max_dim = self.filtration.iter().map(|(_, d)| *d).max().unwrap_or(0);
1003        for dim in 0..=max_dim {
1004            let birth_times: Vec<f64> = self
1005                .filtration
1006                .iter()
1007                .filter(|(_, d)| *d == dim)
1008                .map(|(v, _)| *v)
1009                .collect();
1010            let kill_times: Vec<f64> = self
1011                .filtration
1012                .iter()
1013                .filter(|(_, d)| *d == dim + 1)
1014                .map(|(v, _)| *v)
1015                .collect();
1016            let mut kill_iter = kill_times.iter().peekable();
1017            for birth in &birth_times {
1018                if let Some(&death) = kill_iter.next() {
1019                    barcode.add(*birth, death, dim);
1020                } else {
1021                    barcode.add(*birth, f64::INFINITY, dim);
1022                }
1023            }
1024        }
1025        barcode
1026    }
1027}
1028/// A chain map f: C_• → D_• (a degree-0 morphism of chain complexes).
1029#[derive(Debug, Clone)]
1030pub struct ChainMap {
1031    /// Source chain complex.
1032    pub source: ChainComplex,
1033    /// Target chain complex.
1034    pub target: ChainComplex,
1035    /// Component matrices f_n: C_n → D_n.
1036    pub components: Vec<Vec<Vec<i64>>>,
1037}
1038impl ChainMap {
1039    /// Create a chain map.
1040    pub fn new(source: ChainComplex, target: ChainComplex, components: Vec<Vec<Vec<i64>>>) -> Self {
1041        Self {
1042            source,
1043            target,
1044            components,
1045        }
1046    }
1047    /// Compute the induced map on homology H_n(C) → H_n(D) (returns rank of image).
1048    pub fn induced_homology_rank(&self, n: usize) -> usize {
1049        self.components
1050            .get(n)
1051            .map(|m| image_rank(m, self.source.groups.get(n).map_or(0, |g| g.rank)))
1052            .unwrap_or(0)
1053    }
1054}
1055/// Spectral sequence data.
1056#[allow(dead_code)]
1057#[derive(Debug, Clone)]
1058pub struct SpectralSequenceData {
1059    pub name: String,
1060    pub filtration_type: String,
1061    pub converges_to: String,
1062    pub page: usize,
1063}
1064#[allow(dead_code)]
1065impl SpectralSequenceData {
1066    /// Serre spectral sequence.
1067    pub fn serre(base: &str, fiber: &str, total: &str) -> Self {
1068        Self {
1069            name: format!("Serre({} -> {} -> {})", fiber, total, base),
1070            filtration_type: "Serre filtration".to_string(),
1071            converges_to: format!("H*({})", total),
1072            page: 2,
1073        }
1074    }
1075    /// Leray spectral sequence.
1076    pub fn leray(map: &str) -> Self {
1077        Self {
1078            name: format!("Leray({})", map),
1079            filtration_type: "sheaf cohomology".to_string(),
1080            converges_to: format!("H*(domain({}))", map),
1081            page: 2,
1082        }
1083    }
1084    /// Description of E_2 page.
1085    pub fn e2_page_description(&self) -> String {
1086        format!(
1087            "E_2 page of {}: converges to {}",
1088            self.name, self.converges_to
1089        )
1090    }
1091}
1092/// Fibration sequence: F -> E -> B with long exact sequence of homotopy groups.
1093#[allow(dead_code)]
1094#[derive(Debug, Clone)]
1095pub struct FibrationSequence {
1096    pub total_space: String,
1097    pub base_space: String,
1098    pub fiber: String,
1099    /// π_n(B), π_n(F), π_n(E) stored up to some n.
1100    pub pi_base: Vec<i64>,
1101    pub pi_fiber: Vec<i64>,
1102    pub pi_total: Vec<i64>,
1103}
1104#[allow(dead_code)]
1105impl FibrationSequence {
1106    pub fn new(
1107        total_space: &str,
1108        base_space: &str,
1109        fiber: &str,
1110        pi_base: Vec<i64>,
1111        pi_fiber: Vec<i64>,
1112        pi_total: Vec<i64>,
1113    ) -> Self {
1114        Self {
1115            total_space: total_space.to_string(),
1116            base_space: base_space.to_string(),
1117            fiber: fiber.to_string(),
1118            pi_base,
1119            pi_fiber,
1120            pi_total,
1121        }
1122    }
1123    /// Check Euler formula for fibrations: χ(E) = χ(F) * χ(B).
1124    pub fn euler_product_fibration(&self, chi_f: i64, chi_b: i64) -> i64 {
1125        chi_f * chi_b
1126    }
1127}
1128/// Local cohomology H^n_I(M) via the Čech complex or colimit of Ext^n(R/I^k, M).
1129#[derive(Debug, Clone)]
1130pub struct LocalCohomology {
1131    /// Name of the ideal I.
1132    pub ideal_name: String,
1133    /// Name of the module M.
1134    pub module_name: String,
1135    /// Computed local cohomology groups H^0_I, H^1_I, …
1136    pub groups: Vec<LocalCohomologyGroup>,
1137}
1138impl LocalCohomology {
1139    /// Create a local cohomology computation.
1140    pub fn new(ideal_name: &str, module_name: &str) -> Self {
1141        Self {
1142            ideal_name: ideal_name.to_string(),
1143            module_name: module_name.to_string(),
1144            groups: vec![],
1145        }
1146    }
1147    /// Add a computed cohomology group.
1148    pub fn add_group(&mut self, degree: usize, rank: usize) {
1149        self.groups.push(LocalCohomologyGroup::new(degree, rank));
1150    }
1151    /// Cohomological dimension: the largest n with H^n_I(M) ≠ 0.
1152    pub fn cohomological_dimension(&self) -> Option<usize> {
1153        self.groups.iter().rposition(|g| !g.is_zero())
1154    }
1155}
1156/// A flat resolution of a module.
1157#[derive(Debug, Clone)]
1158pub struct FlatResolution {
1159    /// Module name.
1160    pub module_name: String,
1161    /// Flat dimension.
1162    pub flat_dim: Option<usize>,
1163    /// Ranks of flat modules at each step.
1164    pub ranks: Vec<usize>,
1165}
1166impl FlatResolution {
1167    /// Create a flat resolution.
1168    pub fn new(module_name: &str, ranks: Vec<usize>) -> Self {
1169        let flat_dim = ranks.iter().rposition(|&r| r > 0);
1170        Self {
1171            module_name: module_name.to_string(),
1172            flat_dim,
1173            ranks,
1174        }
1175    }
1176}
1177/// Persistent Betti numbers at a given threshold.
1178#[allow(dead_code)]
1179#[derive(Debug, Clone)]
1180pub struct PersistentBettiNumbers {
1181    /// Persistence diagram: list of (birth, death) pairs per dimension.
1182    pub pairs: Vec<Vec<(f64, f64)>>,
1183}
1184#[allow(dead_code)]
1185impl PersistentBettiNumbers {
1186    pub fn new(pairs: Vec<Vec<(f64, f64)>>) -> Self {
1187        Self { pairs }
1188    }
1189    /// Betti number β_k at threshold t: count of pairs (b,d) with b<=t<d.
1190    pub fn betti_at(&self, k: usize, t: f64) -> usize {
1191        if k >= self.pairs.len() {
1192            return 0;
1193        }
1194        self.pairs[k]
1195            .iter()
1196            .filter(|&&(b, d)| b <= t && t < d)
1197            .count()
1198    }
1199    /// Total persistence of dimension k: Σ (d - b).
1200    pub fn total_persistence(&self, k: usize) -> f64 {
1201        if k >= self.pairs.len() {
1202            return 0.0;
1203        }
1204        self.pairs[k].iter().map(|&(b, d)| d - b).sum()
1205    }
1206    /// Bottleneck distance approximation (naive, O(n^2)).
1207    pub fn bottleneck_approx(&self, other: &Self, k: usize) -> f64 {
1208        if k >= self.pairs.len() || k >= other.pairs.len() {
1209            return 0.0;
1210        }
1211        let a = &self.pairs[k];
1212        let b = &other.pairs[k];
1213        if a.is_empty() && b.is_empty() {
1214            return 0.0;
1215        }
1216        let inf_dist =
1217            |p: (f64, f64), q: (f64, f64)| -> f64 { (p.0 - q.0).abs().max((p.1 - q.1).abs()) };
1218        let mut max_min = 0.0f64;
1219        for &pa in a {
1220            let min_d = b
1221                .iter()
1222                .map(|&pb| inf_dist(pa, pb))
1223                .fold(f64::INFINITY, f64::min);
1224            max_min = max_min.max(min_d);
1225        }
1226        max_min
1227    }
1228}
1229/// de Rham cohomology data for smooth manifolds.
1230#[allow(dead_code)]
1231#[derive(Debug, Clone)]
1232pub struct DeRhamCohomology {
1233    pub manifold_name: String,
1234    pub dimension: usize,
1235    /// Betti numbers (de Rham = singular over R by de Rham's theorem).
1236    pub betti_numbers: Vec<i64>,
1237}
1238#[allow(dead_code)]
1239impl DeRhamCohomology {
1240    pub fn new(manifold_name: &str, dimension: usize, betti_numbers: Vec<i64>) -> Self {
1241        Self {
1242            manifold_name: manifold_name.to_string(),
1243            dimension,
1244            betti_numbers,
1245        }
1246    }
1247    /// Poincaré duality: β_k = β_{n-k} for oriented closed n-manifold.
1248    pub fn check_poincare_duality(&self) -> bool {
1249        let n = self.dimension;
1250        if self.betti_numbers.len() != n + 1 {
1251            return false;
1252        }
1253        (0..=n).all(|k| self.betti_numbers[k] == self.betti_numbers[n - k])
1254    }
1255    /// Euler characteristic.
1256    pub fn euler_characteristic(&self) -> i64 {
1257        self.betti_numbers
1258            .iter()
1259            .enumerate()
1260            .map(|(k, &b)| if k % 2 == 0 { b } else { -b })
1261            .sum()
1262    }
1263}
1264/// A chain complex C_• with integer boundary matrices.
1265///
1266/// `groups[i]` = C_i, `boundaries[i]` = d_{i+1}: C_{i+1} → C_i.
1267#[derive(Debug, Clone, Default)]
1268pub struct ChainComplex {
1269    /// The chain groups.
1270    pub groups: Vec<GradedGroup>,
1271    /// Boundary matrices (one fewer than groups).
1272    pub boundaries: Vec<Vec<Vec<i64>>>,
1273}
1274impl ChainComplex {
1275    /// Create an empty chain complex.
1276    pub fn new() -> Self {
1277        Self::default()
1278    }
1279    /// Add a chain group C_k.
1280    pub fn add_group(&mut self, rank: usize, name: &str) {
1281        self.groups.push(GradedGroup::new(rank, name));
1282    }
1283    /// Add a boundary matrix d_k.
1284    pub fn add_boundary(&mut self, matrix: Vec<Vec<i64>>) {
1285        self.boundaries.push(matrix);
1286    }
1287    /// Compute Betti numbers β_i = ker(d_i) − im(d_{i+1}).
1288    pub fn betti_numbers(&self) -> Vec<i64> {
1289        let n = self.groups.len();
1290        (0..n)
1291            .map(|i| {
1292                let ker = if i > 0 && i - 1 < self.boundaries.len() {
1293                    kernel_rank(&self.boundaries[i - 1], self.groups[i].rank) as i64
1294                } else {
1295                    self.groups[i].rank as i64
1296                };
1297                let img = if i < self.boundaries.len() && i + 1 < self.groups.len() {
1298                    image_rank(&self.boundaries[i], self.groups[i + 1].rank) as i64
1299                } else {
1300                    0
1301                };
1302                ker - img
1303            })
1304            .collect()
1305    }
1306    /// Compute all homology groups H_n(C).
1307    pub fn compute_homology(&self) -> Vec<HomologyGroup> {
1308        self.betti_numbers()
1309            .into_iter()
1310            .enumerate()
1311            .map(|(n, rank)| HomologyGroup {
1312                degree: n as i32,
1313                rank: rank.max(0) as usize,
1314                torsion: vec![],
1315            })
1316            .collect()
1317    }
1318    /// Euler characteristic χ = ∑_n (-1)^n β_n.
1319    pub fn euler_characteristic(&self) -> i64 {
1320        self.betti_numbers()
1321            .iter()
1322            .enumerate()
1323            .map(|(n, &b)| if n % 2 == 0 { b } else { -b })
1324            .sum()
1325    }
1326    /// Check if the complex is exact at position `k` (H_k = 0).
1327    pub fn is_exact_at(&self, k: usize) -> bool {
1328        let betti = self.betti_numbers();
1329        betti.get(k).copied().unwrap_or(0) == 0
1330    }
1331    /// Check if the complex is exact everywhere.
1332    pub fn is_exact(&self) -> bool {
1333        self.betti_numbers().iter().all(|&b| b == 0)
1334    }
1335}
1336/// The differential d_r: E_r^{p,q} → E_r^{p+r, q-r+1} on page r.
1337#[derive(Debug, Clone)]
1338pub struct DifferentialMap {
1339    /// Page number r.
1340    pub page: usize,
1341    /// Source position (p, q).
1342    pub source: (i32, i32),
1343    /// Target position (p+r, q-r+1).
1344    pub target: (i32, i32),
1345    /// Rank of the image of this differential.
1346    pub image_rank: usize,
1347}
1348impl DifferentialMap {
1349    /// Create a differential d_r: E_r^{(p,q)} → E_r^{(p+r, q-r+1)}.
1350    pub fn new(r: usize, p: i32, q: i32, image_rank: usize) -> Self {
1351        let ri = r as i32;
1352        Self {
1353            page: r,
1354            source: (p, q),
1355            target: (p + ri, q - ri + 1),
1356            image_rank,
1357        }
1358    }
1359}
1360/// The bar resolution of k over the group algebra kG.
1361///
1362/// B_n(kG) = kG ⊗ k^{n+1} (as a kG-module).
1363#[derive(Debug, Clone)]
1364pub struct BarResolution {
1365    /// The group name.
1366    pub group_name: String,
1367    /// Number of computed steps.
1368    pub num_steps: usize,
1369    /// Rank of each bar module B_n.
1370    pub ranks: Vec<usize>,
1371}
1372impl BarResolution {
1373    /// Create a bar resolution up to the given number of steps.
1374    ///
1375    /// For a group G with |G| = order, B_n = (kG)^{|G|^n}.
1376    pub fn new(group_name: &str, group_order: usize, num_steps: usize) -> Self {
1377        let ranks = (0..num_steps)
1378            .map(|n| group_order.saturating_pow(n as u32))
1379            .collect();
1380        Self {
1381            group_name: group_name.to_string(),
1382            num_steps,
1383            ranks,
1384        }
1385    }
1386    /// The rank of B_n.
1387    pub fn rank_at(&self, n: usize) -> usize {
1388        self.ranks.get(n).copied().unwrap_or(0)
1389    }
1390}
1391/// A single step in a free resolution: the n-th syzygy module as a free module.
1392#[derive(Debug, Clone)]
1393pub struct ResolutionStep {
1394    /// Degree index of this step.
1395    pub degree: usize,
1396    /// Rank of the free module at this step.
1397    pub rank: usize,
1398    /// Matrix of the boundary map to the previous step.
1399    pub boundary: Vec<Vec<i64>>,
1400}
1401#[allow(dead_code)]
1402#[derive(Debug, Clone)]
1403pub struct PerverseSheafData {
1404    pub stratification: Vec<String>,
1405    pub perversity: Vec<i64>,
1406    pub is_ic_sheaf: bool,
1407    pub support_dimension: Vec<usize>,
1408}
1409#[allow(dead_code)]
1410impl PerverseSheafData {
1411    pub fn new(strat: Vec<String>, perversity: Vec<i64>) -> Self {
1412        let n = strat.len();
1413        PerverseSheafData {
1414            stratification: strat,
1415            perversity,
1416            is_ic_sheaf: false,
1417            support_dimension: (0..n).collect(),
1418        }
1419    }
1420    pub fn intersection_cohomology_description(&self) -> String {
1421        "IC sheaf: intermediate extension j_!* F of local system F".to_string()
1422    }
1423    pub fn bbdg_decomposition(&self) -> String {
1424        "BBDG: semisimple complexes over finite fields decompose into shifts of IC sheaves"
1425            .to_string()
1426    }
1427    pub fn verdier_duality(&self) -> String {
1428        "Verdier duality: D(IC_X(L)) ≅ IC_X(L^∨) for selfdual local system".to_string()
1429    }
1430    pub fn support_condition(&self) -> String {
1431        format!(
1432            "Perversity condition: dim supp H^i ≤ -i on {} strata",
1433            self.stratification.len()
1434        )
1435    }
1436}
1437/// A single page E_r of a spectral sequence.
1438///
1439/// Entries E_r^{p,q} are free abelian groups indexed by (p,q) ∈ ℤ².
1440#[derive(Debug, Clone, Default)]
1441pub struct SpectralSequencePage {
1442    /// E_r^{p,q} stored as (p,q) → rank.
1443    pub entries: HashMap<(i32, i32), usize>,
1444    /// Page number r.
1445    pub page: usize,
1446}
1447impl SpectralSequencePage {
1448    /// Create a new page.
1449    pub fn new(page: usize) -> Self {
1450        Self {
1451            entries: HashMap::new(),
1452            page,
1453        }
1454    }
1455    /// Set E_r^{p,q}.
1456    pub fn set(&mut self, p: i32, q: i32, rank: usize) {
1457        self.entries.insert((p, q), rank);
1458    }
1459    /// Get E_r^{p,q}.
1460    pub fn get(&self, p: i32, q: i32) -> usize {
1461        self.entries.get(&(p, q)).copied().unwrap_or(0)
1462    }
1463    /// Total rank of the page.
1464    pub fn total_rank(&self) -> usize {
1465        self.entries.values().sum()
1466    }
1467}
1468/// Chain complex with explicit boundary maps (stored as ranks for simplicity).
1469#[allow(dead_code)]
1470#[derive(Debug, Clone)]
1471pub struct ChainCplxExt {
1472    /// chain_groups[k] = rank of C_k (free abelian group).
1473    pub chain_groups: Vec<usize>,
1474    /// boundary_ranks[k] = rank of d_k : C_k -> C_{k-1}.
1475    pub boundary_ranks: Vec<usize>,
1476}
1477#[allow(dead_code)]
1478impl ChainCplxExt {
1479    pub fn new(chain_groups: Vec<usize>, boundary_ranks: Vec<usize>) -> Self {
1480        assert!(
1481            chain_groups.len() == boundary_ranks.len() + 1
1482                || chain_groups.len() == boundary_ranks.len(),
1483            "chain_groups.len() should be boundary_ranks.len() or boundary_ranks.len()+1"
1484        );
1485        Self {
1486            chain_groups,
1487            boundary_ranks,
1488        }
1489    }
1490    /// Betti numbers: β_k = rank(C_k) - rank(im d_{k+1}) - rank(d_k).
1491    pub fn betti_numbers(&self) -> Vec<i64> {
1492        let n = self.chain_groups.len();
1493        (0..n)
1494            .map(|k| {
1495                let ck = self.chain_groups[k] as i64;
1496                let im_kp1 = if k + 1 < self.boundary_ranks.len() {
1497                    self.boundary_ranks[k + 1] as i64
1498                } else {
1499                    0
1500                };
1501                let rk_dk = if k < self.boundary_ranks.len() {
1502                    self.boundary_ranks[k] as i64
1503                } else {
1504                    0
1505                };
1506                ck - im_kp1 - rk_dk
1507            })
1508            .collect()
1509    }
1510    /// Euler characteristic.
1511    pub fn euler_characteristic(&self) -> i64 {
1512        let betti = self.betti_numbers();
1513        betti
1514            .iter()
1515            .enumerate()
1516            .map(|(k, &b)| if k % 2 == 0 { b } else { -b })
1517            .sum()
1518    }
1519}
1520/// A projective (free) resolution … → P_2 → P_1 → P_0 → M → 0.
1521#[derive(Debug, Clone)]
1522pub struct ProjectiveResolution {
1523    /// The module being resolved.
1524    pub module_name: String,
1525    /// The steps P_0, P_1, …, P_n.
1526    pub steps: Vec<ResolutionStep>,
1527}
1528impl ProjectiveResolution {
1529    /// Create a new projective resolution.
1530    pub fn new(module_name: &str) -> Self {
1531        Self {
1532            module_name: module_name.to_string(),
1533            steps: vec![],
1534        }
1535    }
1536    /// Add a resolution step.
1537    pub fn add_step(&mut self, rank: usize, boundary: Vec<Vec<i64>>) {
1538        let degree = self.steps.len();
1539        self.steps.push(ResolutionStep {
1540            degree,
1541            rank,
1542            boundary,
1543        });
1544    }
1545    /// Projective dimension: the length of the resolution (largest non-zero step).
1546    pub fn projective_dimension(&self) -> Option<usize> {
1547        self.steps.iter().rposition(|s| s.rank > 0)
1548    }
1549    /// Betti numbers: β_i = rank(P_i).
1550    pub fn betti_numbers(&self) -> Vec<usize> {
1551        self.steps.iter().map(|s| s.rank).collect()
1552    }
1553}
1554/// Künneth formula data.
1555#[allow(dead_code)]
1556#[derive(Debug, Clone)]
1557pub struct KunnethData {
1558    pub space_x: String,
1559    pub space_y: String,
1560    pub field_coefficients: bool,
1561}
1562#[allow(dead_code)]
1563impl KunnethData {
1564    /// Künneth formula over a field.
1565    pub fn over_field(x: &str, y: &str) -> Self {
1566        Self {
1567            space_x: x.to_string(),
1568            space_y: y.to_string(),
1569            field_coefficients: true,
1570        }
1571    }
1572    /// H*(X x Y; k) = H*(X; k) ⊗ H*(Y; k) over field k.
1573    pub fn kunneth_description(&self) -> String {
1574        if self.field_coefficients {
1575            format!(
1576                "H*({} x {}; k) ≅ H*({};k) ⊗ H*({};k)",
1577                self.space_x, self.space_y, self.space_x, self.space_y
1578            )
1579        } else {
1580            format!(
1581                "Künneth: H_n({} x {}) has Tor correction term",
1582                self.space_x, self.space_y
1583            )
1584        }
1585    }
1586}
1587/// Tor_n^R(M, N) computed from a projective resolution of M.
1588#[derive(Debug, Clone)]
1589pub struct TorFunctor {
1590    /// Name of the first module M.
1591    pub module_m: String,
1592    /// Name of the second module N.
1593    pub module_n: String,
1594    /// Computed values Tor_0, Tor_1, …, Tor_k.
1595    pub values: Vec<TorGroup>,
1596}
1597impl TorFunctor {
1598    /// Create a TorFunctor from a projective resolution of M tensored with N.
1599    pub fn compute(module_m: &str, module_n: &str, resolution: &ProjectiveResolution) -> Self {
1600        let betti = resolution.betti_numbers();
1601        let values = betti
1602            .iter()
1603            .enumerate()
1604            .map(|(n, &r)| TorGroup::new(n, r))
1605            .collect();
1606        Self {
1607            module_m: module_m.to_string(),
1608            module_n: module_n.to_string(),
1609            values,
1610        }
1611    }
1612    /// Retrieve Tor_n(M, N).
1613    pub fn tor_at(&self, n: usize) -> Option<&TorGroup> {
1614        self.values.get(n)
1615    }
1616    /// Projective dimension of M = largest n with Tor_n(M, N) ≠ 0.
1617    pub fn projective_dimension(&self) -> Option<usize> {
1618        self.values.iter().rposition(|t| !t.is_zero())
1619    }
1620}
1621/// A birth-death pair in a barcode.
1622#[derive(Debug, Clone, PartialEq)]
1623pub struct BirthDeathPair {
1624    /// Birth time (filtration value).
1625    pub birth: f64,
1626    /// Death time (filtration value), or f64::INFINITY for essential classes.
1627    pub death: f64,
1628    /// Homological degree.
1629    pub degree: usize,
1630}
1631impl BirthDeathPair {
1632    /// Create a birth-death pair.
1633    pub fn new(birth: f64, death: f64, degree: usize) -> Self {
1634        Self {
1635            birth,
1636            death,
1637            degree,
1638        }
1639    }
1640    /// The persistence (lifetime) of this interval.
1641    pub fn persistence(&self) -> f64 {
1642        self.death - self.birth
1643    }
1644    /// True iff this is an essential (infinite-persistence) class.
1645    pub fn is_essential(&self) -> bool {
1646        self.death.is_infinite()
1647    }
1648}
1649#[allow(dead_code)]
1650#[derive(Debug, Clone)]
1651pub struct CechCocycle {
1652    pub open_cover_size: usize,
1653    pub degree: usize,
1654    pub cochain: Vec<Vec<f64>>,
1655    pub is_cocycle: bool,
1656}
1657#[allow(dead_code)]
1658impl CechCocycle {
1659    pub fn new(cover_size: usize, degree: usize) -> Self {
1660        let cochain = vec![vec![0.0; cover_size]; cover_size.pow(degree as u32)];
1661        CechCocycle {
1662            open_cover_size: cover_size,
1663            degree,
1664            cochain,
1665            is_cocycle: true,
1666        }
1667    }
1668    pub fn leray_theorem(&self) -> String {
1669        "Leray: for acyclic covers, Čech cohomology = sheaf cohomology".to_string()
1670    }
1671    pub fn refinement_map_description(&self) -> String {
1672        format!(
1673            "Refinement: Čech H^{}(U;F) → Čech H^{}(V;F) for V refinement of U",
1674            self.degree, self.degree
1675        )
1676    }
1677    pub fn mayer_vietoris_for_two_opens(&self) -> String {
1678        "Mayer-Vietoris: 0 → F(U∪V) → F(U)⊕F(V) → F(U∩V) → H^1 → ...".to_string()
1679    }
1680}