use core::f64::consts::PI;
use metrology::{C, G};
use crate::inspiral::kerr_isco;
const K01: f64 = -1.2019;
const K02: f64 = -1.20764;
const K10: f64 = 3.79245;
const K11: f64 = 1.18385;
const K12: f64 = 4.90494;
const XI: f64 = 0.41616;
const EQUAL_MASS_SPIN: f64 = 0.68646;
const P0: f64 = 0.04827;
const P1: f64 = 0.01707;
fn ratios(m1: f64, m2: f64, a1: f64, a2: f64) -> (f64, f64) {
let m = m1 + m2;
(m1 * m2 / (m * m), (a1 * m1 * m1 + a2 * m2 * m2) / (m * m))
}
pub fn final_spin(m1: f64, m2: f64, a1: f64, a2: f64) -> f64 {
let (nu, a_tot, ell) = orbital(m1, m2, a1, a2);
a_tot + nu * ell
}
fn orbital(m1: f64, m2: f64, a1: f64, a2: f64) -> (f64, f64, f64) {
let (nu, a_tot) = ratios(m1, m2, a1, a2);
let a_eff = a_tot + XI * nu * (a1 + a2);
let (_, e, l) = kerr_isco(a_eff);
let k00 = 16.0 * (EQUAL_MASS_SPIN - 3f64.sqrt() / 2.0) - K10 / 4.0;
let fit = nu * (k00 + K01 * a_eff + K02 * a_eff * a_eff) + nu * nu * (K10 + K11 * a_eff + K12 * a_eff * a_eff);
(nu, a_tot, l - 2.0 * a_tot * (e - 1.0) + fit)
}
pub fn final_spin_precessing(m1: f64, m2: f64, a1: f64, a2: f64, tilt1: f64, tilt2: f64, phi12: f64) -> f64 {
let (p1, p2) = (a1 * tilt1.cos(), a2 * tilt2.cos());
let (_, _, ell) = orbital(m1, m2, p1, p2);
let (q, ell) = (m2 / m1, ell.abs());
let cos_alpha = tilt1.cos() * tilt2.cos() + tilt1.sin() * tilt2.sin() * phi12.cos();
let sum = a1 * a1 + a2 * a2 * q.powi(4) + 2.0 * a1 * a2 * q * q * cos_alpha + 2.0 * (p1 + p2 * q * q) * ell * q + ell * ell * q * q;
sum.sqrt() / (1.0 + q).powi(2)
}
pub fn radiated_energy(m1: f64, m2: f64, a1: f64, a2: f64) -> f64 {
let (nu, a) = ratios(m1, m2, a1, a2);
let (_, e, _) = kerr_isco(a);
(1.0 - e) * nu + 4.0 * nu * nu * (4.0 * P0 + 16.0 * P1 * a * (a + 1.0) + e - 1.0)
}
pub fn horizon_area(mass: f64, chi: f64) -> f64 {
let rg = G * mass / (C * C);
8.0 * PI * rg * rg * (1.0 + (1.0 - chi * chi).sqrt())
}
#[cfg(test)]
mod tests {
use super::*;
use metrology::SOLAR_MASS;
#[test]
fn the_final_spin() {
assert!((final_spin(1.0, 1.0, 0.0, 0.0) - 0.68646).abs() < 1e-12);
let k00 = 16.0 * (EQUAL_MASS_SPIN - 3f64.sqrt() / 2.0) - K10 / 4.0;
assert!((k00 + 3.82).abs() < 0.005, "{k00}");
for a1 in [-0.9, 0.0, 0.5, 0.95] {
let q = 1e-6;
let nu = q / (1.0 + q) / (1.0 + q);
let (_, e, l) = kerr_isco(a1);
let slope = (final_spin(1.0, q, a1, 0.0) - a1) / nu;
assert!((slope - (l - 2.0 * a1 * e)).abs() < 1e-4, "a₁ = {a1}: {slope}");
}
assert!((final_spin(29.0, 36.0, 0.3, -0.2) - final_spin(36.0, 29.0, -0.2, 0.3)).abs() < 1e-15);
assert!(final_spin(1.0, 1.0, 0.9, 0.9) > final_spin(1.0, 1.0, 0.0, 0.0));
assert!(final_spin(1.0, 1.0, 1.0, 1.0) < 1.0);
}
#[test]
fn the_precessing_final_spin() {
for (m1, m2, a1, a2) in [(1.0, 1.0, 0.0f64, 0.0f64), (36.0, 29.0, 0.3, -0.2), (1.0, 0.25, 0.9, 0.5), (1.0, 0.5, -0.8, 0.6), (1.0, 1.0, 0.99, 0.99)] {
let tilt = |a: f64| if a < 0.0 { PI } else { 0.0 };
let generic = final_spin_precessing(m1, m2, a1.abs(), a2.abs(), tilt(a1), tilt(a2), 0.0);
assert!((generic - final_spin(m1, m2, a1, a2).abs()).abs() < 2e-16, "{m1} {m2} {a1} {a2}");
}
let half = PI / 2.0;
let parallel = final_spin_precessing(1.0, 1.0, 0.5, 0.5, half, half, 0.0);
let antiparallel = final_spin_precessing(1.0, 1.0, 0.5, 0.5, half, half, PI);
assert!((parallel - 0.25f64.hypot(EQUAL_MASS_SPIN)).abs() < 1e-15 && (parallel - 0.730566).abs() < 5e-7, "{parallel}");
assert!((antiparallel - EQUAL_MASS_SPIN).abs() < 1e-15, "{antiparallel}");
let (a, b) = (final_spin_precessing(36.0, 29.0, 0.7, 0.4, 1.0, 2.0, 0.5), final_spin_precessing(29.0, 36.0, 0.4, 0.7, 2.0, 1.0, 0.5));
assert!((a - b).abs() < 1e-15, "{a} {b}");
}
#[test]
fn the_radiated_energy() {
assert!((radiated_energy(1.0, 1.0, 0.0, 0.0) - 0.04827).abs() < 1e-15);
assert!((radiated_energy(1.0, 1.0, 1.0, 1.0) - 0.0995).abs() < 5e-5);
let nu = 1e-6 / (1.0 + 1e-6f64).powi(2);
assert!((radiated_energy(1.0, 1e-6, 0.0, 0.0) / nu - (1.0 - (8.0f64 / 9.0).sqrt())).abs() < 1e-5);
let p = 0.6 * (PI / 3.0).cos();
assert!((radiated_energy(1.0, 1.0, p, p) - radiated_energy(1.0, 1.0, 0.3, 0.3)).abs() < 1e-15);
assert!((radiated_energy(1.0, 1.0, 0.3, 0.3) - 0.0600483).abs() < 1e-15, "{}", radiated_energy(1.0, 1.0, 0.3, 0.3));
}
#[test]
fn the_horizon_and_the_area_theorem() {
let rg = G * SOLAR_MASS / (C * C);
assert!((horizon_area(SOLAR_MASS, 0.0) / (16.0 * PI * rg * rg) - 1.0).abs() < 1e-15);
assert!((horizon_area(SOLAR_MASS, 1.0) / (8.0 * PI * rg * rg) - 1.0).abs() < 1e-15);
let (spin, mass) = (final_spin(1.0, 1.0, 0.0, 0.0), 2.0 * (1.0 - radiated_energy(1.0, 1.0, 0.0, 0.0)));
let growth = horizon_area(mass * SOLAR_MASS, spin) / (2.0 * horizon_area(SOLAR_MASS, 0.0));
assert!((growth - 1.5644).abs() < 1e-3, "{growth}");
}
}