1use std::f64::consts::PI;
2use super::schrodinger::{Complex, SchrodingerSolver2D};
3
4#[derive(Clone, Debug)]
6pub struct DoubleSlitSetup {
7 pub slit_width: f64,
8 pub slit_separation: f64,
9 pub wavelength: f64,
10 pub screen_distance: f64,
11}
12
13impl DoubleSlitSetup {
14 pub fn new(slit_width: f64, slit_separation: f64, wavelength: f64, screen_distance: f64) -> Self {
15 Self { slit_width, slit_separation, wavelength, screen_distance }
16 }
17}
18
19pub fn intensity_pattern(setup: &DoubleSlitSetup, screen_positions: &[f64]) -> Vec<f64> {
22 let d = setup.slit_separation;
23 let a = setup.slit_width;
24 let lambda = setup.wavelength;
25 let l = setup.screen_distance;
26
27 screen_positions
28 .iter()
29 .map(|&y| {
30 let sin_theta = y / (y * y + l * l).sqrt();
31
32 let phase_d = PI * d * sin_theta / lambda;
34 let interference = phase_d.cos().powi(2);
35
36 let phase_a = PI * a * sin_theta / lambda;
38 let diffraction = if phase_a.abs() < 1e-12 {
39 1.0
40 } else {
41 (phase_a.sin() / phase_a).powi(2)
42 };
43
44 interference * diffraction
45 })
46 .collect()
47}
48
49pub fn single_slit_pattern(width: f64, wavelength: f64, angles: &[f64]) -> Vec<f64> {
51 angles
52 .iter()
53 .map(|&theta| {
54 let beta = PI * width * theta.sin() / wavelength;
55 if beta.abs() < 1e-12 {
56 1.0
57 } else {
58 (beta.sin() / beta).powi(2)
59 }
60 })
61 .collect()
62}
63
64pub fn n_slit_pattern(n: usize, width: f64, separation: f64, wavelength: f64, angles: &[f64]) -> Vec<f64> {
66 if n == 0 {
67 return vec![0.0; angles.len()];
68 }
69 angles
70 .iter()
71 .map(|&theta| {
72 let sin_t = theta.sin();
73 let beta = PI * width * sin_t / wavelength;
75 let envelope = if beta.abs() < 1e-12 {
76 1.0
77 } else {
78 (beta.sin() / beta).powi(2)
79 };
80
81 let delta = PI * separation * sin_t / wavelength;
83 let n_delta = n as f64 * delta;
84 let multi_slit = if delta.abs() < 1e-12 {
85 (n * n) as f64
86 } else {
87 (n_delta.sin() / delta.sin()).powi(2)
88 };
89
90 envelope * multi_slit / (n * n) as f64
91 })
92 .collect()
93}
94
95pub struct DoubleSlitSimulation {
97 pub solver: SchrodingerSolver2D,
98 pub slit_mask: Vec<Vec<bool>>,
99}
100
101impl DoubleSlitSimulation {
102 pub fn new(
104 nx: usize,
105 ny: usize,
106 dx: f64,
107 dy: f64,
108 dt: f64,
109 wall_x_idx: usize,
110 slit1_y: (usize, usize),
111 slit2_y: (usize, usize),
112 barrier_height: f64,
113 ) -> Self {
114 let mut potential = vec![vec![0.0; ny]; nx];
115 let mut slit_mask = vec![vec![false; ny]; nx];
116
117 for j in 0..ny {
119 let is_slit = (j >= slit1_y.0 && j <= slit1_y.1)
120 || (j >= slit2_y.0 && j <= slit2_y.1);
121 if !is_slit {
122 potential[wall_x_idx][j] = barrier_height;
123 }
124 slit_mask[wall_x_idx][j] = is_slit;
125 }
126
127 let psi = vec![vec![Complex::zero(); ny]; nx];
128 let solver = SchrodingerSolver2D::new(psi, potential, nx, ny, dx, dy, dt, 1.0, 1.0);
129
130 Self { solver, slit_mask }
131 }
132
133 pub fn init_plane_wave(&mut self, k: f64) {
135 let nx = self.solver.nx;
136 let ny = self.solver.ny;
137 let dx = self.solver.dx;
138 let sigma = nx as f64 * dx * 0.1;
139
140 for i in 0..nx {
141 for j in 0..ny {
142 let x = i as f64 * dx;
143 let x0 = nx as f64 * dx * 0.2;
144 let gauss = (-((x - x0) * (x - x0)) / (2.0 * sigma * sigma)).exp();
145 let phase = k * x;
146 self.solver.psi[i][j] = Complex::from_polar(gauss * 0.1, phase);
147 }
148 }
149 }
150
151 pub fn run(&mut self, steps: usize) -> Vec<Vec<f64>> {
153 for _ in 0..steps {
154 self.solver.step_2d();
155 }
156 let nx = self.solver.nx;
157 let ny = self.solver.ny;
158 let mut density = vec![vec![0.0; ny]; nx];
159 for i in 0..nx {
160 for j in 0..ny {
161 density[i][j] = self.solver.psi[i][j].norm_sq();
162 }
163 }
164 density
165 }
166}
167
168pub fn which_path_measurement(
171 setup: &DoubleSlitSetup,
172 screen_positions: &[f64],
173 detection_prob: f64,
174) -> Vec<f64> {
175 let coherent = intensity_pattern(setup, screen_positions);
176
177 let d = setup.slit_separation;
179 let a = setup.slit_width;
180 let lambda = setup.wavelength;
181 let l = setup.screen_distance;
182
183 let incoherent: Vec<f64> = screen_positions
184 .iter()
185 .map(|&y| {
186 let sin_theta = y / (y * y + l * l).sqrt();
187 let phase_a = PI * a * sin_theta / lambda;
188 let diffraction = if phase_a.abs() < 1e-12 {
189 1.0
190 } else {
191 (phase_a.sin() / phase_a).powi(2)
192 };
193 diffraction })
195 .collect();
196
197 coherent
199 .iter()
200 .zip(incoherent.iter())
201 .map(|(&c, &i)| (1.0 - detection_prob) * c + detection_prob * i)
202 .collect()
203}
204
205pub struct DoubleSlitRenderer {
207 pub width: usize,
208 pub height: usize,
209}
210
211impl DoubleSlitRenderer {
212 pub fn new(width: usize, height: usize) -> Self {
213 Self { width, height }
214 }
215
216 pub fn render(
218 &self,
219 setup: &DoubleSlitSetup,
220 particle_counts: Option<&[f64]>,
221 ) -> Vec<Vec<char>> {
222 let mut grid = vec![vec![' '; self.width]; self.height];
223 let wall_col = self.width / 3;
224
225 let slit_center1 = self.height / 2 - (self.height as f64 * setup.slit_separation / (4.0 * setup.screen_distance)) as usize;
227 let slit_center2 = self.height / 2 + (self.height as f64 * setup.slit_separation / (4.0 * setup.screen_distance)) as usize;
228 let slit_half = (self.height as f64 * setup.slit_width / (4.0 * setup.screen_distance)).max(1.0) as usize;
229
230 for row in 0..self.height {
231 let is_slit1 = row >= slit_center1.saturating_sub(slit_half)
232 && row <= (slit_center1 + slit_half).min(self.height - 1);
233 let is_slit2 = row >= slit_center2.saturating_sub(slit_half)
234 && row <= (slit_center2 + slit_half).min(self.height - 1);
235 if is_slit1 || is_slit2 {
236 grid[row][wall_col] = ' ';
237 } else {
238 grid[row][wall_col] = '#';
239 }
240 }
241
242 if let Some(counts) = particle_counts {
244 let screen_col = self.width - 2;
245 let max_count = counts.iter().cloned().fold(0.0_f64, f64::max).max(1e-10);
246 for row in 0..self.height {
247 let idx = (row * counts.len()) / self.height;
248 let idx = idx.min(counts.len() - 1);
249 let intensity = counts[idx] / max_count;
250 let n_dots = (intensity * (self.width / 3) as f64) as usize;
251 for c in 0..n_dots.min(self.width - screen_col) {
252 grid[row][screen_col - c] = if intensity > 0.7 { '@' } else if intensity > 0.3 { '*' } else { '.' };
253 }
254 }
255 }
256
257 grid
258 }
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264
265 #[test]
266 fn test_fringe_spacing() {
267 let setup = DoubleSlitSetup::new(0.01, 0.1, 0.001, 1.0);
269 let expected_spacing = setup.wavelength * setup.screen_distance / setup.slit_separation;
270
271 let n = 1000;
273 let y_max = 0.05;
274 let positions: Vec<f64> = (0..n).map(|i| -y_max + 2.0 * y_max * i as f64 / n as f64).collect();
275 let pattern = intensity_pattern(&setup, &positions);
276
277 let mut peaks = Vec::new();
278 for i in 1..n - 1 {
279 if pattern[i] > pattern[i - 1] && pattern[i] > pattern[i + 1] && pattern[i] > 0.5 {
280 peaks.push(positions[i]);
281 }
282 }
283
284 if peaks.len() >= 2 {
285 let measured_spacing = (peaks[1] - peaks[0]).abs();
286 assert!(
287 (measured_spacing - expected_spacing).abs() < expected_spacing * 0.3,
288 "Expected spacing {}, got {}",
289 expected_spacing,
290 measured_spacing
291 );
292 }
293 }
294
295 #[test]
296 fn test_single_slit_envelope() {
297 let pattern = single_slit_pattern(0.1, 0.001, &[0.0, 0.01, 0.02]);
298 assert!((pattern[0] - 1.0).abs() < 1e-10);
300 assert!(pattern[1] <= pattern[0] + 1e-10);
302 }
303
304 #[test]
305 fn test_n_slit_pattern() {
306 let angles = vec![0.0, 0.01, 0.02, 0.05];
308 let single = single_slit_pattern(0.1, 0.001, &angles);
309 let n1 = n_slit_pattern(1, 0.1, 0.5, 0.001, &angles);
310 for (a, b) in single.iter().zip(n1.iter()) {
311 assert!((a - b).abs() < 1e-6, "single: {}, n=1: {}", a, b);
312 }
313 }
314
315 #[test]
316 fn test_center_maximum() {
317 let setup = DoubleSlitSetup::new(0.01, 0.1, 0.001, 1.0);
318 let pattern = intensity_pattern(&setup, &[0.0]);
319 assert!((pattern[0] - 1.0).abs() < 1e-10, "Center should be max");
320 }
321
322 #[test]
323 fn test_which_path_kills_fringes() {
324 let setup = DoubleSlitSetup::new(0.01, 0.1, 0.001, 1.0);
325 let positions: Vec<f64> = (0..100).map(|i| -0.05 + 0.001 * i as f64).collect();
326
327 let coherent = intensity_pattern(&setup, &positions);
328 let detected = which_path_measurement(&setup, &positions, 1.0);
329
330 let coherent_var: f64 = coherent.iter().map(|&x| (x - 0.5).powi(2)).sum::<f64>();
332 let detected_var: f64 = detected.iter().map(|&x| (x - 0.5).powi(2)).sum::<f64>();
333 assert!(
335 coherent_var > detected_var * 0.5,
336 "Coherent var {} should be >= detected var {}",
337 coherent_var,
338 detected_var
339 );
340 }
341
342 #[test]
343 fn test_simulation_creation() {
344 let mut sim = DoubleSlitSimulation::new(
345 32, 32, 0.5, 0.5, 0.001, 10,
346 (12, 14), (17, 19), 1000.0,
347 );
348 sim.init_plane_wave(5.0);
349 let density = sim.run(5);
350 assert_eq!(density.len(), 32);
351 assert_eq!(density[0].len(), 32);
352 }
353
354 #[test]
355 fn test_renderer() {
356 let setup = DoubleSlitSetup::new(0.01, 0.1, 0.001, 1.0);
357 let renderer = DoubleSlitRenderer::new(40, 20);
358 let grid = renderer.render(&setup, None);
359 assert_eq!(grid.len(), 20);
360 assert_eq!(grid[0].len(), 40);
361 let wall_count: usize = grid.iter().flat_map(|r| r.iter()).filter(|&&c| c == '#').count();
363 assert!(wall_count > 0);
364 }
365
366 #[test]
367 fn test_n_slit_peaks() {
368 let n = 4;
370 let angles: Vec<f64> = (0..1000).map(|i| -0.1 + 0.0002 * i as f64).collect();
371 let pattern = n_slit_pattern(n, 0.01, 0.1, 0.001, &angles);
372 let center_idx = 500;
374 assert!((pattern[center_idx] - 1.0).abs() < 0.1);
375 }
376}