use pantometry::em::{Boundary, Cavity, Medium, Wall};
use pantometry::optics::{single_slit_intensity, slit_zero};
use pantometry::prelude::*;
const PER: usize = 16;
const HALF_WIDTH: usize = 9;
struct Aperture {
re: Vec<f64>,
im: Vec<f64>,
dx: f64,
k0: f64,
axis: f64,
}
impl Aperture {
fn intensity(&self, sin_theta: f64) -> f64 {
let n = self.re.len();
let (mut tr, mut ti) = (0.0, 0.0);
for i in 0..n {
let x = (i as f64 - (n - 1) as f64 / 2.0) * self.dx;
let ph = -self.k0 * x * sin_theta;
let (cs, sn) = (ph.cos(), ph.sin());
tr += self.re[i] * cs - self.im[i] * sn;
ti += self.re[i] * sn + self.im[i] * cs;
}
(tr * tr + ti * ti) / self.axis.max(f64::MIN_POSITIVE)
}
fn pattern(&self, samples: usize) -> Vec<(f64, f64)> {
(0..samples)
.map(|s| {
let sin_theta = 0.5 * s as f64 / (samples - 1) as f64;
(sin_theta, self.intensity(sin_theta))
})
.collect()
}
}
fn aperture(width_wavelengths: f64) -> Aperture {
let wavelength = 500e-9;
let dx = wavelength / PER as f64;
let nx = 2 * HALF_WIDTH * PER;
let nz = 10 * PER;
let screen = 8 * PER;
let record = screen + PER; let half_slit = (0.5 * width_wavelengths * PER as f64).round() as usize;
let centre = nx / 2;
let mut c = Cavity::new("slit", (nx, 2, nz), Length::from_si(dx), Medium::vacuum());
c.set_boundary(Wall::XLow, Boundary::Magnetic);
c.set_boundary(Wall::XHigh, Boundary::Magnetic);
c.set_boundary(Wall::ZLow, Boundary::Open);
c.set_boundary(Wall::ZHigh, Boundary::Open);
c.obstruct(|i, _j, k| {
(k == screen || k == screen + 1) && (i + half_slit < centre || i > centre + half_slit)
});
let dt = Time::from_si(c.courant_limit().to_si() * 0.5);
let k0 = 2.0 * std::f64::consts::PI / wavelength;
let (start, spread) = (3.0 * wavelength, 1.6 * wavelength);
c.launch_along_z(dt, move |_x, _y, z| {
let u = (z - start) / spread;
(-u * u).exp() * (k0 * (z - start)).cos()
});
let mut bus = Exchange::new();
let steps = (1.3 * nz as f64 / 0.2887) as usize;
let f = 299_792_458.0 / wavelength;
let mut re = vec![0.0f64; nx + 1];
let mut im = vec![0.0f64; nx + 1];
for n in 0..steps {
let t = n as f64 * dt.to_si();
c.step(Time::from_si(t), dt, &mut bus).expect("stable");
let phase = -2.0 * std::f64::consts::PI * f * t;
let (cs, sn) = (phase.cos(), phase.sin());
for i in 0..=nx {
let e = c.electric_at(i, 0, record).y;
re[i] += e * cs;
im[i] += e * sn;
}
}
let axis = {
let (ar, ai): (f64, f64) = (re.iter().sum(), im.iter().sum());
ar * ar + ai * ai
};
Aperture {
re,
im,
dx,
k0,
axis,
}
}
#[test]
fn a_wide_slit_agrees_with_scalar_diffraction_and_a_narrow_one_does_not() {
let wavelength = Length::from_si(500e-9);
let mut errors = Vec::new();
for width in [1.0, 3.0, 6.0, 12.0] {
let a = Length::from_si(width * 500e-9);
let measured = aperture(width).pattern(121);
let limit = slit_zero(a, wavelength, 2).unwrap_or(0.5).min(0.5);
let mut worst: f64 = 0.0;
for (sin_theta, intensity) in measured.iter().filter(|(s, _)| *s <= limit) {
let closed = single_slit_intensity(a, wavelength, *sin_theta);
worst = worst.max((intensity - closed).abs());
}
println!(
" a = {width:>4.1} lambda: worst difference {:.4} of the axial intensity, out to \
sin theta = {limit:.3}",
worst
);
errors.push((width, worst));
}
assert!(
errors[0].1 > 0.2,
"at one wavelength the scalar formula should be badly wrong: {:.4}",
errors[0].1
);
assert!(
errors[3].1 < 0.06,
"at twelve wavelengths it should be close: {:.4}",
errors[3].1
);
for pair in errors.windows(2) {
assert!(
pair[1].1 < pair[0].1,
"widening the slit must help: {:.4} at {} lambda then {:.4} at {}",
pair[0].1,
pair[0].0,
pair[1].1,
pair[1].0
);
}
println!(
" so from one wavelength to twelve the disagreement fell {:.1}x",
errors[0].1 / errors[3].1
);
}
#[test]
fn the_first_dark_fringe_is_where_the_formula_says() {
let wavelength = Length::from_si(500e-9);
for width in [6.0, 12.0] {
let a = Length::from_si(width * 500e-9);
let measured = aperture(width).pattern(401);
let closed = slit_zero(a, wavelength, 1).expect("a wide slit has a first zero");
let (at, _) = measured
.iter()
.enumerate()
.filter(|(_, (s, _))| (*s - closed).abs() < 0.4 * closed)
.min_by(|a, b| a.1 .1.total_cmp(&b.1 .1))
.expect("there is a minimum near the predicted zero");
let (lo, mid, hi) = (measured[at - 1].1, measured[at].1, measured[at + 1].1);
let denom = lo - 2.0 * mid + hi;
let shift = if denom.abs() > 0.0 {
0.5 * (lo - hi) / denom
} else {
0.0
};
let step = measured[1].0 - measured[0].0;
let found = measured[at].0 + shift * step;
println!(
" a = {width:>4.1} lambda: first zero at sin theta = {found:.5} against \
lambda/a = {closed:.5} — off {:.2}%",
(found / closed - 1.0).abs() * 100.0
);
assert!(
(found / closed - 1.0).abs() < 0.04,
"the first zero is at lambda/a: {found:.5} against {closed:.5}"
);
}
}
#[test]
fn a_sub_wavelength_slit_radiates_instead_of_diffracting() {
let wavelength = Length::from_si(500e-9);
assert!(
slit_zero(Length::from_si(0.6 * 500e-9), wavelength, 1).is_none(),
"there is no angle at which a 0.6-wavelength slit is dark"
);
let narrow = aperture(0.75).intensity(0.5);
let wide = aperture(12.0).intensity(0.5);
println!(
" at sin theta = 0.5: a 0.75 lambda slit sends {narrow:.4} of its axial intensity, a 12 \
lambda one {wide:.6}"
);
assert!(
narrow > 0.1,
"a sub-wavelength slit is nearly omnidirectional: {narrow:.4}"
);
assert!(
narrow / wide.max(1e-12) > 20.0,
"which is orders more than a wide slit puts there: {:.1}x",
narrow / wide.max(1e-12)
);
}