1use std::f64::consts::PI;
2use super::schrodinger::{Complex, WaveFunction1D, SchrodingerSolver1D};
3use super::wavefunction::gaussian_wavepacket;
4
5#[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
23pub 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 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#[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
61pub 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 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 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 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
135pub 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 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 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 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
209pub 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
230pub 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
269pub 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; let e_sq = 1.44; let r_turn = 2.0 * z * e_sq / e; if r_turn <= r_nucleus_fm {
280 return 0.0; }
282
283 let eta = z * e_sq * (2.0_f64 * 931.5 * 4.0).sqrt() / (2.0 * 197.3); let gamow = 2.0 * PI * eta / e.sqrt();
286
287 let r_n = r_nucleus_fm;
290 let r_t = r_turn;
291 let mu = 4.0 * 931.5; let hbar_c = 197.3; 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 let v = (2.0 * e / mu).sqrt() * 3e23; let freq = v / r_n; if transmission > 0.0 {
306 1.0 / (freq * transmission) } 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 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 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 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 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 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 let lifetime = alpha_decay_lifetime(82, 8.78, 7.1);
393 assert!(lifetime > 0.0 && lifetime.is_finite());
395 }
396}