use metrology::{ALPHA, AMU, C, G, HBAR};
pub fn eta(a_a: f64, a_b: f64) -> f64 {
2.0 * (a_a - a_b) / (a_a + a_b)
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Test {
pub name: &'static str,
pub eta: f64,
pub stat: f64,
pub syst: f64,
pub source: &'static str,
}
impl Test {
pub fn combine(&self) -> f64 {
self.stat.hypot(self.syst)
}
}
pub const TESTS: [Test; 4] = [
Test { name: "MICROSCOPE Ti–Pt", eta: -1.5e-15, stat: 2.3e-15, syst: 1.5e-15, source: "https://arxiv.org/abs/2209.15487" },
Test { name: "MICROSCOPE Pt–Pt (control)", eta: 0.0, stat: 1.1e-15, syst: 2.3e-15, source: "https://arxiv.org/abs/2209.15487" },
Test { name: "Eöt-Wash Be–Ti, towards the Earth", eta: 0.3e-13, stat: 1.8e-13, syst: 0.0, source: "https://arxiv.org/abs/0712.0607" },
Test { name: "Stanford ⁸⁵Rb–⁸⁷Rb", eta: 1.6e-12, stat: 1.8e-12, syst: 3.4e-12, source: "https://arxiv.org/abs/2005.11624" },
];
pub const MICROSCOPE_LIMITS: (f64, f64) = (4.5e-15, 6.5e-15);
pub const ALPHA_OVER_G_U2: f64 = ALPHA * HBAR * C / (G * AMU * AMU);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Mediator {
Vector,
Scalar,
}
pub fn eta_from_coupling(mediator: Mediator, epsilon: f64, earth: f64, difference: f64) -> f64 {
let sign = match mediator {
Mediator::Vector => -1.0,
Mediator::Scalar => 1.0,
};
sign * ALPHA_OVER_G_U2 * epsilon * epsilon * earth * difference
}
pub fn coupling_bound(mediator: Mediator, earth: f64, difference: f64, (positive, negative): (f64, f64)) -> f64 {
let per_epsilon2 = eta_from_coupling(mediator, 1.0, earth, difference);
let limit = if per_epsilon2 > 0.0 { positive } else { negative };
(limit / per_epsilon2.abs()).sqrt() * (4.0 * core::f64::consts::PI * ALPHA).sqrt()
}
pub fn sphere_form_factor(x: f64) -> f64 {
if x.abs() < 1.0 {
let (mut term, mut sum) = (1.0, 0.0);
for k in 0..12 {
sum += 3.0 * term / f64::from(2 * k + 3);
term *= x * x / f64::from((2 * k + 2) * (2 * k + 3));
}
sum
} else {
3.0 * (x * x.cosh() - x.sinh()) / x.powi(3)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::modified::{boson_mass_ev, yukawa_factor};
#[test]
fn eotvos_parameters() {
let g = 7.95;
assert!(eta(3.0, 1.0) == 1.0 && eta(1.0, 2.0) == -eta(2.0, 1.0) && eta(g, g) == 0.0);
assert!((eta(g + 1e-6, g) / (1e-6 / g) - 1.0).abs() < 1e-7);
let [ti_pt, pt_pt, eot_wash, stanford] = TESTS;
assert!((ti_pt.combine() / 2.745_906e-15 - 1.0).abs() < 1e-6);
assert!((ti_pt.combine() * metrology::GM_EARTH / (6371e3 + 710e3f64).powi(2) / 2.18e-14 - 1.0).abs() < 0.01);
assert!(ti_pt.eta.abs() < ti_pt.combine() && pt_pt.eta.abs() < pt_pt.combine());
assert!(eot_wash.eta.abs() < eot_wash.combine() && (stanford.combine() / 3.847e-12 - 1.0).abs() < 1e-3);
}
#[test]
fn fayets_coupling_bounds() {
assert!((ALPHA_OVER_G_U2 / 1.2536e36 - 1.0).abs() < 5e-5);
let (db, dl): (f64, f64) = (0.00079, 0.05704);
let dbl = db - dl;
assert!((dbl + 0.05625).abs() < 1e-15);
let earth = |coefficient: f64, d: f64| coefficient / (ALPHA_OVER_G_U2 * d.abs());
let (eb, ebl, el) = (earth(1.00e33, db), earth(3.623e34, dbl), earth(3.482e34, dl));
assert!((ebl + el - 1.0008).abs() < 1e-4 && (eb - (ebl + el)).abs() < 0.01);
assert!((eta_from_coupling(Mediator::Vector, 1.0, ebl, dbl) / 3.623e34 - 1.0).abs() < 1e-12);
for (mediator, q, d, want) in [
(Mediator::Vector, ebl, dbl, 1.1e-25), (Mediator::Vector, eb, db, 7.7e-25), (Mediator::Vector, el, dl, 1.3e-25),
(Mediator::Scalar, ebl, dbl, 1.3e-25), (Mediator::Scalar, eb, db, 6.4e-25), (Mediator::Scalar, el, dl, 1.1e-25),
] {
let got = coupling_bound(mediator, q, d, MICROSCOPE_LIMITS);
assert!((got / want - 1.0).abs() < 0.05, "{mediator:?}: {got:e} against {want:e}");
}
let vector = coupling_bound(Mediator::Vector, ebl, dbl, MICROSCOPE_LIMITS);
let scalar = coupling_bound(Mediator::Scalar, ebl, dbl, MICROSCOPE_LIMITS);
assert!((vector / 1.067e-25 - 1.0).abs() < 1e-3 && (scalar / 1.283e-25 - 1.0).abs() < 1e-3);
assert!((scalar / vector - (6.5f64 / 4.5).sqrt()).abs() < 1e-12);
}
#[test]
fn the_form_factor_of_a_sphere() {
for (x, want) in [(0.5, 1.0252), (1.0, 1.1036), (2.0, 1.4616), (3.0, 2.2428), (5.0, 7.1243), (10.0, 297.36), (20.0, 1.728e6), (30.0, 1.722e10), (50.0, 3.049e18), (100.0, 3.992e39), (200.0, 2.696e82)] {
assert!((sphere_form_factor(x) / want - 1.0).abs() < 2.5e-4, "x = {x}: {}", sphere_form_factor(x));
}
for x in [0.0, 1e-8, 1e-3, 0.1] {
let x2: f64 = x * x;
let want = 1.0 + x2 / 10.0 + x2 * x2 / 280.0 + x2 * x2 * x2 / 15120.0;
assert!((sphere_form_factor(x) - want).abs() < 1e-8 * x2 * x2 * x2 + 2e-16, "{x}");
}
let (below, above) = (sphere_form_factor(1.0 - 1e-15), sphere_form_factor(1.0));
assert!((below - above).abs() < 4e-15);
assert!((sphere_form_factor(-2.0) - sphere_form_factor(2.0)).abs() < 1e-15);
}
#[test]
fn a_layered_earth_at_satellite_height() {
let shells = [(0.5, 1.0), (0.1333, 0.9953), (0.1715, 0.8948), (0.1929, 0.5462), (0.0023, 0.1916)];
let earth = |x: f64| shells.iter().map(|&(y, s)| y * sphere_form_factor(s * x)).sum::<f64>();
assert!((earth(3.0) / 2.0021 - 1.0).abs() < 1e-5 && (earth(0.0) - 1.0).abs() < 1e-15);
let (radius, r) = (6371e3, 6371e3 + 710e3);
let hbar_c = HBAR * C / metrology::EV; for (m, f, g) in [(1e-14, 0.957, 1.022), (2e-14, 0.867, 1.074), (5e-14, 0.574, 1.320), (1e-13, 0.281, 1.886), (2e-13, 0.0927, 3.28)] {
let lambda = hbar_c / m;
assert!((boson_mass_ev(lambda) / m - 1.0).abs() < 1e-12);
let big_f = (yukawa_factor(r, 1.0, lambda) - 1.0) * earth(radius / lambda);
assert!((big_f / f - 1.0).abs() < 2e-3 && (big_f.powf(-0.5) / g - 1.0).abs() < 2e-3, "m = {m:e}: F = {big_f}");
}
}
}