Skip to main content

proof_engine/quantum/
tunneling.rs

1use std::f64::consts::PI;
2use super::schrodinger::{Complex, WaveFunction1D, SchrodingerSolver1D};
3use super::wavefunction::gaussian_wavepacket;
4
5/// Rectangular potential barrier.
6#[derive(Clone, Debug)]
7pub struct RectangularBarrier {
8    pub x_start: f64,
9    pub x_end: f64,
10    pub height: f64,
11}
12
13impl RectangularBarrier {
14    pub fn new(x_start: f64, x_end: f64, height: f64) -> Self {
15        Self { x_start, x_end, height }
16    }
17
18    pub fn width(&self) -> f64 {
19        self.x_end - self.x_start
20    }
21}
22
23/// Analytical transmission coefficient for a rectangular barrier.
24/// T = 1 / (1 + V0^2 sinh^2(kappa*a) / (4 E (V0 - E))) for E < V0
25/// T = 1 / (1 + V0^2 sin^2(k*a) / (4 E (E - V0))) for E > V0
26pub fn transmission_coefficient(energy: f64, barrier: &RectangularBarrier) -> f64 {
27    let v0 = barrier.height;
28    let a = barrier.width();
29    let mass = 1.0;
30    let hbar = 1.0;
31
32    if energy <= 0.0 {
33        return 0.0;
34    }
35
36    if (energy - v0).abs() < 1e-12 {
37        // Limiting case
38        let m_a_sq = 2.0 * mass * v0 * a * a / (hbar * hbar);
39        return 1.0 / (1.0 + m_a_sq / 4.0);
40    }
41
42    if energy < v0 {
43        let kappa = (2.0 * mass * (v0 - energy)).sqrt() / hbar;
44        let sinh_val = (kappa * a).sinh();
45        1.0 / (1.0 + v0 * v0 * sinh_val * sinh_val / (4.0 * energy * (v0 - energy)))
46    } else {
47        let k = (2.0 * mass * (energy - v0)).sqrt() / hbar;
48        let sin_val = (k * a).sin();
49        1.0 / (1.0 + v0 * v0 * sin_val * sin_val / (4.0 * energy * (energy - v0)))
50    }
51}
52
53/// Result of a tunneling simulation.
54#[derive(Clone, Debug)]
55pub struct TunnelingResult {
56    pub transmitted_prob: f64,
57    pub reflected_prob: f64,
58    pub time_steps: Vec<Vec<f64>>,
59}
60
61/// Tunneling simulation using the Schrodinger solver.
62pub struct TunnelingSimulation {
63    pub solver: SchrodingerSolver1D,
64    pub barrier: RectangularBarrier,
65}
66
67impl TunnelingSimulation {
68    pub fn new(
69        n_points: usize,
70        x_min: f64,
71        x_max: f64,
72        barrier: RectangularBarrier,
73        energy: f64,
74        sigma: f64,
75        mass: f64,
76        hbar: f64,
77        dt: f64,
78    ) -> Self {
79        let dx = (x_max - x_min) / (n_points - 1) as f64;
80        let x_grid: Vec<f64> = (0..n_points).map(|i| x_min + i as f64 * dx).collect();
81
82        // Initial Gaussian wave packet to the left of barrier
83        let x0 = barrier.x_start - 3.0 * sigma;
84        let k0 = (2.0 * mass * energy).sqrt() / hbar;
85        let psi = gaussian_wavepacket(x0, k0, sigma, &x_grid);
86        let mut wf = WaveFunction1D::new(psi, dx, x_min);
87        wf.normalize();
88
89        let potential: Vec<f64> = x_grid
90            .iter()
91            .map(|&x| {
92                if x >= barrier.x_start && x <= barrier.x_end {
93                    barrier.height
94                } else {
95                    0.0
96                }
97            })
98            .collect();
99
100        let solver = SchrodingerSolver1D::new(wf, potential, mass, hbar, dt);
101        Self { solver, barrier }
102    }
103
104    /// Run the simulation for given number of steps.
105    pub fn run(&mut self, steps: usize) -> TunnelingResult {
106        let mut time_steps = Vec::new();
107        let n = self.solver.psi.n();
108        let dx = self.solver.psi.dx;
109        let x_min = self.solver.psi.x_min;
110
111        // Find barrier end index
112        let barrier_end_idx = ((self.barrier.x_end - x_min) / dx).ceil() as usize;
113        let barrier_end_idx = barrier_end_idx.min(n - 1);
114
115        for step in 0..steps {
116            self.solver.step();
117            if step % (steps / 10).max(1) == 0 {
118                let density: Vec<f64> = self.solver.psi.psi.iter().map(|c| c.norm_sq()).collect();
119                time_steps.push(density);
120            }
121        }
122
123        let density: Vec<f64> = self.solver.psi.psi.iter().map(|c| c.norm_sq()).collect();
124        let transmitted_prob: f64 = density[barrier_end_idx..].iter().sum::<f64>() * dx;
125        let reflected_prob: f64 = density[..barrier_end_idx].iter().sum::<f64>() * dx;
126
127        TunnelingResult {
128            transmitted_prob,
129            reflected_prob,
130            time_steps,
131        }
132    }
133}
134
135/// Resonant tunneling through a double barrier. Returns transmission vs energy.
136pub fn double_barrier(
137    b1: &RectangularBarrier,
138    b2: &RectangularBarrier,
139    energies: &[f64],
140) -> Vec<f64> {
141    let mass = 1.0;
142    let hbar = 1.0;
143
144    energies
145        .iter()
146        .map(|&e| {
147            if e <= 0.0 {
148                return 0.0;
149            }
150            // Transfer matrix method for double barrier
151            let k0 = (2.0 * mass * e).sqrt() / hbar;
152
153            let compute_barrier_matrix = |barrier: &RectangularBarrier, e: f64| -> [[Complex; 2]; 2] {
154                let a = barrier.width();
155                if e < barrier.height {
156                    let kappa = (2.0 * mass * (barrier.height - e)).sqrt() / hbar;
157                    let ch = (kappa * a).cosh();
158                    let sh = (kappa * a).sinh();
159                    let r = kappa / k0;
160                    [
161                        [Complex::new(ch, 0.0), Complex::new(sh * (r - 1.0 / r) / 2.0, 0.0)],
162                        [Complex::new(sh * (1.0 / r - r) / 2.0, 0.0), Complex::new(ch, 0.0)],
163                    ]
164                } else {
165                    let k = (2.0 * mass * (e - barrier.height)).sqrt() / hbar;
166                    let c = (k * a).cos();
167                    let s = (k * a).sin();
168                    let r = k / k0;
169                    [
170                        [Complex::new(c, 0.0), Complex::new(s * (r - 1.0 / r) / 2.0, 0.0)],
171                        [Complex::new(-s * (1.0 / r - r) / 2.0, 0.0), Complex::new(c, 0.0)],
172                    ]
173                }
174            };
175
176            // Free propagation between barriers
177            let gap = b2.x_start - b1.x_end;
178            let phase = k0 * gap;
179            let prop = [
180                [Complex::from_polar(1.0, phase), Complex::zero()],
181                [Complex::zero(), Complex::from_polar(1.0, -phase)],
182            ];
183
184            let m1 = compute_barrier_matrix(b1, e);
185            let m2 = compute_barrier_matrix(b2, e);
186
187            // Multiply m2 * prop * m1
188            let mul = |a: &[[Complex; 2]; 2], b: &[[Complex; 2]; 2]| -> [[Complex; 2]; 2] {
189                [
190                    [
191                        a[0][0] * b[0][0] + a[0][1] * b[1][0],
192                        a[0][0] * b[0][1] + a[0][1] * b[1][1],
193                    ],
194                    [
195                        a[1][0] * b[0][0] + a[1][1] * b[1][0],
196                        a[1][0] * b[0][1] + a[1][1] * b[1][1],
197                    ],
198                ]
199            };
200
201            let temp = mul(&prop, &m1);
202            let total = mul(&m2, &temp);
203            let t11 = total[0][0];
204            1.0 / t11.norm_sq()
205        })
206        .collect()
207}
208
209/// WKB approximation for tunneling through an arbitrary potential.
210/// T ~ exp(-2/hbar * integral sqrt(2m(V(x)-E)) dx) from x1 to x2.
211pub fn wkb_approximation(potential: &[f64], energy: f64, x_grid: &[f64]) -> f64 {
212    let mass = 1.0;
213    let hbar = 1.0;
214    let n = potential.len();
215    if n < 2 {
216        return 0.0;
217    }
218    let dx = if n > 1 { x_grid[1] - x_grid[0] } else { 1.0 };
219
220    let mut integral = 0.0;
221    for i in 0..n {
222        if potential[i] > energy {
223            let kappa = (2.0 * mass * (potential[i] - energy)).sqrt() / hbar;
224            integral += kappa * dx;
225        }
226    }
227    (-2.0 * integral).exp()
228}
229
230/// Render tunneling: barrier as wall glyphs, wave packet as brightness.
231pub struct TunnelingRenderer {
232    pub width: usize,
233}
234
235impl TunnelingRenderer {
236    pub fn new(width: usize) -> Self {
237        Self { width }
238    }
239
240    pub fn render(
241        &self,
242        solver: &SchrodingerSolver1D,
243        barrier: &RectangularBarrier,
244    ) -> Vec<(char, f64, f64, f64)> {
245        let n = solver.psi.n();
246        let dx = solver.psi.dx;
247        let x_min = solver.psi.x_min;
248        let mut result = Vec::with_capacity(self.width);
249
250        for i in 0..self.width {
251            let idx = (i * n) / self.width.max(1);
252            let idx = idx.min(n - 1);
253            let x = x_min + idx as f64 * dx;
254
255            if x >= barrier.x_start && x <= barrier.x_end {
256                result.push(('#', 0.5, 0.5, 0.5));
257            } else {
258                let prob = solver.psi.psi[idx].norm_sq();
259                let brightness = (prob * 100.0).min(1.0);
260                let phase = solver.psi.psi[idx].arg();
261                let hue = (phase + PI) / (2.0 * PI);
262                result.push(('.', brightness, hue, 1.0));
263            }
264        }
265        result
266    }
267}
268
269/// Gamow model for alpha decay lifetime.
270/// Uses WKB approximation for Coulomb barrier tunneling.
271pub fn alpha_decay_lifetime(z_daughter: u32, e_alpha_mev: f64, r_nucleus_fm: f64) -> f64 {
272    let z = z_daughter as f64;
273    let e = e_alpha_mev; // MeV
274
275    // Coulomb barrier turning point
276    let e_sq = 1.44; // e^2/(4 pi eps0) in MeV*fm
277    let r_turn = 2.0 * z * e_sq / e; // fm
278
279    if r_turn <= r_nucleus_fm {
280        return 0.0; // No barrier
281    }
282
283    // Gamow factor
284    let eta = z * e_sq * (2.0_f64 * 931.5 * 4.0).sqrt() / (2.0 * 197.3); // dimensionless, approximate
285    let gamow = 2.0 * PI * eta / e.sqrt();
286
287    // Simplified: T ~ exp(-G) where G is the Gamow factor
288    // Approximate integral of Coulomb barrier
289    let r_n = r_nucleus_fm;
290    let r_t = r_turn;
291    let mu = 4.0 * 931.5; // reduced mass in MeV/c^2 (alpha on heavy nucleus)
292    let hbar_c = 197.3; // MeV*fm
293
294    let g = (2.0_f64 * mu).sqrt() / hbar_c
295        * 2.0 * z * e_sq
296        * ((r_t / r_n).sqrt().acos() - (r_n / r_t * (1.0 - r_n / r_t)).sqrt())
297        * r_t.sqrt();
298
299    let transmission = (-2.0_f64 * g).exp();
300
301    // Frequency of alpha hitting barrier ~ v/R
302    let v = (2.0 * e / mu).sqrt() * 3e23; // fm/s (very rough)
303    let freq = v / r_n; // attempts per second
304
305    if transmission > 0.0 {
306        1.0 / (freq * transmission) // lifetime in seconds
307    } else {
308        f64::INFINITY
309    }
310}
311
312#[cfg(test)]
313mod tests {
314    use super::*;
315
316    #[test]
317    fn test_transmission_high_energy() {
318        let barrier = RectangularBarrier::new(0.0, 1.0, 5.0);
319        let t = transmission_coefficient(50.0, &barrier);
320        // E >> V0, should be close to 1 (with oscillations)
321        assert!(t > 0.5, "T at high energy: {}", t);
322    }
323
324    #[test]
325    fn test_transmission_low_energy() {
326        let barrier = RectangularBarrier::new(0.0, 2.0, 10.0);
327        let t = transmission_coefficient(0.1, &barrier);
328        // E << V0, should be very small
329        assert!(t < 0.01, "T at low energy: {}", t);
330    }
331
332    #[test]
333    fn test_transmission_equal_energy() {
334        let barrier = RectangularBarrier::new(0.0, 1.0, 5.0);
335        let t = transmission_coefficient(5.0, &barrier);
336        assert!(t > 0.0 && t <= 1.0, "T at E=V0: {}", t);
337    }
338
339    #[test]
340    fn test_wkb_approximation() {
341        let n = 100;
342        let x_grid: Vec<f64> = (0..n).map(|i| i as f64 * 0.1).collect();
343        let potential: Vec<f64> = x_grid
344            .iter()
345            .map(|&x| if x >= 3.0 && x <= 5.0 { 10.0 } else { 0.0 })
346            .collect();
347        let t_high = wkb_approximation(&potential, 9.0, &x_grid);
348        let t_low = wkb_approximation(&potential, 1.0, &x_grid);
349        assert!(t_high > t_low, "Higher energy should tunnel more");
350    }
351
352    #[test]
353    fn test_double_barrier_resonances() {
354        let b1 = RectangularBarrier::new(0.0, 0.5, 10.0);
355        let b2 = RectangularBarrier::new(2.0, 2.5, 10.0);
356        let energies: Vec<f64> = (1..100).map(|i| i as f64 * 0.1).collect();
357        let trans = double_barrier(&b1, &b2, &energies);
358        // Should have resonance peaks where T approaches 1
359        let max_t = trans.iter().cloned().fold(0.0_f64, f64::max);
360        assert!(max_t > 0.1, "Should have resonance peak, max T: {}", max_t);
361    }
362
363    #[test]
364    fn test_tunneling_simulation() {
365        let barrier = RectangularBarrier::new(3.0, 3.5, 5.0);
366        let mut sim = TunnelingSimulation::new(
367            256, -5.0, 10.0, barrier, 3.0, 1.0, 1.0, 1.0, 0.001,
368        );
369        let result = sim.run(100);
370        let total = result.transmitted_prob + result.reflected_prob;
371        // Total probability should be approximately conserved
372        assert!(total > 0.5 && total < 1.5, "Total prob: {}", total);
373    }
374
375    #[test]
376    fn test_renderer() {
377        let barrier = RectangularBarrier::new(3.0, 3.5, 5.0);
378        let sim = TunnelingSimulation::new(
379            128, -5.0, 10.0, barrier.clone(), 3.0, 1.0, 1.0, 1.0, 0.001,
380        );
381        let renderer = TunnelingRenderer::new(40);
382        let rendered = renderer.render(&sim.solver, &barrier);
383        assert_eq!(rendered.len(), 40);
384        // Should have some barrier characters
385        let barrier_chars: usize = rendered.iter().filter(|&&(c, _, _, _)| c == '#').count();
386        assert!(barrier_chars > 0);
387    }
388
389    #[test]
390    fn test_alpha_decay() {
391        // Polonium-212 alpha decay: Z_daughter=82, E_alpha~8.78 MeV, R~7.1 fm
392        let lifetime = alpha_decay_lifetime(82, 8.78, 7.1);
393        // Should be a very short lifetime (sub-microsecond)
394        assert!(lifetime > 0.0 && lifetime.is_finite());
395    }
396}