use core::f64::consts::PI;
use metrology::{C, HBAR, MU_0};
pub(crate) fn dot(a: [f64; 3], b: [f64; 3]) -> f64 {
a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
}
pub(crate) fn cross(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
[a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]]
}
pub fn toroidal_dipole(segs: &[([f64; 3], [f64; 3])]) -> [f64; 3] {
let mut t = [0.0; 3];
for &(r, idl) in segs {
let (rj, rr) = (dot(r, idl), dot(r, r));
for k in 0..3 {
t[k] += 0.1 * (rj * r[k] - 2.0 * rr * idl[k]);
}
}
t
}
pub fn toroidal_solenoid_moment(n: f64, i: f64, major: f64, minor: f64) -> f64 {
n * i * PI * major * minor * minor / 2.0
}
pub fn effective_dipole(p: [f64; 2], t: [f64; 2], k: f64) -> [f64; 2] {
[p[0] - k / C * t[1], p[1] + k / C * t[0]]
}
pub fn toroidal_radiated_power(t: f64, omega: f64) -> f64 {
MU_0 * omega.powi(6) * t * t / (12.0 * PI * C.powi(5))
}
pub fn mie_a1_zero_diameter(n: f64, wavelength: f64) -> f64 {
let psi = |z: f64| z.sin() / z - z.cos();
let dpsi = |z: f64| z.cos() / z - z.sin() / (z * z) + z.sin();
let g = |x: f64| n * psi(n * x) * dpsi(x) - psi(x) * dpsi(n * x);
let step = 0.01 / n.max(1.0);
let mut lo = step;
while g(lo) * g(lo + step) > 0.0 {
lo += step;
if lo > 100.0 {
return f64::NAN;
}
}
let mut hi = lo + step;
for _ in 0..60 {
let mid = 0.5 * (lo + hi);
if g(lo) * g(mid) <= 0.0 {
hi = mid;
} else {
lo = mid;
}
}
0.5 * (lo + hi) * wavelength / PI
}
pub fn hypertoroidal_moment(centres: &[[f64; 3]], moments: &[[f64; 3]]) -> [f64; 3] {
let mut h = [0.0; 3];
for (&r, &v) in centres.iter().zip(moments) {
let c = cross(r, v);
for k in 0..3 {
h[k] += 0.5 * c[k];
}
}
h
}
pub fn aharonov_bohm_phase(charge: f64, flux: f64) -> f64 {
charge * flux / HBAR
}
pub fn loop_emf(dflux_dt: f64) -> f64 {
-dflux_dt
}
pub fn supertoroidal_generating_fn(r: f64, z: f64, t: f64, q1: f64, q2: f64, alpha: f64) -> [f64; 2] {
let (tau, sigma) = (z - C * t, z + C * t);
let a2 = q1 * q1 + tau * tau;
let w = [r * r * q1 / a2 + q2, -r * r * tau / a2 - sigma];
let mag = 1.0 / (a2.sqrt() * w[0].hypot(w[1]).powf(alpha));
let phase = -(tau.atan2(q1) + alpha * w[1].atan2(w[0]));
[mag * phase.cos(), mag * phase.sin()]
}
#[cfg(test)]
mod tests {
use super::*;
use metrology::{DIRAC_FLUX, E_CHARGE, FLUX_QUANTUM};
fn close(a: f64, b: f64, rel: f64) -> bool {
(a / b - 1.0).abs() < rel
}
fn solenoid(turns: usize, i: f64, big_r: f64, a: f64, m: usize) -> Vec<([f64; 3], [f64; 3])> {
let mut segs = Vec::new();
for j in 0..turns {
let ph = 2.0 * PI * (j as f64 + 0.5) / turns as f64;
let pt = |k: usize| {
let th = 2.0 * PI * k as f64 / m as f64;
let rho = big_r + a * th.cos();
[rho * ph.cos(), rho * ph.sin(), a * th.sin()]
};
for k in 0..m {
let (p, q) = (pt(k), pt(k + 1));
segs.push(([0.5 * (p[0] + q[0]), 0.5 * (p[1] + q[1]), 0.5 * (p[2] + q[2])], [i * (q[0] - p[0]), i * (q[1] - p[1]), i * (q[2] - p[2])]));
}
}
segs
}
#[test]
fn toroidal_solenoid_moment_matches_the_segment_sum() {
for a in [0.05, 0.5] {
let t = toroidal_dipole(&solenoid(24, 1.0, 1.0, a, 200));
assert!(close(t[2].abs(), toroidal_solenoid_moment(24.0, 1.0, 1.0, a), 1e-3), "a = {a}");
assert!(t[0].abs() < 1e-12 && t[1].abs() < 1e-12);
}
let segs = solenoid(24, 1.0, 1.0, 0.3, 200);
let t = toroidal_dipole(&segs)[2];
let half: f64 = segs.iter().map(|&(r, idl)| 0.5 * dot(r, idl) * r[2]).sum();
let planes: f64 = segs.iter().map(|&(r, idl)| 0.25 * cross(r, cross(r, idl))[2]).sum();
assert!(close(half, t, 1e-12) && close(planes, 1.5 * t, 1e-12));
}
#[test]
fn ring_identity_and_hypertoroids() {
let (turns, m) = (24, 200);
let segs = solenoid(turns, 1.0, 1.0, 0.3, m);
let mut centres = Vec::new();
let mut moments = Vec::new();
for (j, turn) in segs.chunks(m).enumerate() {
let ph = 2.0 * PI * (j as f64 + 0.5) / turns as f64;
let c = [ph.cos(), ph.sin(), 0.0];
let mut mj = [0.0; 3];
for &(r, idl) in turn {
let x = cross([r[0] - c[0], r[1] - c[1], r[2] - c[2]], idl);
(0..3).for_each(|k| mj[k] += 0.5 * x[k]);
}
centres.push(c);
moments.push(mj);
}
assert!(close(hypertoroidal_moment(¢res, &moments)[2], toroidal_dipole(&segs)[2], 1e-12));
let ring: Vec<_> = (0..8).map(|j| f64::from(j) * PI / 4.0).collect();
let c: Vec<_> = ring.iter().map(|p| [2.0 * p.cos(), 2.0 * p.sin(), 0.0]).collect();
let t: Vec<_> = ring.iter().map(|p| [-3.0 * p.sin(), 3.0 * p.cos(), 0.0]).collect();
let h = hypertoroidal_moment(&c, &t);
assert!(close(h[2], 0.5 * 8.0 * 2.0 * 3.0, 1e-12) && h[0].abs() < 1e-12 && h[1].abs() < 1e-12);
}
fn far_field_power(segs: &[([f64; 3], [f64; 3])], k: f64, p_z: [f64; 2]) -> f64 {
let (nt, np) = (60, 24);
let w = k * C;
let mut total = 0.0;
for it in 0..nt {
let th = PI * (it as f64 + 0.5) / nt as f64;
for ip in 0..np {
let ph = 2.0 * PI * (ip as f64 + 0.5) / np as f64;
let n = [th.sin() * ph.cos(), th.sin() * ph.sin(), th.cos()];
let (mut re, mut im) = ([0.0; 3], [0.0; 3]);
for &(r, idl) in segs {
let a = -k * dot(n, r);
(0..3).for_each(|q| {
re[q] += idl[q] * a.cos();
im[q] += idl[q] * a.sin();
});
}
re[2] += w * p_z[1];
im[2] -= w * p_z[0];
let sq = dot(cross(n, re), cross(n, re)) + dot(cross(n, im), cross(n, im));
total += sq * th.sin() * (PI / nt as f64) * (2.0 * PI / np as f64);
}
}
MU_0 * C * k * k / (32.0 * PI * PI) * total
}
#[test]
fn radiation_and_the_anapole() {
let segs = solenoid(24, 1.0, 1.0, 0.3, 60);
let t = toroidal_dipole(&segs)[2];
let k = 0.05;
let p1 = far_field_power(&segs, k, [0.0; 2]);
assert!(close(p1, toroidal_radiated_power(t, k * C), 1e-2));
assert!(close(toroidal_radiated_power(t, 2.0 * k * C) / toroidal_radiated_power(t, k * C), 64.0, 1e-12));
assert!(close(far_field_power(&segs, 2.0 * k, [0.0; 2]) / p1, 64.0, 1e-2));
let ana = [0.0, -k / C * t];
assert_eq!(effective_dipole(ana, [t, 0.0], k), [0.0, 0.0]);
assert!(far_field_power(&segs, k, ana) / p1 < 1e-3);
let wrong = [0.0, k / C * t];
let pe = effective_dipole(wrong, [t, 0.0], k);
let dipole_power = MU_0 * (k * C).powi(4) * (pe[0] * pe[0] + pe[1] * pe[1]) / (12.0 * PI * C);
assert!(close(dipole_power / p1, 4.0, 1e-2));
assert!(close(far_field_power(&segs, k, wrong) / p1, 4.0, 1e-2));
}
#[test]
fn silicon_sphere_anapole() {
let d = mie_a1_zero_diameter(4.0, 550.0);
assert!((d - 204.0).abs() < 3.0 && (d - 204.028).abs() < 1e-3, "{d}");
}
#[test]
fn aharonov_bohm_and_faraday() {
assert!(close(aharonov_bohm_phase(E_CHARGE, FLUX_QUANTUM), PI, 1e-12));
assert!(close(aharonov_bohm_phase(E_CHARGE, DIRAC_FLUX), 2.0 * PI, 1e-12));
assert_eq!(loop_emf(2.5), -2.5);
}
#[test]
fn supertoroidal_pulses_solve_the_wave_equation() {
let (q1, q2) = (1.0, 20.0);
for (r, z, t) in [(0.3, 0.2, 1e-9), (2.0, -1.5, -3e-9), (0.0, 0.0, 0.0)] {
let f = supertoroidal_generating_fn(r, z, t, q1, q2, 1.0);
let (tau, sigma) = (z - C * t, z + C * t);
let d = [r * r + q1 * q2 + tau * sigma, q2 * tau - q1 * sigma];
let dd = d[0] * d[0] + d[1] * d[1];
assert!((f[0] - d[0] / dd).hypot(f[1] + d[1] / dd) < 1e-12 / dd.sqrt());
}
for alpha in [1.0, 2.5, 5.0] {
for (r, z, t) in [(0.3, 0.2, 1e-9), (2.0, -1.5, -3e-9), (1.0, 4.0, 2e-8)] {
let f = |r: f64, z: f64, t: f64| supertoroidal_generating_fn(r, z, t, q1, q2, alpha);
let h = 1e-3;
let ht = h / C;
for q in 0..2 {
let f0 = f(r, z, t)[q];
let frr = (f(r + h, z, t)[q] - 2.0 * f0 + f(r - h, z, t)[q]) / (h * h);
let fr = (f(r + h, z, t)[q] - f(r - h, z, t)[q]) / (2.0 * h);
let fzz = (f(r, z + h, t)[q] - 2.0 * f0 + f(r, z - h, t)[q]) / (h * h);
let ftt = (f(r, z, t + ht)[q] - 2.0 * f0 + f(r, z, t - ht)[q]) / (ht * ht * C * C);
let box_f = frr + fr / r + fzz - ftt;
let scale = frr.abs() + (fr / r).abs() + fzz.abs() + ftt.abs();
assert!(box_f.abs() < 1e-5 * scale, "α = {alpha}, ({r}, {z}, {t}): {box_f} vs {scale}");
}
}
}
}
}