Skip to main content

oxilean_std/differential_geometry/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use super::functions::*;
6
7/// First fundamental form coefficients (metric tensor)
8///
9/// Given a surface patch r(u,v):
10/// - E = g_uu = r_u · r_u
11/// - F = g_uv = r_u · r_v
12/// - G = g_vv = r_v · r_v
13pub struct FirstFundamentalForm {
14    pub e: f64,
15    pub f: f64,
16    pub g: f64,
17}
18impl FirstFundamentalForm {
19    pub fn new(e: f64, f: f64, g: f64) -> Self {
20        Self { e, f, g }
21    }
22    /// Determinant EG - F^2
23    pub fn det(&self) -> f64 {
24        self.e * self.g - self.f * self.f
25    }
26    /// Area element sqrt(EG - F^2)
27    pub fn area_element(&self) -> f64 {
28        self.det().sqrt()
29    }
30    /// Check positive definiteness: det > 0 and E > 0
31    pub fn is_positive_definite(&self) -> bool {
32        self.det() > 0.0 && self.e > 0.0
33    }
34}
35/// Euler integrator for the geodesic equation on a 2D Riemannian manifold.
36///
37/// Integrates  d²x^k/dt² = -Γ^k_ij (dx^i/dt)(dx^j/dt)
38/// using the forward Euler method.
39pub struct GeodesicIntegrator {
40    /// Current position (u, v)
41    pub pos: [f64; 2],
42    /// Current velocity (u̇, v̇)
43    pub vel: [f64; 2],
44}
45impl GeodesicIntegrator {
46    pub fn new(pos: [f64; 2], vel: [f64; 2]) -> Self {
47        Self { pos, vel }
48    }
49    /// Advance by one step of size dt using the Euler method.
50    ///
51    /// Requires the Christoffel symbols at the current position.
52    pub fn step(&mut self, metric: &RiemannianMetric2D, gamma: &[[[f64; 2]; 2]; 2], dt: f64) {
53        let acc = metric.geodesic_acceleration(gamma, &self.vel);
54        self.pos[0] += self.vel[0] * dt;
55        self.pos[1] += self.vel[1] * dt;
56        self.vel[0] += acc[0] * dt;
57        self.vel[1] += acc[1] * dt;
58    }
59    /// Integrate for `n` steps and return trajectory of positions.
60    pub fn integrate(
61        &mut self,
62        metric: &RiemannianMetric2D,
63        gamma: &[[[f64; 2]; 2]; 2],
64        dt: f64,
65        n: usize,
66    ) -> Vec<[f64; 2]> {
67        let mut traj = Vec::with_capacity(n + 1);
68        traj.push(self.pos);
69        for _ in 0..n {
70            self.step(metric, gamma, dt);
71            traj.push(self.pos);
72        }
73        traj
74    }
75}
76/// A torus with major radius R (center to tube center) and minor radius r (tube radius)
77pub struct Torus {
78    pub major_radius: f64,
79    pub minor_radius: f64,
80}
81impl Torus {
82    /// Gaussian curvature at angle theta (measured from outer equator)
83    ///
84    /// K(θ) = cos(θ) / (r(R + r cos(θ)))
85    pub fn gaussian_curvature_at(&self, theta: f64) -> f64 {
86        let r = self.minor_radius;
87        let big_r = self.major_radius;
88        theta.cos() / (r * (big_r + r * theta.cos()))
89    }
90    /// Euler characteristic χ = 0
91    pub fn euler_characteristic(&self) -> i32 {
92        0
93    }
94    /// Surface area = 4π² R r
95    pub fn area(&self) -> f64 {
96        4.0 * std::f64::consts::PI * std::f64::consts::PI * self.major_radius * self.minor_radius
97    }
98}
99/// A sphere of given radius
100pub struct Sphere {
101    pub radius: f64,
102}
103impl Sphere {
104    /// Gaussian curvature K = 1/R^2 (constant)
105    pub fn gaussian_curvature(&self) -> f64 {
106        1.0 / (self.radius * self.radius)
107    }
108    /// Euler characteristic χ = 2
109    pub fn euler_characteristic(&self) -> i32 {
110        2
111    }
112    /// Surface area = 4πR²
113    pub fn area(&self) -> f64 {
114        4.0 * std::f64::consts::PI * self.radius * self.radius
115    }
116    /// First fundamental form at any point (E=R², F=0, G=R² sin²θ)
117    /// Using standard spherical coords (θ=polar, φ=azimuthal), at θ=π/2: G=R²
118    pub fn first_fundamental_form_equator(&self) -> FirstFundamentalForm {
119        let r2 = self.radius * self.radius;
120        FirstFundamentalForm::new(r2, 0.0, r2)
121    }
122    /// Second fundamental form at equator: L=R, M=0, N=R
123    pub fn second_fundamental_form_equator(&self) -> SecondFundamentalForm {
124        SecondFundamentalForm::new(self.radius, 0.0, self.radius)
125    }
126}
127/// Second fundamental form coefficients (shape operator)
128///
129/// Given unit normal N:
130/// - L = h_uu = N · r_uu
131/// - M = h_uv = N · r_uv
132/// - N_coeff = h_vv = N · r_vv  (renamed to avoid clash with normal N)
133pub struct SecondFundamentalForm {
134    pub l: f64,
135    pub m: f64,
136    pub n: f64,
137}
138impl SecondFundamentalForm {
139    pub fn new(l: f64, m: f64, n: f64) -> Self {
140        Self { l, m, n }
141    }
142    /// Gaussian curvature K = (LN - M^2) / (EG - F^2)
143    pub fn gaussian_curvature(&self, first: &FirstFundamentalForm) -> f64 {
144        let det_first = first.det();
145        if det_first.abs() < 1e-12 {
146            return 0.0;
147        }
148        (self.l * self.n - self.m * self.m) / det_first
149    }
150    /// Mean curvature H = (EN - 2FM + GL) / (2(EG - F^2))
151    pub fn mean_curvature(&self, first: &FirstFundamentalForm) -> f64 {
152        let det_first = first.det();
153        if det_first.abs() < 1e-12 {
154            return 0.0;
155        }
156        (first.e * self.n - 2.0 * first.f * self.m + first.g * self.l) / (2.0 * det_first)
157    }
158    /// Principal curvatures κ₁, κ₂ (eigenvalues of shape operator)
159    ///
160    /// H = (κ₁ + κ₂)/2 and K = κ₁ κ₂, so κ = H ± sqrt(H^2 - K)
161    pub fn principal_curvatures(&self, first: &FirstFundamentalForm) -> (f64, f64) {
162        let h = self.mean_curvature(first);
163        let k = self.gaussian_curvature(first);
164        let discriminant = (h * h - k).max(0.0);
165        let sqrt_disc = discriminant.sqrt();
166        (h - sqrt_disc, h + sqrt_disc)
167    }
168}
169/// A 2D Lorentzian metric g = diag(-c², 1) for flat spacetime.
170///
171/// This models the (t, x) plane with speed of light c.
172/// The signature is (-,+): timelike vectors v have g(v,v) < 0.
173#[allow(dead_code)]
174pub struct LorentzianMetric2D {
175    /// Speed of light squared: c²
176    pub c_sq: f64,
177}
178#[allow(dead_code)]
179impl LorentzianMetric2D {
180    /// Flat Minkowski metric with c=1.
181    pub fn minkowski() -> Self {
182        Self { c_sq: 1.0 }
183    }
184    /// Metric tensor g_ij: g[0][0]=-c², g[0][1]=g[1][0]=0, g[1][1]=1.
185    pub fn metric_tensor(&self) -> [[f64; 2]; 2] {
186        [[-self.c_sq, 0.0], [0.0, 1.0]]
187    }
188    /// Inner product g(u, v) = -c² u[0]v[0] + u[1]v[1].
189    pub fn inner_product(&self, u: &[f64; 2], v: &[f64; 2]) -> f64 {
190        -self.c_sq * u[0] * v[0] + u[1] * v[1]
191    }
192    /// Check if a vector is timelike: g(v,v) < 0.
193    pub fn is_timelike(&self, v: &[f64; 2]) -> bool {
194        self.inner_product(v, v) < 0.0
195    }
196    /// Check if a vector is spacelike: g(v,v) > 0.
197    pub fn is_spacelike(&self, v: &[f64; 2]) -> bool {
198        self.inner_product(v, v) > 0.0
199    }
200    /// Check if a vector is null (lightlike): g(v,v) = 0.
201    pub fn is_null(&self, v: &[f64; 2]) -> bool {
202        self.inner_product(v, v).abs() < 1e-12
203    }
204    /// Proper time along a timelike curve parameterized by coordinate time.
205    ///
206    /// For a worldline with spatial velocity dx/dt = v_x, the proper time is
207    /// τ = ∫ sqrt(c² - v_x²) / c dt ≈ sqrt(1 - v²/c²) · Δt (time dilation).
208    pub fn proper_time_element(&self, vx: f64) -> f64 {
209        let c_sq = self.c_sq;
210        let val = c_sq - vx * vx;
211        if val <= 0.0 {
212            0.0
213        } else {
214            val.sqrt() / c_sq.sqrt()
215        }
216    }
217}
218/// Hodge star operator ★ for differential forms in flat Euclidean R^n.
219///
220/// For an oriented orthonormal basis (e_0, ..., e_{n-1}), the Hodge star of
221/// dx^{i_1} ∧ ... ∧ dx^{i_k} is the unique (n-k)-form such that
222/// ω ∧ ★ω = |ω|² vol_n.
223pub struct HodgeStar {
224    /// Ambient dimension n
225    pub dim: usize,
226}
227impl HodgeStar {
228    pub fn new(dim: usize) -> Self {
229        Self { dim }
230    }
231    /// Apply Hodge star to a k-form, returning an (n-k)-form.
232    ///
233    /// Uses the formula: ★(dx^{i_1} ∧ ... ∧ dx^{i_k}) = ε · dx^{j_1} ∧ ... ∧ dx^{j_{n-k}}
234    /// where {j_1, ..., j_{n-k}} is the complementary set and ε = ±1 is the sign
235    /// of the permutation (i_1, ..., i_k, j_1, ..., j_{n-k}).
236    pub fn apply(&self, form: &DifferentialFormWedge) -> DifferentialFormWedge {
237        assert_eq!(
238            form.dim, self.dim,
239            "form dimension must match HodgeStar dimension"
240        );
241        let n = self.dim;
242        let mut result_terms = Vec::new();
243        for (coeff, indices) in &form.terms {
244            let mut complement: Vec<usize> = (0..n).filter(|j| !indices.contains(j)).collect();
245            complement.sort();
246            let mut perm: Vec<usize> = indices.clone();
247            perm.extend_from_slice(&complement);
248            let sign = permutation_sign(&perm);
249            result_terms.push((coeff * sign, complement));
250        }
251        DifferentialFormWedge::new(self.dim, result_terms)
252    }
253    /// Compute the L² inner product ⟨α, β⟩ = ∫ α ∧ ★β over the unit cube.
254    ///
255    /// For flat Euclidean space this is just matching coefficients:
256    /// ⟨α, β⟩ = Σ_I α_I β_I
257    pub fn inner_product(
258        &self,
259        alpha: &DifferentialFormWedge,
260        beta: &DifferentialFormWedge,
261    ) -> f64 {
262        let star_beta = self.apply(beta);
263        let wedge = alpha.wedge(&star_beta);
264        let full_indices: Vec<usize> = (0..self.dim).collect();
265        wedge
266            .terms
267            .iter()
268            .find(|(_, i)| *i == full_indices)
269            .map_or(0.0, |(c, _)| *c)
270    }
271}
272/// Computes the Weyl tensor decomposition in dimension n=3 (where Weyl vanishes identically)
273/// and n=4 (generic case).
274///
275/// Weyl = Riemann - (1/(n-2))(Ricci ∧ g) + (R / ((n-1)(n-2)))(g ∧ g)
276/// where ∧ denotes the Kulkarni-Nomizu product.
277#[allow(dead_code)]
278pub struct WeylTensorComputer {
279    /// Dimension n
280    pub dim: usize,
281    /// Scalar curvature R
282    pub scalar_curvature: f64,
283}
284#[allow(dead_code)]
285impl WeylTensorComputer {
286    pub fn new(dim: usize, scalar_curvature: f64) -> Self {
287        Self {
288            dim,
289            scalar_curvature,
290        }
291    }
292    /// In dimension 3, the Weyl tensor vanishes identically: W = 0.
293    pub fn weyl_vanishes_in_dim3(&self) -> bool {
294        self.dim == 3
295    }
296    /// Compute the coefficient multiplying (Ricci ∧ g) in the Weyl decomposition.
297    pub fn ricci_coefficient(&self) -> f64 {
298        if self.dim < 3 {
299            return 0.0;
300        }
301        -1.0 / (self.dim as f64 - 2.0)
302    }
303    /// Compute the coefficient multiplying (g ∧ g) in the Weyl decomposition.
304    pub fn metric_coefficient(&self) -> f64 {
305        let n = self.dim as f64;
306        if n < 3.0 {
307            return 0.0;
308        }
309        self.scalar_curvature / ((n - 1.0) * (n - 2.0))
310    }
311    /// Compute the Weyl norm estimate for a conformally flat manifold (Weyl = 0).
312    pub fn is_conformally_flat(&self, weyl_norm: f64) -> bool {
313        weyl_norm < 1e-10
314    }
315}
316/// A curve in R^3: γ(t) = (x(t), y(t), z(t)), given as sampled points
317pub struct Curve3D {
318    pub points: Vec<[f64; 3]>,
319}
320impl Curve3D {
321    pub fn new(points: Vec<[f64; 3]>) -> Self {
322        Self { points }
323    }
324    /// Approximate arc length by summing chord lengths
325    pub fn length(&self) -> f64 {
326        if self.points.len() < 2 {
327            return 0.0;
328        }
329        self.points
330            .windows(2)
331            .map(|w| norm3(&sub3(&w[1], &w[0])))
332            .sum()
333    }
334    /// Numerical curvature |γ'' × γ'| / |γ'|^3 using finite differences
335    pub fn curvature_at(&self, i: usize) -> f64 {
336        let n = self.points.len();
337        if i == 0 || i + 1 >= n {
338            return 0.0;
339        }
340        let prev = &self.points[i - 1];
341        let curr = &self.points[i];
342        let next = &self.points[i + 1];
343        let d1 = scale3(&sub3(next, prev), 0.5);
344        let d2 = sub3(&add3(next, prev), &scale3(curr, 2.0));
345        let cross = cross3(&d2, &d1);
346        let cross_norm = norm3(&cross);
347        let d1_norm = norm3(&d1);
348        if d1_norm < 1e-12 {
349            return 0.0;
350        }
351        cross_norm / (d1_norm * d1_norm * d1_norm)
352    }
353    /// Numerical torsion using finite differences
354    pub fn torsion_at(&self, i: usize) -> f64 {
355        let n = self.points.len();
356        if i < 2 || i + 2 >= n {
357            return 0.0;
358        }
359        let prev2 = &self.points[i - 2];
360        let prev1 = &self.points[i - 1];
361        let curr = &self.points[i];
362        let next1 = &self.points[i + 1];
363        let next2 = &self.points[i + 2];
364        let d1 = scale3(&sub3(next1, prev1), 0.5);
365        let d2 = sub3(&add3(next1, prev1), &scale3(curr, 2.0));
366        let d2_prev = sub3(&add3(curr, prev2), &scale3(prev1, 2.0));
367        let d2_next = sub3(&add3(next2, curr), &scale3(next1, 2.0));
368        let d3 = scale3(&sub3(&d2_next, &d2_prev), 0.5);
369        let cross_d1_d2 = cross3(&d1, &d2);
370        let denom = dot3(&cross_d1_d2, &cross_d1_d2);
371        if denom < 1e-12 {
372            return 0.0;
373        }
374        dot3(&cross_d1_d2, &d3) / denom
375    }
376    /// Check if curve is closed: first ≈ last point
377    pub fn is_closed(&self) -> bool {
378        if self.points.len() < 2 {
379            return false;
380        }
381        let first = self
382            .points
383            .first()
384            .expect("points has at least 2 elements: checked by early return");
385        let last = self
386            .points
387            .last()
388            .expect("points has at least 2 elements: checked by early return");
389        norm3(&sub3(last, first)) < 1e-6
390    }
391    /// Frenet-Serret frame (T, N, B) at index i
392    pub fn frenet_frame_at(&self, i: usize) -> Option<([f64; 3], [f64; 3], [f64; 3])> {
393        let n = self.points.len();
394        if i == 0 || i + 1 >= n {
395            return None;
396        }
397        let prev = &self.points[i - 1];
398        let next = &self.points[i + 1];
399        let curr = &self.points[i];
400        let d1 = scale3(&sub3(next, prev), 0.5);
401        let tangent = normalize3(&d1);
402        if norm3(&tangent) < 1e-12 {
403            return None;
404        }
405        let d2 = sub3(&add3(next, prev), &scale3(curr, 2.0));
406        let d2_dot_t = dot3(&d2, &tangent);
407        let d2_perp = sub3(&d2, &scale3(&tangent, d2_dot_t));
408        let normal = normalize3(&d2_perp);
409        if norm3(&normal) < 1e-12 {
410            return None;
411        }
412        let binormal = cross3(&tangent, &normal);
413        Some((tangent, normal, binormal))
414    }
415}
416/// Christoffel symbols for a Riemannian manifold (first kind).
417#[allow(dead_code)]
418#[derive(Debug, Clone)]
419pub struct ChristoffelSymbols {
420    pub dim: usize,
421    pub gamma: Vec<Vec<Vec<f64>>>,
422}
423#[allow(dead_code)]
424impl ChristoffelSymbols {
425    pub fn new(dim: usize) -> Self {
426        let gamma = vec![vec![vec![0.0; dim]; dim]; dim];
427        ChristoffelSymbols { dim, gamma }
428    }
429    pub fn set(&mut self, k: usize, i: usize, j: usize, val: f64) {
430        self.gamma[k][i][j] = val;
431        self.gamma[k][j][i] = val;
432    }
433    pub fn get(&self, k: usize, i: usize, j: usize) -> f64 {
434        self.gamma[k][i][j]
435    }
436    /// For flat Euclidean space, all Christoffel symbols vanish.
437    pub fn is_flat(&self) -> bool {
438        self.gamma
439            .iter()
440            .all(|r| r.iter().all(|c| c.iter().all(|&v| v.abs() < 1e-12)))
441    }
442}
443/// Curvature form (abstract Riemann curvature tensor representation).
444#[allow(dead_code)]
445#[derive(Debug, Clone)]
446pub struct CurvatureTensor {
447    pub dim: usize,
448    pub scalar_curvature: f64,
449    pub ricci: Vec<Vec<f64>>,
450}
451#[allow(dead_code)]
452impl CurvatureTensor {
453    pub fn flat(dim: usize) -> Self {
454        CurvatureTensor {
455            dim,
456            scalar_curvature: 0.0,
457            ricci: vec![vec![0.0; dim]; dim],
458        }
459    }
460    pub fn sphere_unit(dim: usize) -> Self {
461        let r = (dim * (dim - 1)) as f64;
462        let ricci: Vec<Vec<f64>> = (0..dim)
463            .map(|i| {
464                (0..dim)
465                    .map(|j| if i == j { r / dim as f64 } else { 0.0 })
466                    .collect()
467            })
468            .collect();
469        CurvatureTensor {
470            dim,
471            scalar_curvature: r,
472            ricci,
473        }
474    }
475    pub fn is_einstein(&self, lambda: f64) -> bool {
476        for i in 0..self.dim {
477            for j in 0..self.dim {
478                let expected = if i == j { lambda } else { 0.0 };
479                if (self.ricci[i][j] - expected).abs() > 1e-9 {
480                    return false;
481                }
482            }
483        }
484        true
485    }
486    pub fn ricci_scalar(&self) -> f64 {
487        (0..self.dim).map(|i| self.ricci[i][i]).sum()
488    }
489}
490/// Checks if a 2-plane (given by two tangent vectors) is calibrated by a 2-form φ.
491///
492/// A 2-plane spanned by (u, v) is calibrated if φ(u,v) = Area(u,v) = |u×v|.
493#[allow(dead_code)]
494pub struct CalibrationChecker {
495    /// The calibration 2-form stored as skew-symmetric matrix: φ_ij with φ(e_i, e_j) = mat[i][j]
496    pub form: [[f64; 3]; 3],
497}
498#[allow(dead_code)]
499impl CalibrationChecker {
500    pub fn new(form: [[f64; 3]; 3]) -> Self {
501        Self { form }
502    }
503    /// Special Lagrangian calibration form Re(dz_1 ∧ dz_2 ∧ dz_3) restricted to R^3.
504    /// In the (e_1, e_2, e_3) basis: φ = dx ∧ dy ∧ dz (volume form in R^3 treated as 3D).
505    /// Here we return a simpler 2D version: φ = dx ∧ dy.
506    pub fn area_form_r3() -> Self {
507        Self {
508            form: [[0.0, 1.0, 0.0], [-1.0, 0.0, 0.0], [0.0, 0.0, 0.0]],
509        }
510    }
511    /// Evaluate φ(u, v) = Σ_{ij} φ_{ij} u^i v^j.
512    pub fn evaluate(&self, u: &[f64; 3], v: &[f64; 3]) -> f64 {
513        let mut sum = 0.0;
514        for i in 0..3 {
515            for j in 0..3 {
516                sum += self.form[i][j] * u[i] * v[j];
517            }
518        }
519        sum
520    }
521    /// Area of the 2-plane spanned by u, v: |u × v|.
522    pub fn area(&self, u: &[f64; 3], v: &[f64; 3]) -> f64 {
523        let c = cross3(u, v);
524        norm3(&c)
525    }
526    /// Check if the 2-plane spanned by u, v is calibrated: φ(u,v) ≥ Area(u,v) · comass.
527    pub fn is_calibrated(&self, u: &[f64; 3], v: &[f64; 3]) -> bool {
528        let phi_uv = self.evaluate(u, v);
529        let area_uv = self.area(u, v);
530        if area_uv < 1e-12 {
531            return true;
532        }
533        (phi_uv / area_uv - 1.0).abs() < 1e-9
534    }
535}
536/// Symbolic representation of a differential p-form as a sum of basis monomials.
537///
538/// A p-form in R^n is represented as a list of (coefficient, sorted index list) pairs.
539/// For example, in R^3:
540///   2 dx^0 ∧ dx^1 + 3 dx^1 ∧ dx^2 is `[(2.0, vec![0,1]), (3.0, vec![1,2])]`
541#[derive(Clone, Debug)]
542pub struct DifferentialFormWedge {
543    /// List of (coefficient, sorted basis indices)
544    pub terms: Vec<(f64, Vec<usize>)>,
545    /// Ambient dimension n
546    pub dim: usize,
547}
548impl DifferentialFormWedge {
549    /// Construct a k-form from explicit terms.
550    pub fn new(dim: usize, terms: Vec<(f64, Vec<usize>)>) -> Self {
551        let mut form = Self { dim, terms };
552        form.normalize();
553        form
554    }
555    /// Basis 1-form dx^i in R^n.
556    pub fn basis_1form(dim: usize, i: usize) -> Self {
557        Self::new(dim, vec![(1.0, vec![i])])
558    }
559    /// Sort indices and apply sign from permutation, merge duplicates.
560    fn normalize(&mut self) {
561        let mut normalized: Vec<(f64, Vec<usize>)> = Vec::new();
562        for (coeff, indices) in &self.terms {
563            if *coeff == 0.0 {
564                continue;
565            }
566            let mut idx = indices.clone();
567            let mut sign = 1.0_f64;
568            let n = idx.len();
569            let mut has_dup = false;
570            for i in 0..n {
571                for j in 0..n - 1 - i {
572                    if idx[j] > idx[j + 1] {
573                        idx.swap(j, j + 1);
574                        sign *= -1.0;
575                    } else if idx[j] == idx[j + 1] {
576                        has_dup = true;
577                    }
578                }
579            }
580            if has_dup {
581                continue;
582            }
583            if let Some(existing) = normalized.iter_mut().find(|(_, idxs)| *idxs == idx) {
584                existing.0 += coeff * sign;
585            } else {
586                normalized.push((coeff * sign, idx));
587            }
588        }
589        normalized.retain(|(c, _)| c.abs() > 1e-14);
590        self.terms = normalized;
591    }
592    /// Wedge product α ∧ β.
593    pub fn wedge(&self, other: &DifferentialFormWedge) -> DifferentialFormWedge {
594        assert_eq!(self.dim, other.dim, "dimension mismatch in wedge product");
595        let mut result_terms = Vec::new();
596        for (ca, ia) in &self.terms {
597            for (cb, ib) in &other.terms {
598                let mut indices = ia.clone();
599                indices.extend_from_slice(ib);
600                result_terms.push((ca * cb, indices));
601            }
602        }
603        DifferentialFormWedge::new(self.dim, result_terms)
604    }
605    /// Scale by a scalar.
606    pub fn scale(&self, s: f64) -> DifferentialFormWedge {
607        let terms = self.terms.iter().map(|(c, i)| (c * s, i.clone())).collect();
608        DifferentialFormWedge::new(self.dim, terms)
609    }
610    /// Add two forms.
611    pub fn add(&self, other: &DifferentialFormWedge) -> DifferentialFormWedge {
612        assert_eq!(self.dim, other.dim, "dimension mismatch in form addition");
613        let mut terms = self.terms.clone();
614        terms.extend_from_slice(&other.terms);
615        DifferentialFormWedge::new(self.dim, terms)
616    }
617    /// Degree (number of indices in first term, or 0 for zero form).
618    pub fn degree(&self) -> usize {
619        self.terms.first().map_or(0, |(_, i)| i.len())
620    }
621    /// Check if this is the zero form.
622    pub fn is_zero(&self) -> bool {
623        self.terms.is_empty()
624    }
625}
626/// A Randers-type Finsler metric on R: F(x, v) = α(x)|v| + β(x)·v
627///
628/// where α(x) > 0 and |β(x)| < α(x) for strong convexity.
629/// This is the simplest non-reversible Finsler metric.
630#[allow(dead_code)]
631pub struct RandersFinsler {
632    /// Riemannian part α (constant here for simplicity)
633    pub alpha: f64,
634    /// 1-form part β (constant)
635    pub beta: f64,
636}
637#[allow(dead_code)]
638impl RandersFinsler {
639    /// Create a Randers metric. Requires |beta| < alpha.
640    pub fn new(alpha: f64, beta: f64) -> Option<Self> {
641        if beta.abs() < alpha && alpha > 0.0 {
642            Some(Self { alpha, beta })
643        } else {
644            None
645        }
646    }
647    /// Finsler norm F(v) = alpha |v| + beta v.
648    pub fn norm(&self, v: f64) -> f64 {
649        self.alpha * v.abs() + self.beta * v
650    }
651    /// Check strong convexity: F > 0 for v ≠ 0.
652    pub fn is_strongly_convex(&self) -> bool {
653        self.alpha + self.beta > 0.0 && self.alpha - self.beta > 0.0
654    }
655    /// Fundamental tensor (second derivative of F²/2 w.r.t. v): g = α² + αβ sgn(v)
656    pub fn fundamental_tensor(&self, v: f64) -> f64 {
657        if v.abs() < 1e-12 {
658            return self.alpha * self.alpha;
659        }
660        let sign_v = if v > 0.0 { 1.0 } else { -1.0 };
661        let factor = self.alpha * sign_v + self.beta;
662        factor * factor
663    }
664}
665/// A point on a surface parameterized as (u,v) → (x,y,z)
666pub struct SurfacePoint {
667    pub u: f64,
668    pub v: f64,
669    pub position: [f64; 3],
670}
671/// Riemannian metric tensor at a point (as a symmetric positive-definite matrix).
672#[allow(dead_code)]
673#[derive(Debug, Clone)]
674pub struct RiemannMetric {
675    pub dim: usize,
676    pub components: Vec<Vec<f64>>,
677}
678#[allow(dead_code)]
679impl RiemannMetric {
680    pub fn new(dim: usize) -> Self {
681        let components = (0..dim)
682            .map(|i| (0..dim).map(|j| if i == j { 1.0 } else { 0.0 }).collect())
683            .collect();
684        RiemannMetric { dim, components }
685    }
686    pub fn euclidean(dim: usize) -> Self {
687        Self::new(dim)
688    }
689    pub fn set_component(&mut self, i: usize, j: usize, val: f64) {
690        self.components[i][j] = val;
691        self.components[j][i] = val;
692    }
693    /// Compute determinant (for 2x2 only, simplified).
694    pub fn det_2d(&self) -> Option<f64> {
695        if self.dim == 2 {
696            Some(
697                self.components[0][0] * self.components[1][1]
698                    - self.components[0][1] * self.components[1][0],
699            )
700        } else {
701            None
702        }
703    }
704    pub fn inner_product(&self, u: &[f64], v: &[f64]) -> f64 {
705        assert_eq!(u.len(), self.dim);
706        assert_eq!(v.len(), self.dim);
707        let mut result = 0.0;
708        for i in 0..self.dim {
709            for j in 0..self.dim {
710                result += self.components[i][j] * u[i] * v[j];
711            }
712        }
713        result
714    }
715    pub fn norm(&self, v: &[f64]) -> f64 {
716        self.inner_product(v, v).sqrt()
717    }
718}
719/// Lie derivative of a tensor field along a vector field (abstract).
720#[allow(dead_code)]
721#[derive(Debug, Clone)]
722pub struct LieDerivative {
723    pub vector_field: String,
724    pub tensor_name: String,
725    pub result_name: String,
726}
727#[allow(dead_code)]
728impl LieDerivative {
729    pub fn new(v: &str, t: &str) -> Self {
730        LieDerivative {
731            vector_field: v.to_string(),
732            tensor_name: t.to_string(),
733            result_name: format!("L_{}({})", v, t),
734        }
735    }
736    /// Cartan's magic formula: L_X ω = i_X(dω) + d(i_X ω).
737    pub fn cartan_formula() -> &'static str {
738        "L_X = i_X ∘ d + d ∘ i_X"
739    }
740    /// L_X f = X(f) for a smooth function f.
741    pub fn on_function(v: &str, f: &str) -> String {
742        format!("{}({})", v, f)
743    }
744}
745/// Element of SO(3): 3×3 rotation matrix stored row-major.
746#[derive(Clone, Debug)]
747pub struct LieGroupSO3 {
748    /// Row-major 3×3 rotation matrix
749    pub matrix: [[f64; 3]; 3],
750}
751impl LieGroupSO3 {
752    /// Identity rotation.
753    pub fn identity() -> Self {
754        Self {
755            matrix: [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
756        }
757    }
758    /// Rodrigues' rotation formula: R(ω) = I + sin(θ)/θ [ω]× + (1-cos(θ))/θ² [ω]ײ
759    ///
760    /// where ω is the rotation axis-angle vector (|ω| = θ).
761    pub fn from_axis_angle(omega: &[f64; 3]) -> Self {
762        let theta = norm3(omega);
763        if theta < 1e-12 {
764            return Self::identity();
765        }
766        let k = [omega[0] / theta, omega[1] / theta, omega[2] / theta];
767        let s = theta.sin();
768        let c = theta.cos();
769        let one_minus_c = 1.0 - c;
770        let matrix = [
771            [
772                c + k[0] * k[0] * one_minus_c,
773                k[0] * k[1] * one_minus_c - k[2] * s,
774                k[0] * k[2] * one_minus_c + k[1] * s,
775            ],
776            [
777                k[1] * k[0] * one_minus_c + k[2] * s,
778                c + k[1] * k[1] * one_minus_c,
779                k[1] * k[2] * one_minus_c - k[0] * s,
780            ],
781            [
782                k[2] * k[0] * one_minus_c - k[1] * s,
783                k[2] * k[1] * one_minus_c + k[0] * s,
784                c + k[2] * k[2] * one_minus_c,
785            ],
786        ];
787        Self { matrix }
788    }
789    /// Apply rotation to a 3-vector v → R·v.
790    pub fn apply(&self, v: &[f64; 3]) -> [f64; 3] {
791        let r = &self.matrix;
792        [
793            r[0][0] * v[0] + r[0][1] * v[1] + r[0][2] * v[2],
794            r[1][0] * v[0] + r[1][1] * v[1] + r[1][2] * v[2],
795            r[2][0] * v[0] + r[2][1] * v[1] + r[2][2] * v[2],
796        ]
797    }
798    /// Compose two rotations: (self * other).apply(v) = self.apply(other.apply(v))
799    pub fn compose(&self, other: &LieGroupSO3) -> LieGroupSO3 {
800        let a = &self.matrix;
801        let b = &other.matrix;
802        let mut c = [[0.0f64; 3]; 3];
803        for i in 0..3 {
804            for j in 0..3 {
805                for k in 0..3 {
806                    c[i][j] += a[i][k] * b[k][j];
807                }
808            }
809        }
810        LieGroupSO3 { matrix: c }
811    }
812    /// Transpose = inverse for SO(3).
813    pub fn transpose(&self) -> LieGroupSO3 {
814        let m = &self.matrix;
815        LieGroupSO3 {
816            matrix: [
817                [m[0][0], m[1][0], m[2][0]],
818                [m[0][1], m[1][1], m[2][1]],
819                [m[0][2], m[1][2], m[2][2]],
820            ],
821        }
822    }
823    /// Extract the axis-angle vector ω from R using the logarithm map.
824    ///
825    /// Returns ω such that from_axis_angle(ω) ≈ self.
826    pub fn log_map(&self) -> [f64; 3] {
827        let m = &self.matrix;
828        let trace = m[0][0] + m[1][1] + m[2][2];
829        let cos_theta = ((trace - 1.0) / 2.0).clamp(-1.0, 1.0);
830        let theta = cos_theta.acos();
831        if theta.abs() < 1e-12 {
832            return [0.0, 0.0, 0.0];
833        }
834        let factor = theta / (2.0 * theta.sin());
835        [
836            factor * (m[2][1] - m[1][2]),
837            factor * (m[0][2] - m[2][0]),
838            factor * (m[1][0] - m[0][1]),
839        ]
840    }
841    /// Check if the matrix is a valid rotation (det ≈ 1, R·Rᵀ ≈ I).
842    pub fn is_valid(&self) -> bool {
843        let rt = self.transpose();
844        let prod = self.compose(&rt);
845        let m = &prod.matrix;
846        let identity_err = (m[0][0] - 1.0).abs()
847            + (m[1][1] - 1.0).abs()
848            + (m[2][2] - 1.0).abs()
849            + m[0][1].abs()
850            + m[0][2].abs()
851            + m[1][0].abs()
852            + m[1][2].abs()
853            + m[2][0].abs()
854            + m[2][1].abs();
855        identity_err < 1e-10
856    }
857}
858/// Geodesic on a Riemannian manifold (approximated by straight lines for flat space).
859#[allow(dead_code)]
860#[derive(Debug, Clone)]
861pub struct Geodesic {
862    pub start: Vec<f64>,
863    pub end: Vec<f64>,
864    pub n_steps: usize,
865}
866#[allow(dead_code)]
867impl Geodesic {
868    pub fn new(start: Vec<f64>, end: Vec<f64>, steps: usize) -> Self {
869        assert_eq!(start.len(), end.len());
870        Geodesic {
871            start,
872            end,
873            n_steps: steps,
874        }
875    }
876    pub fn dim(&self) -> usize {
877        self.start.len()
878    }
879    pub fn point_at(&self, t: f64) -> Vec<f64> {
880        self.start
881            .iter()
882            .zip(self.end.iter())
883            .map(|(s, e)| s + t * (e - s))
884            .collect()
885    }
886    pub fn euclidean_length(&self) -> f64 {
887        self.start
888            .iter()
889            .zip(self.end.iter())
890            .map(|(s, e)| (e - s).powi(2))
891            .sum::<f64>()
892            .sqrt()
893    }
894    pub fn sample_points(&self) -> Vec<Vec<f64>> {
895        (0..=self.n_steps)
896            .map(|i| {
897                let t = i as f64 / self.n_steps as f64;
898                self.point_at(t)
899            })
900            .collect()
901    }
902}
903/// A 3D Riemannian metric g_{ij}(x) given as a symmetric 3×3 matrix.
904pub struct RiemannianMetric3D {
905    /// Symmetric 3×3 metric tensor g[i][j]
906    pub g: [[f64; 3]; 3],
907}
908impl RiemannianMetric3D {
909    pub fn new(g: [[f64; 3]; 3]) -> Self {
910        Self { g }
911    }
912    /// Flat Euclidean metric on R^3
913    pub fn euclidean() -> Self {
914        Self {
915            g: [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
916        }
917    }
918    /// Determinant of a 3×3 matrix via cofactor expansion.
919    pub fn det(&self) -> f64 {
920        let g = &self.g;
921        g[0][0] * (g[1][1] * g[2][2] - g[1][2] * g[2][1])
922            - g[0][1] * (g[1][0] * g[2][2] - g[1][2] * g[2][0])
923            + g[0][2] * (g[1][0] * g[2][1] - g[1][1] * g[2][0])
924    }
925    /// Inverse metric g^{ij} using the adjugate / cofactor formula.
926    pub fn inverse(&self) -> [[f64; 3]; 3] {
927        let g = &self.g;
928        let d = self.det();
929        if d.abs() < 1e-15 {
930            return [[0.0; 3]; 3];
931        }
932        let mut inv = [[0.0f64; 3]; 3];
933        inv[0][0] = (g[1][1] * g[2][2] - g[1][2] * g[2][1]) / d;
934        inv[0][1] = (g[0][2] * g[2][1] - g[0][1] * g[2][2]) / d;
935        inv[0][2] = (g[0][1] * g[1][2] - g[0][2] * g[1][1]) / d;
936        inv[1][0] = (g[1][2] * g[2][0] - g[1][0] * g[2][2]) / d;
937        inv[1][1] = (g[0][0] * g[2][2] - g[0][2] * g[2][0]) / d;
938        inv[1][2] = (g[0][2] * g[1][0] - g[0][0] * g[1][2]) / d;
939        inv[2][0] = (g[1][0] * g[2][1] - g[1][1] * g[2][0]) / d;
940        inv[2][1] = (g[0][1] * g[2][0] - g[0][0] * g[2][1]) / d;
941        inv[2][2] = (g[0][0] * g[1][1] - g[0][1] * g[1][0]) / d;
942        inv
943    }
944    /// Christoffel symbols Γ^k_ij given metric partial derivatives dg[i][j][k] = ∂_k g_{ij}.
945    ///
946    /// Returns gamma[k][i][j] = (1/2) g^{kl} (∂_i g_{jl} + ∂_j g_{il} - ∂_l g_{ij}).
947    pub fn christoffel(&self, dg: &[[[f64; 3]; 3]; 3]) -> [[[f64; 3]; 3]; 3] {
948        let g_inv = self.inverse();
949        let mut gamma = [[[0.0f64; 3]; 3]; 3];
950        for k in 0..3 {
951            for i in 0..3 {
952                for j in 0..3 {
953                    let mut val = 0.0;
954                    for l in 0..3 {
955                        let term = dg[j][l][i] + dg[i][l][j] - dg[i][j][l];
956                        val += g_inv[k][l] * term;
957                    }
958                    gamma[k][i][j] = 0.5 * val;
959                }
960            }
961        }
962        gamma
963    }
964    /// Volume element sqrt(det g).
965    pub fn volume_element(&self) -> f64 {
966        self.det().abs().sqrt()
967    }
968}
969/// Differential form of degree k on an n-dimensional manifold.
970#[allow(dead_code)]
971#[derive(Debug, Clone)]
972pub struct DifferentialForm {
973    pub degree: usize,
974    pub ambient_dim: usize,
975    pub is_closed: bool,
976    pub is_exact: bool,
977}
978#[allow(dead_code)]
979impl DifferentialForm {
980    pub fn new(degree: usize, n: usize) -> Self {
981        DifferentialForm {
982            degree,
983            ambient_dim: n,
984            is_closed: false,
985            is_exact: false,
986        }
987    }
988    pub fn zero_form(n: usize) -> Self {
989        DifferentialForm::new(0, n)
990    }
991    pub fn volume_form(n: usize) -> Self {
992        let mut f = DifferentialForm::new(n, n);
993        f.is_closed = true;
994        f
995    }
996    /// Exact forms are closed: d(dω) = 0.
997    pub fn mark_exact(&mut self) {
998        self.is_exact = true;
999        self.is_closed = true;
1000    }
1001    /// Dimension of the space of k-forms on R^n: C(n,k).
1002    pub fn space_dimension(&self) -> usize {
1003        let n = self.ambient_dim;
1004        let k = self.degree;
1005        if k > n {
1006            return 0;
1007        }
1008        let mut result = 1usize;
1009        for i in 0..k {
1010            result = result * (n - i) / (i + 1);
1011        }
1012        result
1013    }
1014    pub fn is_top_form(&self) -> bool {
1015        self.degree == self.ambient_dim
1016    }
1017}
1018/// Approximate holonomy by integrating parallel transport around a small loop.
1019///
1020/// Uses the formula: Hol(γ) ≈ exp(-∫∫_D R^{1}_{2 12} du dv)
1021/// for a 2D manifold with Gaussian curvature K.
1022#[allow(dead_code)]
1023pub struct HolonomyComputer {
1024    /// Christoffel symbols Γ[k][i][j] at the base point
1025    pub christoffel: [[[f64; 2]; 2]; 2],
1026}
1027#[allow(dead_code)]
1028impl HolonomyComputer {
1029    pub fn new(christoffel: [[[f64; 2]; 2]; 2]) -> Self {
1030        Self { christoffel }
1031    }
1032    /// Parallel transport a 2D vector v along the direction (du, dv) for a small step.
1033    ///
1034    /// dv^k = -Γ^k_{ij} v^i dx^j
1035    pub fn parallel_transport_step(&self, v: &[f64; 2], dx: &[f64; 2]) -> [f64; 2] {
1036        let gamma = &self.christoffel;
1037        let mut dv = [0.0f64; 2];
1038        for k in 0..2 {
1039            for i in 0..2 {
1040                for j in 0..2 {
1041                    dv[k] -= gamma[k][i][j] * v[i] * dx[j];
1042                }
1043            }
1044        }
1045        [v[0] + dv[0], v[1] + dv[1]]
1046    }
1047    /// Approximate holonomy angle around a small square loop of size ε.
1048    ///
1049    /// Returns the rotation angle φ such that Hol ≈ R(φ).
1050    /// Uses: φ ≈ R^{1}_{2 12} · ε²  (area law).
1051    pub fn holonomy_angle_square_loop(&self, eps: f64) -> f64 {
1052        let gamma = &self.christoffel;
1053        let v0 = [1.0_f64, 0.0_f64];
1054        let steps = [
1055            [eps, 0.0_f64],
1056            [0.0_f64, eps],
1057            [-eps, 0.0_f64],
1058            [0.0_f64, -eps],
1059        ];
1060        let mut v = v0;
1061        for dx in &steps {
1062            v = self.parallel_transport_step(&v, dx);
1063        }
1064        let cos_phi = v[0] * v0[0] + v[1] * v0[1];
1065        let sin_phi = v[1] * v0[0] - v[0] * v0[1];
1066        let _ = gamma;
1067        sin_phi.atan2(cos_phi)
1068    }
1069}
1070/// A 2D Riemannian metric g_ij(u,v) given by coefficient functions.
1071///
1072/// The metric tensor g = [[g00, g01], [g10, g11]] with g10 = g01.
1073/// Used to compute Christoffel symbols Γ^k_ij and geodesic acceleration.
1074pub struct RiemannianMetric2D {
1075    /// g_00 component
1076    pub g00: f64,
1077    /// g_01 = g_10 component
1078    pub g01: f64,
1079    /// g_11 component
1080    pub g11: f64,
1081}
1082impl RiemannianMetric2D {
1083    pub fn new(g00: f64, g01: f64, g11: f64) -> Self {
1084        Self { g00, g01, g11 }
1085    }
1086    /// Flat Euclidean metric on R^2
1087    pub fn euclidean() -> Self {
1088        Self::new(1.0, 0.0, 1.0)
1089    }
1090    /// Determinant of g
1091    pub fn det(&self) -> f64 {
1092        self.g00 * self.g11 - self.g01 * self.g01
1093    }
1094    /// Inverse metric g^{ij}
1095    pub fn inverse(&self) -> [[f64; 2]; 2] {
1096        let d = self.det();
1097        if d.abs() < 1e-15 {
1098            return [[0.0; 2]; 2];
1099        }
1100        [[self.g11 / d, -self.g01 / d], [-self.g01 / d, self.g00 / d]]
1101    }
1102    /// Christoffel symbols Γ^k_ij from finite-difference perturbation of the metric.
1103    ///
1104    /// This version accepts partial derivatives of g_ij: dg[i][j][k] = ∂_k g_ij.
1105    /// Returns Γ[k][i][j] = (1/2) g^{kl} (∂_i g_{jl} + ∂_j g_{il} - ∂_l g_{ij}).
1106    pub fn christoffel(&self, dg: &[[[f64; 2]; 2]; 2]) -> [[[f64; 2]; 2]; 2] {
1107        let g_inv = self.inverse();
1108        let mut gamma = [[[0.0f64; 2]; 2]; 2];
1109        for k in 0..2 {
1110            for i in 0..2 {
1111                for j in 0..2 {
1112                    let mut val = 0.0;
1113                    for l in 0..2 {
1114                        let term = dg[j][l][i] + dg[i][l][j] - dg[i][j][l];
1115                        val += g_inv[k][l] * term;
1116                    }
1117                    gamma[k][i][j] = 0.5 * val;
1118                }
1119            }
1120        }
1121        gamma
1122    }
1123    /// Geodesic acceleration: ẍ^k = -Γ^k_ij ẋ^i ẋ^j
1124    pub fn geodesic_acceleration(&self, gamma: &[[[f64; 2]; 2]; 2], vel: &[f64; 2]) -> [f64; 2] {
1125        let mut acc = [0.0f64; 2];
1126        for k in 0..2 {
1127            for i in 0..2 {
1128                for j in 0..2 {
1129                    acc[k] -= gamma[k][i][j] * vel[i] * vel[j];
1130                }
1131            }
1132        }
1133        acc
1134    }
1135}