use metrology::{C, EARTH_ROTATION, GM_EARTH};
const C2: f64 = C * C;
pub fn frequency_shift(dw: f64) -> f64 {
-dw / C2
}
pub fn potential_difference(df: f64) -> f64 {
-C2 * df
}
pub fn height_difference(df: f64, g: f64) -> f64 {
C2 * df / g
}
pub fn rate_tcg(u: f64, v: f64) -> f64 {
-(u + 0.5 * v * v) / C2
}
pub fn proper_time_offset(samples: &[(f64, f64, f64)]) -> f64 {
samples.windows(2).map(|w| 0.5 * (rate_tcg(w[0].1, w[0].2) + rate_tcg(w[1].1, w[1].2)) * (w[1].0 - w[0].0)).sum()
}
pub const GPS_CLOCK_RATE_OFFSET: f64 = -4.4647e-10;
pub const GNSS_ECCENTRICITY_F: f64 = -4.442_807_633e-10;
pub fn eccentricity_correction(a: f64, e: f64, big_e: f64) -> f64 {
GNSS_ECCENTRICITY_F * e * a.sqrt() * big_e.sin()
}
pub fn sagnac(emitter: [f64; 3], receiver: [f64; 3]) -> f64 {
EARTH_ROTATION / C2 * (emitter[0] * receiver[1] - emitter[1] * receiver[0])
}
pub fn shapiro_delay(gm: f64, r1: f64, r2: f64, r12: f64) -> f64 {
2.0 * gm / (C2 * C) * ((r1 + r2 + r12) / (r1 + r2 - r12)).ln()
}
pub fn one_way_transfer(emitter: [f64; 3], receiver: [f64; 3]) -> f64 {
let norm = |v: [f64; 3]| (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt();
let d = norm([receiver[0] - emitter[0], receiver[1] - emitter[1], receiver[2] - emitter[2]]);
d / C + shapiro_delay(GM_EARTH, norm(emitter), norm(receiver), d) + sagnac(emitter, receiver)
}
#[cfg(test)]
mod tests {
use super::*;
use geopotential::normal::GRS80;
use metrology::{AU, G0, GM_SUN, GRS80_GM, L_G, W0_IAU, W0_IHRS};
fn close(a: f64, b: f64, tol: f64) -> bool {
(a - b).abs() <= tol
}
#[test]
fn chronometric_levelling() {
assert!(close(frequency_shift(-G0 * 0.01), 1.0912e-18, 1e-21) && close(height_difference(1e-18, G0), 0.009_17, 1e-5));
assert!(close(frequency_shift(potential_difference(3.2e-17)), 3.2e-17, 1e-30));
assert_eq!(frequency_shift(W0_IAU - W0_IAU), 0.0);
assert!(close(frequency_shift(9.8), -1.09e-16, 1e-18) && close(rate_tcg(W0_IAU, 0.0), -L_G, 1e-19));
assert!(close(height_difference(frequency_shift(W0_IHRS - W0_IAU), G0), 0.265, 1e-3));
}
#[test]
fn the_tokyo_skytree() {
let (dh, g, nu) = (452.596, 9.797_248, 429_228_004_229_873.0);
assert!(close(frequency_shift(-g * dh) * nu, 21.18, 0.01));
assert!(close(height_difference(frequency_shift(-g * dh), g), dh, 1e-9));
let gamma = GRS80.gamma_at(35.71f64.to_radians(), 230.0);
assert!(close(gamma, g, 2e-5), "{gamma}");
}
#[test]
fn gradients_across_a_sample_and_the_suns_redshift() {
let jila = frequency_shift(-9.796 * 1e-3);
assert!(close(jila, 1.09e-19, 5e-22) && (jila - 0.98e-19).abs() < 2.3e-20);
let wisconsin = frequency_shift(-9.803 * 1e-2);
assert!(close(wisconsin, 10.9e-19, 5e-21) && (((12.4e-19 - wisconsin) / 0.7f64.hypot(2.5) / 1e-19) - 0.575).abs() < 0.001);
let (r_sun, r_earth) = (6.957e8, 6.3781e6);
let speed = |w_clock: f64, w_ref: f64| -C * frequency_shift(w_clock - w_ref);
assert!(close(speed(GM_SUN / r_sun, 0.0), 636.31, 0.005) && close(speed(GM_SUN / r_sun, GM_SUN / AU), 633.35, 0.005));
let seen = speed(GM_SUN / r_sun, GM_SUN / 147.75e9 + GM_EARTH / r_earth);
assert!(close(seen, 633.10, 0.01) && (638.0 - seen).abs() < 6.0);
}
#[test]
fn gnss_corrections() {
assert!(close(GNSS_ECCENTRICITY_F, -2.0 * GRS80_GM.sqrt() / C2, 1e-19));
let a = 5153.8f64.powi(2);
let orbit = rate_tcg(GM_EARTH / a, (GM_EARTH / a).sqrt()) - rate_tcg(W0_IAU, 0.0);
assert!(close(orbit, -GPS_CLOCK_RATE_OFFSET, 3e-14), "{orbit:e}");
assert!(close(eccentricity_correction(a, 0.01, core::f64::consts::FRAC_PI_2), -22.9e-9, 0.05e-9));
let (sat, rcv) = ([6_378_137.0, 20_000e3, 0.0], [6_378_137.0, 0.0, 0.0]);
let s = sagnac(sat, rcv);
assert!(s < -10e-9 && s > -133e-9 && sagnac(rcv, sat) == -s);
let light = 20_000e3 / C;
let shapiro = one_way_transfer(sat, rcv) - light - s;
assert!(shapiro > 1e-12 && shapiro < 1e-10, "{shapiro:e}");
}
#[test]
fn cassini_and_proper_time() {
let b: f64 = 1.6 * 6.957e8;
let (r1, r2) = (AU, 8.43 * AU);
let r12 = (r1 * r1 - b * b).sqrt() + (r2 * r2 - b * b).sqrt();
let round_trip = 2.0 * shapiro_delay(GM_SUN, r1, r2, r12) * 1e6;
assert!(close(round_trip, 262.42, 0.01) && close(round_trip / (240.0 - 20.0 * (1.6f64.powi(2) / 8.43).ln()), 1.0, 0.006));
let a = 26_561.75e3;
let (u, v) = (GM_EARTH / a, (GM_EARTH / a).sqrt());
let offset = proper_time_offset(&[(0.0, u, v), (20.0, u, v), (60.0, u, v)]);
assert!(close(offset, 60.0 * rate_tcg(u, v), 1e-24) && close(offset, -1.503e-8, 0.002e-8));
assert_eq!(proper_time_offset(&[(0.0, u, v)]), 0.0);
}
}