Skip to main content

oxilean_std/mathematical_physics/
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};
6
7use super::functions::*;
8use super::functions::{EPSILON_0, MU_0};
9
10/// A simplified (flat) spacetime for geodesic computations.
11///
12/// In flat (Minkowski) spacetime all Christoffel symbols vanish, so geodesics
13/// are straight lines and the curvature is identically zero.
14#[derive(Debug, Clone)]
15pub struct GeodesicEquation {
16    /// Metric components g_{μν} (stored as a square matrix).
17    pub metric_components: Vec<Vec<f64>>,
18}
19impl GeodesicEquation {
20    /// Create a flat (Minkowski-like) metric in `dim` dimensions.
21    pub fn new(dim: usize) -> Self {
22        let mut g = vec![vec![0.0; dim]; dim];
23        for i in 0..dim {
24            g[i][i] = 1.0;
25        }
26        Self {
27            metric_components: g,
28        }
29    }
30    /// Return Γ^i_{jk}: zero for a flat metric (stub).
31    pub fn christoffel_symbol(&self, _i: usize, _j: usize, _k: usize) -> f64 {
32        0.0
33    }
34    /// Return a scalar curvature measure (Ricci scalar); zero for flat metric.
35    pub fn geodesic_deviation(&self) -> f64 {
36        0.0
37    }
38}
39/// Numerical integration method for Hamiltonian systems.
40#[derive(Debug, Clone, PartialEq, Eq)]
41pub enum IntegratorMethod {
42    /// Forward Euler (not symplectic — for reference).
43    Euler,
44    /// Leapfrog / Störmer-Verlet (symplectic, second-order).
45    Leapfrog,
46    /// Runge-Kutta 4 (not symplectic — for reference).
47    RK4,
48    /// Symplectic Euler (first-order symplectic).
49    SymplecticEuler,
50}
51/// A simple cochain complex representing a truncated BRST complex.
52///
53/// Stores cohomology-degree cochains as vectors of real numbers.
54/// The BRST differential Q increases ghost number by 1.
55#[allow(dead_code)]
56#[derive(Debug, Clone)]
57pub struct BRSTComplex {
58    /// Cochains at each ghost number 0, 1, 2, ...
59    pub cochains: Vec<Vec<f64>>,
60    /// The differential matrix Q[k] : C^k → C^{k+1}.
61    pub differentials: Vec<Vec<Vec<f64>>>,
62}
63#[allow(dead_code)]
64impl BRSTComplex {
65    /// Create a zero BRST complex with given dimensions at each degree.
66    pub fn new(dims: Vec<usize>) -> Self {
67        let n = dims.len();
68        let cochains = dims.iter().map(|&d| vec![0.0; d]).collect();
69        let differentials = (0..n.saturating_sub(1))
70            .map(|k| vec![vec![0.0; dims[k]]; dims[k + 1]])
71            .collect();
72        Self {
73            cochains,
74            differentials,
75        }
76    }
77    /// Apply the differential Q at degree k: (Qv)[i] = Σ_j Q[k][i][j] * v[j].
78    pub fn apply_differential(&self, k: usize, v: &[f64]) -> Vec<f64> {
79        if k + 1 >= self.differentials.len() + 1 {
80            return vec![];
81        }
82        let q = &self.differentials[k];
83        q.iter()
84            .map(|row| row.iter().zip(v.iter()).map(|(a, b)| a * b).sum())
85            .collect()
86    }
87    /// Check nilpotency Q² = 0 at degree k (||Q_{k+1} Q_k||_F < ε).
88    pub fn check_nilpotency(&self, k: usize, eps: f64) -> bool {
89        if k + 1 >= self.differentials.len() {
90            return true;
91        }
92        let qk = &self.differentials[k];
93        let qk1 = &self.differentials[k + 1];
94        let rows1 = qk1.len();
95        let cols1 = if rows1 > 0 {
96            qk1[0].len()
97        } else {
98            return true;
99        };
100        let cols0 = if !qk.is_empty() {
101            qk[0].len()
102        } else {
103            return true;
104        };
105        for i in 0..rows1 {
106            for j in 0..cols0 {
107                let val: f64 = (0..cols1)
108                    .map(|l| {
109                        qk1[i].get(l).copied().unwrap_or(0.0)
110                            * qk.get(l).and_then(|r| r.get(j)).copied().unwrap_or(0.0)
111                    })
112                    .sum();
113                if val.abs() > eps {
114                    return false;
115                }
116            }
117        }
118        true
119    }
120    /// Compute the Euler characteristic χ = Σ (-1)^k dim C^k.
121    pub fn euler_characteristic(&self) -> i64 {
122        self.cochains
123            .iter()
124            .enumerate()
125            .map(|(k, c)| {
126                if k % 2 == 0 {
127                    c.len() as i64
128                } else {
129                    -(c.len() as i64)
130                }
131            })
132            .sum()
133    }
134    /// Return the dimensions of the cochain spaces.
135    pub fn dimensions(&self) -> Vec<usize> {
136        self.cochains.iter().map(|c| c.len()).collect()
137    }
138}
139/// Exact one-soliton solution of the KdV equation.
140///
141/// The KdV equation is: ∂u/∂t − 6u ∂u/∂x + ∂³u/∂x³ = 0.
142/// (Using the convention with the −6u ux term.)
143///
144/// The one-soliton solution is: u(x, t) = −κ²/2 · sech²(κ/2 · (x − κ²t − x₀)).
145#[derive(Debug, Clone)]
146pub struct KdVSoliton {
147    /// Wave-number parameter κ > 0.  Speed = κ².
148    pub kappa: f64,
149    /// Initial position x₀.
150    pub x0: f64,
151}
152impl KdVSoliton {
153    /// Create a soliton with wave-number `kappa` centred at `x0`.
154    pub fn new(kappa: f64, x0: f64) -> Self {
155        assert!(kappa > 0.0, "kappa must be positive");
156        Self { kappa, x0 }
157    }
158    /// Evaluate u(x, t).
159    pub fn eval(&self, x: f64, t: f64) -> f64 {
160        let xi = 0.5 * self.kappa * (x - self.kappa * self.kappa * t - self.x0);
161        let sech = 1.0 / xi.cosh();
162        -0.5 * self.kappa * self.kappa * sech * sech
163    }
164    /// Soliton speed c = κ².
165    pub fn speed(&self) -> f64 {
166        self.kappa * self.kappa
167    }
168    /// Peak amplitude A = −κ²/2.
169    pub fn amplitude(&self) -> f64 {
170        -0.5 * self.kappa * self.kappa
171    }
172    /// Evaluate the soliton at multiple (x, t) pairs.
173    pub fn eval_grid(&self, xs: &[f64], t: f64) -> Vec<f64> {
174        xs.iter().map(|&x| self.eval(x, t)).collect()
175    }
176}
177/// A complete Hamiltonian system with state (q, p) and a user-supplied
178/// Hamiltonian gradient function.
179///
180/// Uses symplectic Euler integration by default for long-time stability.
181#[derive(Debug, Clone)]
182pub struct HamiltonianSystem {
183    /// Generalised coordinates q.
184    pub q: Vec<f64>,
185    /// Conjugate momenta p.
186    pub p: Vec<f64>,
187    /// Time step.
188    pub dt: f64,
189    /// Current simulation time.
190    pub time: f64,
191}
192impl HamiltonianSystem {
193    /// Create a new system with given initial conditions and time step.
194    pub fn new(q: Vec<f64>, p: Vec<f64>, dt: f64) -> Self {
195        assert_eq!(q.len(), p.len(), "q and p must have the same dimension");
196        Self {
197            q,
198            p,
199            dt,
200            time: 0.0,
201        }
202    }
203    /// Advance one time step using symplectic Euler integration.
204    ///
205    /// `grad_h(q, p)` must return `(∂H/∂p, ∂H/∂q)`.
206    pub fn step(&mut self, grad_h: &dyn Fn(&[f64], &[f64]) -> (Vec<f64>, Vec<f64>)) {
207        let integrator = SymplecticIntegrator {
208            dt: self.dt,
209            method: IntegratorMethod::SymplecticEuler,
210        };
211        let (q_new, p_new) = integrator.step(&self.q, &self.p, grad_h);
212        self.q = q_new;
213        self.p = p_new;
214        self.time += self.dt;
215    }
216    /// Run `n` steps and return the trajectory as `(q_history, p_history)`.
217    pub fn run(
218        &mut self,
219        n: usize,
220        grad_h: &dyn Fn(&[f64], &[f64]) -> (Vec<f64>, Vec<f64>),
221    ) -> (Vec<Vec<f64>>, Vec<Vec<f64>>) {
222        let mut qs = Vec::with_capacity(n);
223        let mut ps = Vec::with_capacity(n);
224        for _ in 0..n {
225            self.step(grad_h);
226            qs.push(self.q.clone());
227            ps.push(self.p.clone());
228        }
229        (qs, ps)
230    }
231    /// Evaluate a scalar Hamiltonian H(q,p) = Σ p_i²/(2m_i) + V(q).
232    /// Provided for convenience; `grad_h` must still be supplied externally.
233    pub fn kinetic_energy(&self, masses: &[f64]) -> f64 {
234        self.p
235            .iter()
236            .zip(masses.iter())
237            .map(|(&pi, &mi)| pi * pi / (2.0 * mi))
238            .sum()
239    }
240}
241/// A classical point particle in n-dimensional space.
242#[derive(Debug, Clone)]
243pub struct ClassicalParticle {
244    /// Mass of the particle (kg).
245    pub mass: f64,
246    /// Position vector (m).
247    pub position: Vec<f64>,
248    /// Velocity vector (m/s).
249    pub velocity: Vec<f64>,
250}
251impl ClassicalParticle {
252    /// Create a new particle at the origin with zero velocity.
253    pub fn new(mass: f64, dim: usize) -> Self {
254        Self {
255            mass,
256            position: vec![0.0; dim],
257            velocity: vec![0.0; dim],
258        }
259    }
260    /// Kinetic energy: K = ½ m v².
261    pub fn kinetic_energy(&self) -> f64 {
262        let v_sq: f64 = self.velocity.iter().map(|&vi| vi * vi).sum();
263        0.5 * self.mass * v_sq
264    }
265    /// Set the position vector.
266    pub fn set_position(&mut self, pos: Vec<f64>) {
267        self.position = pos;
268    }
269    /// Set the velocity vector.
270    pub fn set_velocity(&mut self, vel: Vec<f64>) {
271        self.velocity = vel;
272    }
273    /// Linear momentum: p = m v.
274    pub fn momentum(&self) -> Vec<f64> {
275        self.velocity.iter().map(|&vi| self.mass * vi).collect()
276    }
277}
278/// A simple Path Integral Monte Carlo estimator for 1D quantum mechanics.
279///
280/// Uses a discrete Euclidean path (imaginary time) to estimate the ground
281/// state energy via the virial estimator.
282#[allow(dead_code)]
283#[derive(Debug, Clone)]
284pub struct PathIntegralMonteCarlo {
285    /// Number of time slices.
286    pub n_slices: usize,
287    /// Imaginary time step τ = β/N.
288    pub tau: f64,
289    /// Particle mass.
290    pub mass: f64,
291    /// Current path (positions at each time slice).
292    pub path: Vec<f64>,
293}
294#[allow(dead_code)]
295impl PathIntegralMonteCarlo {
296    /// Create a new PIMC estimator with all positions set to zero.
297    pub fn new(n_slices: usize, beta: f64, mass: f64) -> Self {
298        Self {
299            n_slices,
300            tau: beta / (n_slices as f64),
301            mass,
302            path: vec![0.0; n_slices],
303        }
304    }
305    /// Euclidean (kinetic) action for the current path.
306    /// S_E = Σ_k m/(2τ) * (x_{k+1} − x_k)²
307    pub fn kinetic_action(&self) -> f64 {
308        let prefactor = self.mass / (2.0 * self.tau);
309        let n = self.n_slices;
310        (0..n)
311            .map(|k| {
312                let diff = self.path[(k + 1) % n] - self.path[k];
313                prefactor * diff * diff
314            })
315            .sum()
316    }
317    /// Harmonic potential action contribution.
318    /// S_V = τ * Σ_k ½ ω² x_k²
319    pub fn potential_action_harmonic(&self, omega: f64) -> f64 {
320        self.tau
321            * self
322                .path
323                .iter()
324                .map(|&x| 0.5 * omega * omega * x * x)
325                .sum::<f64>()
326    }
327    /// Total Euclidean action (kinetic + harmonic potential).
328    pub fn total_action_harmonic(&self, omega: f64) -> f64 {
329        self.kinetic_action() + self.potential_action_harmonic(omega)
330    }
331    /// Estimate the ground state energy via the virial theorem (harmonic case).
332    /// E_0 ≈ ½ω² ⟨x²⟩ + ½/(2m τ²) corrections (simplified).
333    pub fn virial_energy_estimate(&self, omega: f64) -> f64 {
334        let x_sq: f64 = self.path.iter().map(|&x| x * x).sum::<f64>() / (self.n_slices as f64);
335        0.5 * omega * omega * x_sq
336    }
337    /// Set the path to a given configuration.
338    pub fn set_path(&mut self, path: Vec<f64>) {
339        assert_eq!(path.len(), self.n_slices);
340        self.path = path;
341    }
342    /// Compute the winding number (only meaningful for a ring).
343    pub fn winding_number(&self) -> f64 {
344        let first = self.path[0];
345        let last = self.path[self.n_slices - 1];
346        (last - first) / (2.0 * std::f64::consts::PI)
347    }
348}
349/// 1D Schrödinger equation propagator on a uniform spatial grid.
350///
351/// Evolves a complex wave function ψ(x,t) under H = −(ħ²/2m) d²/dx² + V(x)
352/// using the split-operator method: `exp(−iHΔt/ħ) ≈ exp(−iVΔt/2ħ) exp(−iTΔt/ħ) exp(−iVΔt/2ħ)`.
353///
354/// Here we use a simplified real-valued approximation (ignoring the imaginary
355/// time phase) to stay dependency-free.
356#[derive(Debug, Clone)]
357pub struct SchrodingerPropagator {
358    /// Spatial grid size (number of points).
359    pub n_grid: usize,
360    /// Spatial step Δx (m).
361    pub dx: f64,
362    /// Particle mass (kg, in units where ħ = 1).
363    pub mass: f64,
364    /// Time step Δt.
365    pub dt: f64,
366    /// Potential energy at each grid point V(x_i).
367    pub potential: Vec<f64>,
368}
369impl SchrodingerPropagator {
370    /// Create a propagator on `n_grid` points with spacing `dx`.
371    pub fn new(n_grid: usize, dx: f64, mass: f64, dt: f64, potential: Vec<f64>) -> Self {
372        assert_eq!(
373            potential.len(),
374            n_grid,
375            "potential length must equal n_grid"
376        );
377        Self {
378            n_grid,
379            dx,
380            mass,
381            dt,
382            potential,
383        }
384    }
385    /// Apply one kinetic-energy half-step via the finite-difference Laplacian.
386    ///
387    /// Returns the real part of `exp(−i T Δt) ψ` using a first-order
388    /// Euler approximation: `ψ' = ψ − i(Δt/2m Δx²) Δ_disc ψ`.
389    ///
390    /// Stores (re, im) as two parallel `Vec<f64>`.
391    pub fn kinetic_half_step(&self, psi_re: &[f64], psi_im: &[f64]) -> (Vec<f64>, Vec<f64>) {
392        let coeff = self.dt / (2.0 * self.mass * self.dx * self.dx);
393        let n = self.n_grid;
394        let mut re_out = vec![0.0; n];
395        let mut im_out = vec![0.0; n];
396        for i in 0..n {
397            let left = if i == 0 { 0.0 } else { 1.0 };
398            let right = if i + 1 == n { 0.0 } else { 1.0 };
399            let lap_re = left * psi_re[i.saturating_sub(1)] - 2.0 * psi_re[i]
400                + right * psi_re[(i + 1).min(n - 1)];
401            let lap_im = left * psi_im[i.saturating_sub(1)] - 2.0 * psi_im[i]
402                + right * psi_im[(i + 1).min(n - 1)];
403            re_out[i] = psi_re[i] - coeff * lap_im;
404            im_out[i] = psi_im[i] + coeff * lap_re;
405        }
406        (re_out, im_out)
407    }
408    /// Apply one potential-energy phase step: `exp(−iV(x)Δt/2) ψ`.
409    pub fn potential_half_step(&self, psi_re: &[f64], psi_im: &[f64]) -> (Vec<f64>, Vec<f64>) {
410        let mut re_out = vec![0.0; self.n_grid];
411        let mut im_out = vec![0.0; self.n_grid];
412        for i in 0..self.n_grid {
413            let phase = -self.potential[i] * self.dt / 2.0;
414            let (s, c) = phase.sin_cos();
415            re_out[i] = psi_re[i] * c - psi_im[i] * s;
416            im_out[i] = psi_re[i] * s + psi_im[i] * c;
417        }
418        (re_out, im_out)
419    }
420    /// Full split-operator step: V/2 → T → V/2.
421    pub fn step(&self, psi_re: &[f64], psi_im: &[f64]) -> (Vec<f64>, Vec<f64>) {
422        let (r1, i1) = self.potential_half_step(psi_re, psi_im);
423        let (r2, i2) = self.kinetic_half_step(&r1, &i1);
424        self.potential_half_step(&r2, &i2)
425    }
426    /// Compute the norm ‖ψ‖² = Σ (|ψ_i|²) dx.
427    pub fn norm_squared(&self, psi_re: &[f64], psi_im: &[f64]) -> f64 {
428        psi_re
429            .iter()
430            .zip(psi_im.iter())
431            .map(|(&r, &i)| (r * r + i * i) * self.dx)
432            .sum()
433    }
434}
435/// A vector in a bosonic Fock space, represented by occupation numbers.
436///
437/// The state |n_0, n_1, ..., n_{k-1}⟩ specifies how many particles occupy
438/// each single-particle mode.
439#[derive(Debug, Clone, PartialEq, Eq)]
440pub struct FockSpaceVec {
441    /// Occupation numbers for each mode.
442    pub occupations: Vec<u64>,
443}
444impl FockSpaceVec {
445    /// Create a vacuum state |0,0,...,0⟩ with `n_modes` modes.
446    pub fn vacuum(n_modes: usize) -> Self {
447        Self {
448            occupations: vec![0; n_modes],
449        }
450    }
451    /// Total particle number N = Σ n_k.
452    pub fn total_number(&self) -> u64 {
453        self.occupations.iter().sum()
454    }
455    /// Apply creation operator a†_k: increase mode k by 1.
456    /// Returns `None` if `k` is out of bounds.
457    /// The norm factor √(n_k + 1) is returned as the second element.
458    pub fn apply_creation(&self, k: usize) -> Option<(Self, f64)> {
459        if k >= self.occupations.len() {
460            return None;
461        }
462        let mut new_occ = self.occupations.clone();
463        let n_k = new_occ[k];
464        new_occ[k] += 1;
465        let norm = ((n_k + 1) as f64).sqrt();
466        Some((
467            Self {
468                occupations: new_occ,
469            },
470            norm,
471        ))
472    }
473    /// Apply annihilation operator a_k: decrease mode k by 1.
474    /// Returns `None` if `k` is out of bounds or mode is already empty.
475    /// The norm factor √(n_k) is returned as the second element.
476    pub fn apply_annihilation(&self, k: usize) -> Option<(Self, f64)> {
477        if k >= self.occupations.len() || self.occupations[k] == 0 {
478            return None;
479        }
480        let mut new_occ = self.occupations.clone();
481        let n_k = new_occ[k];
482        new_occ[k] -= 1;
483        let norm = (n_k as f64).sqrt();
484        Some((
485            Self {
486                occupations: new_occ,
487            },
488            norm,
489        ))
490    }
491    /// Number operator expectation value for mode k.
492    pub fn number_op(&self, k: usize) -> u64 {
493        self.occupations.get(k).copied().unwrap_or(0)
494    }
495    /// Inner product ⟨self | other⟩: 1 if occupation vectors are identical, else 0.
496    pub fn inner_product(&self, other: &FockSpaceVec) -> f64 {
497        if self.occupations == other.occupations {
498            1.0
499        } else {
500            0.0
501        }
502    }
503}
504/// A classical electromagnetic field at a single point.
505#[derive(Debug, Clone)]
506pub struct ElectromagneticField {
507    /// Electric field vector (V/m).
508    pub e: Vec<f64>,
509    /// Magnetic field vector (T).
510    pub b: Vec<f64>,
511}
512impl ElectromagneticField {
513    /// Create a zero electromagnetic field.
514    pub fn new() -> Self {
515        Self {
516            e: vec![0.0; 3],
517            b: vec![0.0; 3],
518        }
519    }
520    /// Electromagnetic energy density u = (ε₀|E|² + |B|²/μ₀) / 2.
521    pub fn energy_density(&self) -> f64 {
522        let e_sq: f64 = self.e.iter().map(|&ei| ei * ei).sum();
523        let b_sq: f64 = self.b.iter().map(|&bi| bi * bi).sum();
524        (EPSILON_0 * e_sq + b_sq / MU_0) / 2.0
525    }
526    /// Poynting vector S = E × B / μ₀.
527    pub fn poynting_vector(&self) -> Vec<f64> {
528        let cross = cross3(&self.e, &self.b);
529        cross.into_iter().map(|c| c / MU_0).collect()
530    }
531    /// Lorentz force on a charge q moving with velocity v: F = q(E + v × B).
532    pub fn lorentz_force(&self, q: f64, v: &[f64]) -> Vec<f64> {
533        let vxb = cross3(v, &self.b);
534        (0..3).map(|i| q * (self.e[i] + vxb[i])).collect()
535    }
536}
537/// Data encoding a mirror symmetry pair of Calabi-Yau manifolds.
538///
539/// Tracks the Hodge numbers h^{p,q} of both manifolds and verifies the
540/// mirror exchange h^{p,q}(X) = h^{n-p,q}(Y).
541#[allow(dead_code)]
542#[derive(Debug, Clone)]
543pub struct MirrorSymmetryData {
544    /// Complex dimension n.
545    pub complex_dim: usize,
546    /// Hodge numbers h^{p,q} of the manifold X (stored as a matrix).
547    pub hodge_x: Vec<Vec<u64>>,
548    /// Hodge numbers h^{p,q} of the mirror Y.
549    pub hodge_y: Vec<Vec<u64>>,
550}
551#[allow(dead_code)]
552impl MirrorSymmetryData {
553    /// Create a mirror symmetry data structure with given dimension.
554    pub fn new(complex_dim: usize) -> Self {
555        let n = complex_dim + 1;
556        Self {
557            complex_dim,
558            hodge_x: vec![vec![0; n]; n],
559            hodge_y: vec![vec![0; n]; n],
560        }
561    }
562    /// Set h^{p,q}(X).
563    pub fn set_hodge_x(&mut self, p: usize, q: usize, val: u64) {
564        self.hodge_x[p][q] = val;
565    }
566    /// Set h^{p,q}(Y).
567    pub fn set_hodge_y(&mut self, p: usize, q: usize, val: u64) {
568        self.hodge_y[p][q] = val;
569    }
570    /// Verify mirror symmetry: h^{p,q}(X) = h^{n-p,q}(Y) for all p, q.
571    pub fn verify_mirror(&self) -> bool {
572        let n = self.complex_dim;
573        for p in 0..=n {
574            for q in 0..=n {
575                if n < p {
576                    continue;
577                }
578                let mirror_p = n - p;
579                if self.hodge_x[p][q] != self.hodge_y[mirror_p][q] {
580                    return false;
581                }
582            }
583        }
584        true
585    }
586    /// Euler characteristic χ = Σ_{p,q} (-1)^{p+q} h^{p,q}.
587    pub fn euler_characteristic_x(&self) -> i64 {
588        self.hodge_x
589            .iter()
590            .enumerate()
591            .flat_map(|(p, row)| {
592                row.iter().enumerate().map(move |(q, &h)| {
593                    if (p + q) % 2 == 0 {
594                        h as i64
595                    } else {
596                        -(h as i64)
597                    }
598                })
599            })
600            .sum()
601    }
602    /// Hodge number h^{1,1} for X (Kähler moduli count).
603    pub fn h11_x(&self) -> u64 {
604        if self.complex_dim >= 1 {
605            self.hodge_x[1][1]
606        } else {
607            0
608        }
609    }
610    /// Hodge number h^{1,1} for Y (mirror = h^{n-1,1}(X)).
611    pub fn h11_y(&self) -> u64 {
612        if self.complex_dim >= 1 {
613            self.hodge_y[1][1]
614        } else {
615            0
616        }
617    }
618}
619/// A numerical integrator for Hamiltonian mechanics.
620#[derive(Debug, Clone)]
621pub struct SymplecticIntegrator {
622    /// Time step (s).
623    pub dt: f64,
624    /// Integration method.
625    pub method: IntegratorMethod,
626}
627impl SymplecticIntegrator {
628    /// Advance the state `(q, p)` by one time step.
629    ///
630    /// `grad_H` returns `(∂H/∂p, ∂H/∂q)` — the Hamiltonian gradients used
631    /// to compute Hamilton's equations q̇ = ∂H/∂p, ṗ = −∂H/∂q.
632    pub fn step(
633        &self,
634        q: &[f64],
635        p: &[f64],
636        grad_h: &dyn Fn(&[f64], &[f64]) -> (Vec<f64>, Vec<f64>),
637    ) -> (Vec<f64>, Vec<f64>) {
638        let dt = self.dt;
639        match self.method {
640            IntegratorMethod::Euler => {
641                let (dh_dp, dh_dq) = grad_h(q, p);
642                let q_new: Vec<f64> = q
643                    .iter()
644                    .zip(&dh_dp)
645                    .map(|(&qi, &di)| qi + dt * di)
646                    .collect();
647                let p_new: Vec<f64> = p
648                    .iter()
649                    .zip(&dh_dq)
650                    .map(|(&pi, &di)| pi - dt * di)
651                    .collect();
652                (q_new, p_new)
653            }
654            IntegratorMethod::SymplecticEuler => {
655                let (_, dh_dq) = grad_h(q, p);
656                let p_new: Vec<f64> = p
657                    .iter()
658                    .zip(&dh_dq)
659                    .map(|(&pi, &di)| pi - dt * di)
660                    .collect();
661                let (dh_dp_new, _) = grad_h(q, &p_new);
662                let q_new: Vec<f64> = q
663                    .iter()
664                    .zip(&dh_dp_new)
665                    .map(|(&qi, &di)| qi + dt * di)
666                    .collect();
667                (q_new, p_new)
668            }
669            IntegratorMethod::Leapfrog => {
670                let (_, dh_dq) = grad_h(q, p);
671                let p_half: Vec<f64> = p
672                    .iter()
673                    .zip(&dh_dq)
674                    .map(|(&pi, &di)| pi - 0.5 * dt * di)
675                    .collect();
676                let (dh_dp_half, _) = grad_h(q, &p_half);
677                let q_new: Vec<f64> = q
678                    .iter()
679                    .zip(&dh_dp_half)
680                    .map(|(&qi, &di)| qi + dt * di)
681                    .collect();
682                let (_, dh_dq_new) = grad_h(&q_new, &p_half);
683                let p_new: Vec<f64> = p_half
684                    .iter()
685                    .zip(&dh_dq_new)
686                    .map(|(&pi, &di)| pi - 0.5 * dt * di)
687                    .collect();
688                (q_new, p_new)
689            }
690            IntegratorMethod::RK4 => {
691                let f = |q: &[f64], p: &[f64]| -> (Vec<f64>, Vec<f64>) {
692                    let (dh_dp, dh_dq) = grad_h(q, p);
693                    let dq: Vec<f64> = dh_dp;
694                    let dp: Vec<f64> = dh_dq.iter().map(|&d| -d).collect();
695                    (dq, dp)
696                };
697                let (k1q, k1p) = f(q, p);
698                let q1: Vec<f64> = q
699                    .iter()
700                    .zip(&k1q)
701                    .map(|(&qi, &ki)| qi + 0.5 * dt * ki)
702                    .collect();
703                let p1: Vec<f64> = p
704                    .iter()
705                    .zip(&k1p)
706                    .map(|(&pi, &ki)| pi + 0.5 * dt * ki)
707                    .collect();
708                let (k2q, k2p) = f(&q1, &p1);
709                let q2: Vec<f64> = q
710                    .iter()
711                    .zip(&k2q)
712                    .map(|(&qi, &ki)| qi + 0.5 * dt * ki)
713                    .collect();
714                let p2: Vec<f64> = p
715                    .iter()
716                    .zip(&k2p)
717                    .map(|(&pi, &ki)| pi + 0.5 * dt * ki)
718                    .collect();
719                let (k3q, k3p) = f(&q2, &p2);
720                let q3: Vec<f64> = q.iter().zip(&k3q).map(|(&qi, &ki)| qi + dt * ki).collect();
721                let p3: Vec<f64> = p.iter().zip(&k3p).map(|(&pi, &ki)| pi + dt * ki).collect();
722                let (k4q, k4p) = f(&q3, &p3);
723                let q_new: Vec<f64> = q
724                    .iter()
725                    .enumerate()
726                    .map(|(i, &qi)| qi + dt / 6.0 * (k1q[i] + 2.0 * k2q[i] + 2.0 * k3q[i] + k4q[i]))
727                    .collect();
728                let p_new: Vec<f64> = p
729                    .iter()
730                    .enumerate()
731                    .map(|(i, &pi)| pi + dt / 6.0 * (k1p[i] + 2.0 * k2p[i] + 2.0 * k3p[i] + k4p[i]))
732                    .collect();
733                (q_new, p_new)
734            }
735        }
736    }
737}
738/// A U(1) lattice gauge field on a 2D periodic lattice.
739///
740/// Each link (i, j, mu) stores a phase angle θ ∈ [−π, π], representing
741/// the gauge connection U_{i,mu} = e^{iθ_{i,mu}}.
742///
743/// The Wilson plaquette action is S = β Σ_{plaq} (1 − cos(θ_plaq)).
744#[derive(Debug, Clone)]
745pub struct GaugeField {
746    /// Lattice size in each spatial direction.
747    pub size: usize,
748    /// Link angles: `links[mu][i * size + j]` for direction mu, site (i,j).
749    pub links: Vec<Vec<f64>>,
750    /// Inverse coupling β = 1/g².
751    pub beta: f64,
752}
753impl GaugeField {
754    /// Create a cold-start gauge field (all links set to zero angle).
755    pub fn new_cold(size: usize, beta: f64) -> Self {
756        let n = size * size;
757        Self {
758            size,
759            links: vec![vec![0.0; n], vec![0.0; n]],
760            beta,
761        }
762    }
763    /// Site index for lattice coordinate (i, j) with periodic boundary.
764    fn site(&self, i: usize, j: usize) -> usize {
765        (i % self.size) * self.size + (j % self.size)
766    }
767    /// Compute the plaquette angle sum θ_01(i,j) = θ_0(i,j) + θ_1(i+1,j)
768    ///                                               − θ_0(i,j+1) − θ_1(i,j).
769    pub fn plaquette_angle(&self, i: usize, j: usize) -> f64 {
770        let s00 = self.site(i, j);
771        let s10 = self.site(i + 1, j);
772        let s01 = self.site(i, j + 1);
773        self.links[0][s00] + self.links[1][s10] - self.links[0][s01] - self.links[1][s00]
774    }
775    /// Wilson plaquette action: S = β Σ (1 − cos θ_plaq).
776    pub fn action(&self) -> f64 {
777        let n = self.size;
778        let mut s = 0.0;
779        for i in 0..n {
780            for j in 0..n {
781                s += 1.0 - self.plaquette_angle(i, j).cos();
782            }
783        }
784        self.beta * s
785    }
786    /// Polyakov loop in the μ=0 direction at column j: L(j) = Π_i U_{i,0}(j).
787    pub fn polyakov_loop(&self, j: usize) -> f64 {
788        let angle: f64 = (0..self.size).map(|i| self.links[0][self.site(i, j)]).sum();
789        angle.cos()
790    }
791}
792/// The supersymmetric harmonic oscillator spectrum.
793///
794/// The SUSY oscillator has bosonic and fermionic modes.
795/// States are labeled by (n_b, n_f) where n_b ∈ ℕ and n_f ∈ {0, 1}.
796/// Energy = ω * n_b (zero-point energies cancel).
797#[allow(dead_code)]
798#[derive(Debug, Clone)]
799pub struct SupersymmetricOscillator {
800    /// Angular frequency ω.
801    pub omega: f64,
802}
803#[allow(dead_code)]
804impl SupersymmetricOscillator {
805    /// Create a SUSY oscillator with given frequency.
806    pub fn new(omega: f64) -> Self {
807        assert!(omega > 0.0, "omega must be positive");
808        Self { omega }
809    }
810    /// Energy of state (n_b, n_f): E = ω * n_b.
811    pub fn energy(&self, n_b: u64, _n_f: u64) -> f64 {
812        self.omega * (n_b as f64)
813    }
814    /// Number of states at energy level E = ω * n (two states for n > 0, one for n = 0).
815    pub fn degeneracy(&self, n: u64) -> u64 {
816        if n == 0 {
817            1
818        } else {
819            2
820        }
821    }
822    /// Witten index: Tr((-1)^F e^{-βH}) = 1 (SUSY unbroken).
823    pub fn witten_index(&self) -> i64 {
824        1
825    }
826    /// Partition function Z(β) = Σ_n e^{-βEn} * degeneracy(n), truncated.
827    pub fn partition_function(&self, beta: f64, n_max: u64) -> f64 {
828        (0..=n_max)
829            .map(|n| {
830                let e = self.energy(n, 0);
831                (self.degeneracy(n) as f64) * (-beta * e).exp()
832            })
833            .sum()
834    }
835    /// Supersymmetric ground state energy (exactly zero).
836    pub fn ground_state_energy(&self) -> f64 {
837        0.0
838    }
839}
840/// A tracker for Donaldson-Witten invariants of a smooth 4-manifold.
841///
842/// Stores the instanton moduli space dimensions and mock invariant values
843/// for different instantiation numbers k.
844#[allow(dead_code)]
845#[derive(Debug, Clone)]
846pub struct DonaldsonWitten {
847    /// Name of the 4-manifold.
848    pub manifold_name: String,
849    /// Signature σ of the manifold.
850    pub signature: i64,
851    /// Euler characteristic χ.
852    pub euler_char: i64,
853    /// Stored Donaldson invariants: (instanton_number, invariant_value).
854    pub invariants: Vec<(u64, f64)>,
855}
856#[allow(dead_code)]
857impl DonaldsonWitten {
858    /// Create a new invariant tracker.
859    pub fn new(name: impl Into<String>, signature: i64, euler_char: i64) -> Self {
860        Self {
861            manifold_name: name.into(),
862            signature,
863            euler_char,
864            invariants: Vec::new(),
865        }
866    }
867    /// Virtual dimension of the instanton moduli space M_k(G=SU(2)):
868    /// dim M_k = 8k − 3(1 + b₁ − b₂⁺).
869    pub fn virtual_dimension(&self, k: u64) -> i64 {
870        let b2_plus = (self.euler_char + self.signature) / 2;
871        let b1 = 0i64;
872        8 * (k as i64) - 3 * (1 + b1 - b2_plus)
873    }
874    /// Add a Donaldson invariant for instanton number k.
875    pub fn add_invariant(&mut self, k: u64, value: f64) {
876        self.invariants.push((k, value));
877    }
878    /// Look up the invariant for instanton number k.
879    pub fn get_invariant(&self, k: u64) -> Option<f64> {
880        self.invariants
881            .iter()
882            .find(|&&(kk, _)| kk == k)
883            .map(|&(_, v)| v)
884    }
885    /// b₂⁺ = (χ + σ) / 2.
886    pub fn b2_plus(&self) -> i64 {
887        (self.euler_char + self.signature) / 2
888    }
889    /// b₂⁻ = (χ − σ) / 2.
890    pub fn b2_minus(&self) -> i64 {
891        (self.euler_char - self.signature) / 2
892    }
893}