Skip to main content

proof_engine/topology/
genus.rs

1// topology/genus.rs — Genus computation and surface classification
2
3use glam::Vec3;
4
5// ─── Surface Classification ────────────────────────────────────────────────
6
7/// Classification of closed surfaces by genus and orientability.
8#[derive(Clone, Debug, PartialEq)]
9pub enum SurfaceType {
10    Sphere,
11    Torus,
12    DoubleTorus,
13    TripleTorus,
14    HigherGenusTorus(i32),
15    KleinBottle,
16    ProjectivePlane,
17    NonOrientable(i32), // genus for non-orientable
18}
19
20/// Compute the Euler characteristic: V - E + F.
21pub fn euler_characteristic(vertices: usize, edges: usize, faces: usize) -> i32 {
22    vertices as i32 - edges as i32 + faces as i32
23}
24
25/// Compute the genus of a closed orientable surface from a mesh.
26/// genus = (2 - chi) / 2 for orientable surfaces.
27pub fn genus_from_mesh(vertices: usize, edges: usize, faces: usize) -> i32 {
28    let chi = euler_characteristic(vertices, edges, faces);
29    (2 - chi) / 2
30}
31
32/// Classify a closed surface from its Euler characteristic and orientability.
33pub fn classify_surface(chi: i32, orientable: bool) -> SurfaceType {
34    if orientable {
35        let genus = (2 - chi) / 2;
36        match genus {
37            0 => SurfaceType::Sphere,
38            1 => SurfaceType::Torus,
39            2 => SurfaceType::DoubleTorus,
40            3 => SurfaceType::TripleTorus,
41            g => SurfaceType::HigherGenusTorus(g),
42        }
43    } else {
44        // Non-orientable: chi = 2 - k, where k is the non-orientable genus
45        let k = 2 - chi;
46        match k {
47            1 => SurfaceType::ProjectivePlane,
48            2 => SurfaceType::KleinBottle,
49            n => SurfaceType::NonOrientable(n),
50        }
51    }
52}
53
54/// Compute the connected sum of two surfaces.
55/// For orientable: genus adds.
56/// For non-orientable: non-orientable genus adds.
57/// Orientable + non-orientable = non-orientable.
58pub fn connected_sum(a: &SurfaceType, b: &SurfaceType) -> SurfaceType {
59    let (g_a, orient_a) = surface_params(a);
60    let (g_b, orient_b) = surface_params(b);
61
62    if orient_a && orient_b {
63        // Both orientable: genus adds
64        let total_genus = g_a + g_b;
65        classify_surface(2 - 2 * total_genus, true)
66    } else {
67        // At least one non-orientable
68        // Convert orientable genus to non-orientable: orientable genus g = non-orientable genus 2g
69        let k_a = if orient_a { 2 * g_a } else { g_a };
70        let k_b = if orient_b { 2 * g_b } else { g_b };
71        let total_k = k_a + k_b;
72        classify_surface(2 - total_k, false)
73    }
74}
75
76/// Extract (genus_or_k, orientable) from a SurfaceType.
77fn surface_params(s: &SurfaceType) -> (i32, bool) {
78    match s {
79        SurfaceType::Sphere => (0, true),
80        SurfaceType::Torus => (1, true),
81        SurfaceType::DoubleTorus => (2, true),
82        SurfaceType::TripleTorus => (3, true),
83        SurfaceType::HigherGenusTorus(g) => (*g, true),
84        SurfaceType::ProjectivePlane => (1, false),
85        SurfaceType::KleinBottle => (2, false),
86        SurfaceType::NonOrientable(k) => (*k, false),
87    }
88}
89
90// ─── Genus Renderer ────────────────────────────────────────────────────────
91
92/// Generates mesh vertices for visualizing surfaces of various genus.
93pub struct GenusRenderer;
94
95impl GenusRenderer {
96    /// Generate a mesh approximation of a surface with the given genus.
97    /// Returns vertices of a deformed torus-like shape with `genus` holes.
98    pub fn generate_surface(genus: i32, resolution: usize) -> Vec<Vec3> {
99        if genus <= 0 {
100            return Self::generate_sphere(resolution);
101        }
102
103        let mut vertices = Vec::new();
104
105        // For genus g, create g tori connected in a row
106        let spacing = 3.0;
107        let major_r = 1.0;
108        let minor_r = 0.4;
109
110        for hole in 0..genus {
111            let offset_x = hole as f32 * spacing - (genus as f32 - 1.0) * spacing / 2.0;
112
113            for i in 0..resolution {
114                let u = std::f32::consts::PI * 2.0 * i as f32 / resolution as f32;
115                for j in 0..resolution {
116                    let v = std::f32::consts::PI * 2.0 * j as f32 / resolution as f32;
117                    let x = (major_r + minor_r * v.cos()) * u.cos() + offset_x;
118                    let y = (major_r + minor_r * v.cos()) * u.sin();
119                    let z = minor_r * v.sin();
120                    vertices.push(Vec3::new(x, y, z));
121                }
122            }
123
124            // Connect to next torus with a tube
125            if hole < genus - 1 {
126                let tube_start = offset_x + major_r + minor_r;
127                let tube_end = offset_x + spacing - major_r - minor_r;
128                for i in 0..resolution {
129                    let angle = std::f32::consts::PI * 2.0 * i as f32 / resolution as f32;
130                    for j in 0..resolution / 2 {
131                        let t = j as f32 / (resolution / 2) as f32;
132                        let x = tube_start + t * (tube_end - tube_start);
133                        let y = minor_r * angle.cos();
134                        let z = minor_r * angle.sin();
135                        vertices.push(Vec3::new(x, y, z));
136                    }
137                }
138            }
139        }
140
141        vertices
142    }
143
144    /// Generate a simple sphere mesh (genus 0).
145    fn generate_sphere(resolution: usize) -> Vec<Vec3> {
146        let mut vertices = Vec::new();
147        for i in 0..=resolution {
148            let theta = std::f32::consts::PI * i as f32 / resolution as f32;
149            for j in 0..resolution {
150                let phi = 2.0 * std::f32::consts::PI * j as f32 / resolution as f32;
151                vertices.push(Vec3::new(
152                    theta.sin() * phi.cos(),
153                    theta.sin() * phi.sin(),
154                    theta.cos(),
155                ));
156            }
157        }
158        vertices
159    }
160}
161
162// ─── Tests ─────────────────────────────────────────────────────────────────
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167
168    #[test]
169    fn test_euler_characteristic_tetrahedron() {
170        // Tetrahedron: V=4, E=6, F=4 => chi=2 (sphere)
171        assert_eq!(euler_characteristic(4, 6, 4), 2);
172    }
173
174    #[test]
175    fn test_euler_characteristic_cube() {
176        // Cube: V=8, E=12, F=6 => chi=2 (sphere)
177        assert_eq!(euler_characteristic(8, 12, 6), 2);
178    }
179
180    #[test]
181    fn test_genus_sphere() {
182        // Any triangulation of sphere has chi=2, genus=0
183        assert_eq!(genus_from_mesh(4, 6, 4), 0);
184    }
185
186    #[test]
187    fn test_genus_torus() {
188        // Torus triangulation: V=9, E=27, F=18 => chi=0, genus=1
189        assert_eq!(euler_characteristic(9, 27, 18), 0);
190        assert_eq!(genus_from_mesh(9, 27, 18), 1);
191    }
192
193    #[test]
194    fn test_genus_double_torus() {
195        // chi = -2 => genus = 2
196        // V - E + F = -2 => e.g. V=10, E=30, F=18
197        let chi = euler_characteristic(10, 30, 18);
198        assert_eq!(chi, -2);
199        assert_eq!(genus_from_mesh(10, 30, 18), 2);
200    }
201
202    #[test]
203    fn test_classify_sphere() {
204        assert_eq!(classify_surface(2, true), SurfaceType::Sphere);
205    }
206
207    #[test]
208    fn test_classify_torus() {
209        assert_eq!(classify_surface(0, true), SurfaceType::Torus);
210    }
211
212    #[test]
213    fn test_classify_klein() {
214        assert_eq!(classify_surface(0, false), SurfaceType::KleinBottle);
215    }
216
217    #[test]
218    fn test_classify_projective_plane() {
219        assert_eq!(classify_surface(1, false), SurfaceType::ProjectivePlane);
220    }
221
222    #[test]
223    fn test_connected_sum_torus_torus() {
224        let result = connected_sum(&SurfaceType::Torus, &SurfaceType::Torus);
225        assert_eq!(result, SurfaceType::DoubleTorus);
226    }
227
228    #[test]
229    fn test_connected_sum_sphere_anything() {
230        // Sphere is the identity for connected sum
231        let result = connected_sum(&SurfaceType::Sphere, &SurfaceType::Torus);
232        assert_eq!(result, SurfaceType::Torus);
233    }
234
235    #[test]
236    fn test_connected_sum_klein_projective() {
237        // RP2 # KB = non-orientable genus 3
238        let result = connected_sum(&SurfaceType::ProjectivePlane, &SurfaceType::KleinBottle);
239        assert_eq!(result, SurfaceType::NonOrientable(3));
240    }
241
242    #[test]
243    fn test_genus_renderer_sphere() {
244        let verts = GenusRenderer::generate_surface(0, 10);
245        assert!(!verts.is_empty());
246    }
247
248    #[test]
249    fn test_genus_renderer_torus() {
250        let verts = GenusRenderer::generate_surface(1, 10);
251        assert!(!verts.is_empty());
252    }
253
254    #[test]
255    fn test_genus_renderer_double_torus() {
256        let verts = GenusRenderer::generate_surface(2, 10);
257        assert!(verts.len() > GenusRenderer::generate_surface(1, 10).len());
258    }
259}