use core::f64::consts::{PI, SQRT_2};
use metrology::{C, G};
pub const PLANCK_LUMINOSITY: f64 = C * C * C * C * C / G;
pub fn strain_from_luminosity(fraction: f64, f: f64, r: f64) -> f64 {
2.0 * SQRT_2 * fraction.sqrt() * C / (2.0 * PI * f * r)
}
pub fn circular_flux(eta: f64, x: f64) -> f64 {
32.0 / 5.0 * eta * eta * x.powi(5)
}
pub fn energy_flux_coefficients(l: u32) -> (f64, f64) {
let factorial = |n: u32| (1..=n).map(f64::from).product::<f64>();
let double_factorial = |n: u32| (1..=n).rev().step_by(2).map(f64::from).product::<f64>();
let l_f = f64::from(l);
let a = (l_f + 1.0) * (l_f + 2.0) / ((l_f - 1.0) * l_f * factorial(l) * double_factorial(2 * l + 1));
let b = 4.0 * l_f * (l_f + 2.0) / ((l_f - 1.0) * factorial(l + 1) * double_factorial(2 * l + 1));
(a, b)
}
pub fn peters_enhancement(e: f64) -> f64 {
let e2 = e * e;
(1.0 + 73.0 / 24.0 * e2 + 37.0 / 96.0 * e2 * e2) / (1.0 - e2).powf(3.5)
}
pub fn peters_pbdot(mp: f64, mc: f64, pb: f64, e: f64) -> f64 {
-192.0 * PI / 5.0 * (G / C.powi(3)).powf(5.0 / 3.0) * (pb / (2.0 * PI)).powf(-5.0 / 3.0) * peters_enhancement(e) * mp * mc
/ (mp + mc).cbrt()
}
pub fn shapiro_range(mc: f64) -> f64 {
G * mc / C.powi(3)
}
pub fn shapiro_shape(mp: f64, mc: f64, pb: f64, x: f64) -> f64 {
let m = mp + mc;
x * (2.0 * PI / pb).powf(2.0 / 3.0) * (G * m / C.powi(3)).powf(-1.0 / 3.0) * m / mc
}
pub fn periastron_advance(gm: f64, a: f64, e: f64) -> f64 {
6.0 * PI * gm / (C * C * a * (1.0 - e * e))
}
pub fn geodetic_precession(gm: f64, a: f64) -> f64 {
1.5 * gm.powf(1.5) / (C * C * a.powf(2.5))
}
pub fn shapiro_delay(r: f64, s: f64, e: f64, omega: f64, u: f64) -> f64 {
let (sw, cw) = omega.sin_cos();
let (su, cu) = u.sin_cos();
-2.0 * r * (1.0 - e * cu - s * (sw * (cu - e) + (1.0 - e * e).sqrt() * cw * su)).ln()
}
#[cfg(test)]
mod tests {
use super::*;
use metrology::{MPC, SOLAR_MASS};
fn close(a: f64, b: f64, rel: f64) -> bool {
(a / b - 1.0).abs() < rel
}
#[test]
fn luminosity_and_strain() {
assert!(close(PLANCK_LUMINOSITY, 3.62825e52, 1e-5) && close(PLANCK_LUMINOSITY / 4.0, 9.07064e51, 1e-5));
assert!(close(3.7e49 / PLANCK_LUMINOSITY, 1.01977e-3, 1e-5)); assert!(close(strain_from_luminosity(1e-3, 250.0, 410.0 * MPC), 1.34931e-21, 1e-5));
assert!(close(strain_from_luminosity(1.0, 1.0, C), 0.450158, 1e-5));
assert!(close(circular_flux(0.25, 1.0 / 6.0), 5.14403e-5, 1e-5));
}
#[test]
fn multipole_coefficients() {
for (l, a, b) in [(2, 1.0 / 5.0, 16.0 / 45.0), (3, 1.0 / 189.0, 1.0 / 84.0), (4, 1.0 / 9072.0, 4.0 / 14175.0)] {
let (al, bl) = energy_flux_coefficients(l);
assert!(close(al, a, 1e-12) && close(bl, b, 1e-12), "{l}: {al} {bl}");
}
}
#[test]
fn binary_pulsars() {
assert!(close(peters_enhancement(0.6171340), 11.8568, 1e-5));
let day = 86_400.0;
let b1913 = peters_pbdot(1.438 * SOLAR_MASS, 1.390 * SOLAR_MASS, 0.322997448918 * day, 0.6171340);
assert!(close(b1913, -2.40210e-12, 1e-5) && close(b1913, -2.40263e-12, 1e-3));
let j0737 = peters_pbdot(1.338185 * SOLAR_MASS, 1.248868 * SOLAR_MASS, 0.1022515592973 * day, 0.087777023);
assert!(close(j0737, -1.24781e-12, 1e-5));
}
#[test]
fn periastron_advances_and_geodetic_precession() {
use metrology::{AU, GM_SUN, YEAR};
let arcsec = PI / 648_000.0;
let (a, e) = (0.387_099_27 * AU, 0.205_635_93);
let period = 2.0 * PI * (a * a * a / GM_SUN).sqrt();
assert!(close(periastron_advance(GM_SUN, a, e) * 100.0 * YEAR / period / arcsec, 42.98, 5e-4));
let (pb, e, m) = (0.1022515592973 * 86_400.0, 0.087777023, 2.587052 * GM_SUN);
let a = (m * (pb / (2.0 * PI)).powi(2)).cbrt();
let omega_dot = periastron_advance(m, a, e) / pb * YEAR * 180.0 / PI;
assert!(close(omega_dot, 16.899_367, 1e-6) && close(omega_dot - 16.899_323, 4.4e-5, 0.05), "{omega_dot}");
let s2 = periastron_advance(4.261e6 * GM_SUN, 125.058e-3 * 8246.7 * AU, 0.884_649) / arcsec / 60.0;
assert!(close(s2, 12.156, 1e-3) && close(s2, 12.1, 0.06));
assert!(close(geodetic_precession(GM_SUN, AU) * 100.0 * YEAR / arcsec, 1.919, 1e-3));
let mas_per_year = YEAR * 180.0 / PI * 3.6e6;
assert!(close(geodetic_precession(metrology::GM_EARTH, 6371e3 + 642e3) * mas_per_year / 6606.1, 1.0, 6e-3));
assert!(close(periastron_advance(GM_SUN, AU, 0.0), 6.0 * PI * GM_SUN / (C * C * AU), 1e-15));
assert!(periastron_advance(GM_SUN, AU, 0.99) > 50.0 * periastron_advance(GM_SUN, AU, 0.0));
}
#[test]
fn the_double_pulsar_shapiro_delay() {
let (ma, mb) = (1.338185 * SOLAR_MASS, 1.248868 * SOLAR_MASS);
let r = shapiro_range(mb);
assert!((r / 6.1513e-6 - 1.0).abs() < 1e-5 && ((6.162e-6 - r) / 0.021e-6 - 0.51).abs() < 0.01);
let measured = 1.0 - (-9.65f64).exp();
assert!((measured - 0.999936).abs() < 1e-6);
let s = shapiro_shape(ma, mb, 0.1022515592973 * 86_400.0, 1.415028603);
assert!((measured / s - 1.00009).abs() < 0.00018 && (measured / s - 1.000013).abs() < 1e-6, "{s}");
let peak = shapiro_delay(r, measured, 0.0, 0.3, PI / 2.0 - 0.3);
assert!((peak / (2.0 * r * 9.65) - 1.0).abs() < 1e-12 && peak > shapiro_delay(r, measured, 0.0, 0.3, 1.0));
}
}