use core::f64::consts::PI;
use geopotential::Field;
use metrology::{C, EPS_0, G};
pub const ELECTRON_GRAVITATIONAL_COUPLING: f64 = G * metrology::M_E * metrology::M_E / (metrology::HBAR * C);
pub const GM_PERMITTIVITY: f64 = 1.0 / (4.0 * PI * G);
pub const GM_PERMEABILITY: f64 = 4.0 * PI * G / (C * C);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Convention {
Mashhoon,
RuggieroTartaglia,
MaxwellSi,
}
pub fn equatorial_field(j: f64, r: f64, convention: Convention) -> f64 {
let mashhoon = G * j / (C * r.powi(3));
match convention {
Convention::Mashhoon => mashhoon,
Convention::RuggieroTartaglia => 2.0 * mashhoon,
Convention::MaxwellSi => G * j / (2.0 * C * C * r.powi(3)),
}
}
pub fn force_per_mass(v: f64, b: f64, convention: Convention) -> f64 {
match convention {
Convention::Mashhoon => 2.0 * v * b / C,
Convention::RuggieroTartaglia => v * b / C,
Convention::MaxwellSi => 4.0 * v * b,
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct GravitomagneticDipole {
pub j: f64,
}
impl Field for GravitomagneticDipole {
fn potential(&self, r: f64, lat: f64, _lon: f64) -> f64 {
G * self.j / C * lat.sin() / (r * r)
}
fn vector(&self, r: f64, lat: f64, _lon: f64) -> [f64; 3] {
let k = G * self.j / (C * r.powi(3));
[-k * lat.cos(), 0.0, 2.0 * k * lat.sin()]
}
}
pub const MAS_PER_YEAR: f64 = metrology::YEAR * 180.0 / PI * 3.6e6;
pub fn lense_thirring_node_rate(j: f64, a: f64, e: f64) -> f64 {
2.0 * G * j / (C * C * a.powi(3) * (1.0 - e * e).powf(1.5))
}
pub fn lense_thirring_perigee_rate(j: f64, a: f64, e: f64, inclination: f64) -> f64 {
-3.0 * inclination.cos() * lense_thirring_node_rate(j, a, e)
}
pub fn gyroscope_frame_dragging(j: f64, a: f64) -> f64 {
equatorial_field(j, a, Convention::Mashhoon) / (2.0 * C)
}
pub fn extremal_charge_to_mass() -> f64 {
(4.0 * PI * EPS_0 * G).sqrt()
}
pub fn wgc_ratio(q: f64, m: f64) -> f64 {
q / (m * extremal_charge_to_mass())
}
pub fn blackett_beta(mu: f64, j: f64) -> f64 {
2.0 * mu / (extremal_charge_to_mass() * j)
}
pub fn blackett_moment(j: f64, beta: f64) -> f64 {
beta / 2.0 * extremal_charge_to_mass() * j
}
#[cfg(test)]
mod tests {
use super::*;
use metrology::{E_CHARGE, M_E, M_P};
fn close(a: f64, b: f64, rel: f64) -> bool {
(a / b - 1.0).abs() < rel
}
#[test]
fn earth_dipole() {
let earth = GravitomagneticDipole { j: 5.86e33 };
let r = 6.371e6;
assert!(close(earth.magnitude(r, 0.0, 0.0), 5.044_984e-6, 1e-6));
assert!(close(earth.magnitude(r, 0.0, 0.0) / C, 1.682_83e-14, 1e-5)); let (lat, h) = (0.4, 1e-5);
let up = -(earth.potential(r * (1.0 + h), lat, 0.0) - earth.potential(r * (1.0 - h), lat, 0.0)) / (2.0 * r * h);
let north = -(earth.potential(r, lat + h, 0.0) - earth.potential(r, lat - h, 0.0)) / (2.0 * h * r);
let [n, e, u] = earth.vector(r, lat, 0.0);
assert!(close(u, up, 1e-8) && close(n, north, 1e-8) && e == 0.0);
}
#[test]
fn conventions_agree() {
let (j, r, v) = (5.86e33, 6.371e6, 7.9e3);
let forces = [Convention::Mashhoon, Convention::RuggieroTartaglia, Convention::MaxwellSi].map(|c| force_per_mass(v, equatorial_field(j, r, c), c));
assert!(close(forces[0], 2.658_864e-10, 1e-6));
assert!(close(forces[1], forces[0], 1e-12) && close(forces[2], forces[0], 1e-12));
assert!(close(1.0 / (GM_PERMITTIVITY * GM_PERMEABILITY).sqrt(), C, 1e-12));
assert!(close(GM_PERMITTIVITY, 1.192_297e9, 1e-6) && close(GM_PERMEABILITY, 9.331_99e-27, 1e-5));
}
#[test]
fn frame_dragging_of_orbits_and_gyroscopes() {
use metrology::{EARTH_ANGULAR_MOMENTUM as J, GM_EARTH};
let lageos = lense_thirring_node_rate(J, 12_270e3, 0.0045) * MAS_PER_YEAR;
assert!(close(lageos, 30.67, 2e-4) && close(lageos, 31.0, 0.02));
assert!(close(lense_thirring_node_rate(J, 7820e3, 0.0007) * MAS_PER_YEAR, 118.0, 0.01));
let i = 109.8f64.to_radians();
assert!(close(lense_thirring_perigee_rate(J, 12_270e3, 0.0045, i) / lense_thirring_node_rate(J, 12_270e3, 0.0045), -3.0 * i.cos(), 1e-12));
let a = 6371e3 + 642e3;
let dragging = gyroscope_frame_dragging(J, a) * MAS_PER_YEAR;
assert!(close(dragging * 16.84f64.to_radians().cos(), 39.2, 5e-3), "{dragging}");
assert!(close(gravitational::energy::geodetic_precession(GM_EARTH, a) * MAS_PER_YEAR, 6606.1, 6e-3));
let earth = GravitomagneticDipole { j: J };
assert!(close(gyroscope_frame_dragging(J, a), 0.5 * earth.magnitude(a, 0.0, 0.0) / C, 1e-12));
}
#[test]
fn blackett_and_extremal_ratio() {
assert!(close(extremal_charge_to_mass(), 8.617_52e-11, 1e-5));
assert!(close(ELECTRON_GRAVITATIONAL_COUPLING, 1.751_81e-45, 1e-5));
assert!(close(metrology::ALPHA / ELECTRON_GRAVITATIONAL_COUPLING, 4.165_57e42, 1e-4));
assert!(close(wgc_ratio(E_CHARGE, M_E), 2.040_98e21, 1e-5) && close(wgc_ratio(E_CHARGE, M_P), 1.111_55e18, 1e-5));
assert!(close(blackett_beta(7.91e22, 5.87e33), 0.312_742, 1e-5));
assert!(close(blackett_beta(7.690e22, 5.87e33), 0.304_044, 1e-5));
assert!(close(blackett_moment(5.87e33, blackett_beta(7.91e22, 5.87e33)), 7.91e22, 1e-12));
}
}