use core::f64::consts::PI;
use metrology::C;
pub const K_SR87: f64 = 2.0 * PI / 698.445_7e-9;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct AtomGwDetector {
pub baseline: f64,
pub t: f64,
pub lmt: f64,
pub phase_noise: f64,
pub k: f64,
pub diamonds: f64,
}
impl AtomGwDetector {
pub fn phase_response(&self, f: f64) -> f64 {
let w = 2.0 * PI * f;
let (n, q, t, l) = (self.lmt, self.diamonds, self.t, self.baseline);
let ratio = if (w * t / 2.0).cos().abs() < 1e-9 {
-2.0 * q * (w * q * t).cos() / (w * t / 2.0).sin() } else {
(w * q * t).sin() / (w * t / 2.0).cos()
};
let x = w * n * l / (2.0 * C);
let sinc = if x.abs() < 1e-12 { 1.0 } else { x.sin() / x };
(n * self.k * l * ratio * sinc * (w * t / 2.0 - w * (n - 1.0) * l / (2.0 * C)).sin()).abs()
}
pub fn strain_asd(&self, f: f64) -> f64 {
self.phase_noise / self.phase_response(f)
}
pub fn peak_strain(&self) -> f64 {
self.phase_noise / (2.0 * self.lmt * self.k * self.baseline)
}
}
pub fn tidal_acceleration(h: f64, f: f64, baseline: f64) -> f64 {
0.5 * (2.0 * PI * f).powi(2) * h * baseline
}
pub fn gradiometer_strain(gradient_asd: f64, f: f64) -> f64 {
2.0 * gradient_asd / (2.0 * PI * f).powi(2)
}
pub fn pair_strain(range_asd: f64, separation: f64) -> f64 {
2.0 * range_asd / separation
}
pub fn clock_strain(nu: f64, linewidth: f64, tau: f64, atoms: f64) -> f64 {
linewidth.sqrt() / (nu * (2.0 * PI * tau * atoms).sqrt())
}
#[cfg(test)]
mod tests {
use super::*;
fn design(baseline: f64, t: f64, lmt: f64, phase_noise: f64) -> AtomGwDetector {
AtomGwDetector { baseline, t, lmt, phase_noise, k: K_SR87, diamonds: 1.0 }
}
#[test]
fn published_design_strains() {
assert!((design(2000.0, 5.0, 40_000.0, 3e-6).peak_strain() / 2.08e-21 - 1.0).abs() < 0.01);
assert!((design(50.0, 1.4, 5494.0, 1e-5).peak_strain() / 2.02e-18 - 1.0).abs() < 0.01);
assert!((design(100.0, 1.4, 1000.0, 1e-4).peak_strain() / 5.55e-17 - 1.0).abs() < 0.01);
assert!((design(10.0, 1.4, 100.0, 1e-3).peak_strain() / 5.558e-14 - 1.0).abs() < 1e-3);
}
#[test]
fn response_limits() {
let d = AtomGwDetector { baseline: 1e-3, t: 1.0, lmt: 10.0, phase_noise: 1e-5, k: K_SR87, diamonds: 1.0 };
for f in [0.05, 0.2, 0.37] {
let expect = 2.0 * d.lmt * d.k * d.baseline * (PI * f * d.t).sin().powi(2);
assert!((d.phase_response(f) / expect - 1.0).abs() < 1e-6, "{f}");
}
let q3 = AtomGwDetector { diamonds: 3.0, ..d };
assert!((q3.phase_response(0.5) / (2.0 * 3.0 * d.lmt * d.k * d.baseline) - 1.0).abs() < 1e-6);
assert!((d.strain_asd(0.5) / d.peak_strain() - 1.0).abs() < 1e-6);
}
#[test]
fn gradiometers_clocks_and_pairs() {
assert!((gradiometer_strain(1e-11, 0.01) / 5.07e-9 - 1.0).abs() < 2e-3);
assert!((gradiometer_strain(1e-11, 0.1) / 5.07e-11 - 1.0).abs() < 2e-3);
assert!((clock_strain(430e12, 1e-3, 1.0, 7e6) / 1.109e-20 - 1.0).abs() < 2e-3);
assert!((pair_strain(1e-9, 2.2e5) / 9.09e-15 - 1.0).abs() < 1e-3);
let h = gradiometer_strain(1e-11, 0.1);
assert!((tidal_acceleration(h, 0.1, 1.0) / 1e-11 - 1.0).abs() < 1e-12);
}
}