pub const CODATA_2022_G: [(&str, f64, f64); 16] = [
("NIST-82", 6.672_48, 0.000_43),
("TR&D-96", 6.672_9, 0.000_5),
("LANL-97", 6.673_98, 0.000_70),
("UWash-00", 6.674_255, 0.000_092),
("BIPM-01", 6.675_59, 0.000_27),
("UWup-02", 6.674_22, 0.000_98),
("MSL-03", 6.673_87, 0.000_27),
("HUST-05", 6.672_22, 0.000_87),
("UZur-06", 6.674_25, 0.000_12),
("HUST-09", 6.673_49, 0.000_18),
("BIPM-14", 6.675_54, 0.000_16),
("LENS-14", 6.671_91, 0.000_99),
("UCI-14", 6.674_35, 0.000_13),
("HUSTT-18", 6.674_184, 0.000_078),
("HUSTA-18", 6.674_484, 0.000_077),
("JILA-18", 6.672_60, 0.000_25),
];
pub const CODATA_2022_G_CORRELATIONS: [(&str, &str, f64); 3] =
[("NIST-82", "LANL-97", 0.351), ("HUST-05", "HUST-09", 0.134), ("HUST-09", "HUSTT-18", 0.068)];
pub const MAJORANA_ABSORPTION: f64 = 6.7e-13;
pub const LLR_ABSORPTION_BOUND: f64 = 1e-22;
pub fn gls_mean(values: &[f64], cov: &[Vec<f64>]) -> Option<(f64, f64, f64)> {
let n = values.len();
if n == 0 || cov.len() != n || cov.iter().any(|row| row.len() != n) {
return None;
}
let mut l = vec![vec![0.0; n]; n];
for i in 0..n {
for j in 0..=i {
let s = cov[i][j] - (0..j).map(|k| l[i][k] * l[j][k]).sum::<f64>();
if i == j {
if s <= 0.0 {
return None;
}
l[i][i] = s.sqrt();
} else {
l[i][j] = s / l[j][j];
}
}
}
let solve = |b: &[f64]| {
let mut y = vec![0.0; n];
for i in 0..n {
y[i] = (b[i] - (0..i).map(|k| l[i][k] * y[k]).sum::<f64>()) / l[i][i];
}
for i in (0..n).rev() {
y[i] = (y[i] - (i + 1..n).map(|k| l[k][i] * y[k]).sum::<f64>()) / l[i][i];
}
y
};
let w = solve(&vec![1.0; n]);
let total: f64 = w.iter().sum();
let mean = w.iter().zip(values).map(|(w, y)| w * y).sum::<f64>() / total;
let r: Vec<f64> = values.iter().map(|y| y - mean).collect();
let chi2 = r.iter().zip(solve(&r)).map(|(a, b)| a * b).sum();
Some((mean, total.sqrt().recip(), chi2))
}
pub fn tension(a: f64, u_a: f64, b: f64, u_b: f64) -> f64 {
(a - b) / (u_a * u_a + u_b * u_b).sqrt()
}
pub fn birge_ratio(chi2: f64, dof: usize) -> f64 {
(chi2 / dof as f64).sqrt()
}
pub fn flyby_anderson(v_inf: f64, dec_in: f64, dec_out: f64) -> f64 {
const K: f64 = 2.0 * metrology::EARTH_ROTATION * 6_371e3 / metrology::C;
K * v_inf * (dec_in.cos() - dec_out.cos())
}
pub fn column_density(radius: f64, density: f64) -> f64 {
2.0 * radius * density
}
pub fn shielding_loss(h: f64, column: f64) -> f64 {
-(-h * column).exp_m1()
}
pub fn pioneer_thermal(a0: f64, tau: f64, t: f64) -> f64 {
a0 * (-t / tau).exp2()
}
#[cfg(test)]
mod tests {
use super::*;
fn close(a: f64, b: f64, rel: f64) -> bool {
(a / b - 1.0).abs() < rel
}
fn codata() -> (Vec<f64>, Vec<Vec<f64>>) {
let n = CODATA_2022_G.len();
let mut cov = vec![vec![0.0; n]; n];
for (i, row) in CODATA_2022_G.iter().enumerate() {
cov[i][i] = row.2 * row.2;
}
let at = |id: &str| CODATA_2022_G.iter().position(|r| r.0 == id).unwrap();
for &(a, b, r) in &CODATA_2022_G_CORRELATIONS {
let (i, j) = (at(a), at(b));
cov[i][j] = r * CODATA_2022_G[i].2 * CODATA_2022_G[j].2;
cov[j][i] = cov[i][j];
}
(CODATA_2022_G.iter().map(|r| r.1).collect(), cov)
}
#[test]
fn codata_g() {
let (y, cov) = codata();
let (mean, u, chi2) = gls_mean(&y, &cov).unwrap();
assert!((mean - 6.674_300_4).abs() < 1e-7 && close(u, 3.789_13e-5, 1e-5) && close(chi2, 195.679, 1e-5));
assert!(close(birge_ratio(chi2, 15), 3.611_82, 1e-5));
assert!(close(3.9 * u / mean * 1e6, 22.14, 1e-3) && close(chi2 / 3.9f64.powi(2), 12.865, 1e-3));
for (id, z) in [("BIPM-14", 7.7475), ("JILA-18", -6.8016), ("BIPM-01", 4.7763)] {
let row = CODATA_2022_G.iter().find(|r| r.0 == id).unwrap();
assert!(close((row.1 - mean) / row.2, z, 1e-4), "{id}");
}
let (lo, hi) = y.iter().fold((f64::MAX, f64::MIN), |(a, b), &v| (a.min(v), b.max(v)));
assert!(close((hi - lo) / mean * 1e6, 551.37, 1e-4));
assert!(gls_mean(&[1.0], &[vec![0.0]]).is_none() && gls_mean(&[1.0, 2.0], &[vec![1.0]]).is_none());
}
#[test]
fn flybys() {
let deg = f64::to_radians;
for (v, din, dout, mm) in [(8949.0, -12.52, -34.15, 4.12296), (8877.0, -34.26, -4.87, -4.67440), (6851.0, -20.76, -71.96, 13.2794),
(16010.0, -12.92, -4.99, -1.06817), (3863.0, -2.81, -34.29, 2.06651), (4056.0, 31.44, -31.92, 0.0553082)]
{
assert!(close(flyby_anderson(v, deg(din), deg(dout)) * 1e3, mm, 1e-5), "{v}");
}
}
#[test]
fn shielding_and_pioneer() {
let (earth, moon) = (column_density(6_371e3, 5514.0), column_density(1_737.4e3, 3344.0));
assert!(close(earth, 7.02594e10, 1e-5) && close(moon, 1.16197e10, 1e-5));
assert!(close(shielding_loss(MAJORANA_ABSORPTION, earth), 4.59830e-2, 1e-5));
assert!(close(shielding_loss(MAJORANA_ABSORPTION, moon), 7.755e-3, 1e-3));
assert!(close(shielding_loss(LLR_ABSORPTION_BOUND, earth), 7.02594e-12, 1e-5));
assert!(close(shielding_loss(LLR_ABSORPTION_BOUND, moon) * 5.9301e-3, 6.9e-15, 0.01));
assert!(close(pioneer_thermal(10.1e-10, 28.8, 20.0), 6.24127e-10, 1e-5));
}
#[test]
fn the_g_scatter_is_not_site_dependent() {
let at = |id: &str| *CODATA_2022_G.iter().find(|r| r.0 == id).expect("listed");
let (y, cov) = codata();
let (mean, _, _) = gls_mean(&y, &cov).unwrap();
let ppm = |x: f64| x / mean * 1e6;
let hust: Vec<(&str, f64, f64)> = ["HUST-05", "HUST-09", "HUSTT-18", "HUSTA-18"].iter().map(|id| at(id)).collect();
let (lo, hi) = hust.iter().fold((f64::MAX, f64::MIN), |(a, b), r| (a.min(r.1), b.max(r.1)));
assert!(close(ppm(hi - lo), 339.2, 1e-3), "{}", ppm(hi - lo));
let (t, a) = (at("HUSTT-18"), at("HUSTA-18"));
assert!(close(tension(a.1, a.2, t.1, t.2), 2.737, 1e-3));
assert!(close(ppm(a.1 - t.1), 44.95, 1e-3));
let (b1, b2) = (at("BIPM-01"), at("BIPM-14"));
assert!(tension(b1.1, b1.2, b2.1, b2.2).abs() < 0.2);
assert!(ppm(b1.1 - mean) > 150.0 && ppm(b2.1 - mean) > 150.0);
assert!(ppm(hi - lo) / 551.4 > 0.5);
assert!(close(tension(a.1, a.2, t.1, t.2), -tension(t.1, t.2, a.1, a.2), 1e-12));
assert!(tension(a.1, a.2, a.1, a.2) == 0.0);
}
}