use crate::integrate_log;
use metrology::{C, G};
use core::f64::consts::PI;
pub fn chirp_mass(m1: f64, m2: f64) -> f64 {
(m1 * m2).powf(0.6) / (m1 + m2).powf(0.2)
}
pub fn f_isco(m: f64) -> f64 {
C.powi(3) / (6f64.powf(1.5) * PI * G * m)
}
pub fn time_to_merger(f: f64, chirp: f64) -> f64 {
5.0 / 256.0 * (PI * f).powf(-8.0 / 3.0) * (G * chirp / C.powi(3)).powf(-5.0 / 3.0)
}
pub fn frequency_drift(f: f64, chirp: f64) -> f64 {
96.0 / 5.0 * PI.powf(8.0 / 3.0) * (G * chirp / C.powi(3)).powf(5.0 / 3.0) * f.powf(11.0 / 3.0)
}
pub fn amplitude(f: f64, chirp: f64, d: f64) -> f64 {
(5.0f64 / 24.0).sqrt() * PI.powf(-2.0 / 3.0) * (C / d) * (G * chirp / C.powi(3)).powf(5.0 / 6.0) * f.powf(-7.0 / 6.0)
}
pub fn snr(h: impl Fn(f64) -> f64, s_n: impl Fn(f64) -> f64, f_lo: f64, f_hi: f64) -> f64 {
(4.0 * integrate_log(f_lo, f_hi, 4000, |f| { let a = h(f); a * a / s_n(f) })).sqrt()
}
pub fn horizon(m1: f64, m2: f64, threshold: f64, s_n: impl Fn(f64) -> f64, f_lo: f64) -> f64 {
let chirp = chirp_mass(m1, m2);
let f_hi = f_isco(m1 + m2);
if f_lo >= f_hi {
return 0.0;
}
snr(|f| amplitude(f, chirp, 1.0), s_n, f_lo, f_hi) / threshold
}
pub fn energy_spectrum(chirp: f64, f: f64) -> f64 {
PI.powf(2.0 / 3.0) / 3.0 * G.powf(2.0 / 3.0) * chirp.powf(5.0 / 3.0) * f.powf(-1.0 / 3.0)
}
pub const HORIZON_TO_RANGE: f64 = 2.264;
#[cfg(test)]
mod tests {
use super::*;
use crate::curves;
use metrology::{MPC, SOLAR_MASS};
#[test]
fn binary_neutron_star_numbers() {
let m = 1.4 * SOLAR_MASS;
let mc = chirp_mass(m, m);
assert!((mc / SOLAR_MASS / 1.21877 - 1.0).abs() < 1e-5, "{}", mc / SOLAR_MASS);
assert!((time_to_merger(20.0, mc) / 157.9 - 1.0).abs() < 1e-3, "{}", time_to_merger(20.0, mc));
assert!((frequency_drift(100.0, mc) / 17.36 - 1.0).abs() < 1e-3);
assert!((f_isco(2.0 * m) / 1570.4 - 1.0).abs() < 1e-4);
assert!((f_isco(SOLAR_MASS) / 4397.0 - 1.0).abs() < 1e-3);
}
#[test]
fn snr_scales_inversely_with_distance() {
let mc = chirp_mass(30.0 * SOLAR_MASS, 30.0 * SOLAR_MASS);
let near = snr(|f| amplitude(f, mc, 400.0 * MPC), curves::aligo, 20.0, 70.0);
let far = snr(|f| amplitude(f, mc, 800.0 * MPC), curves::aligo, 20.0, 70.0);
assert!((near / far - 2.0).abs() < 1e-9);
}
#[test]
fn horizon_with_the_analytic_fit() {
let d = horizon(1.4 * SOLAR_MASS, 1.4 * SOLAR_MASS, 8.0, curves::aligo, 20.0) / MPC;
assert!((d / 496.5 - 1.0).abs() < 1e-3, "{d}");
}
#[test]
fn heavy_binaries_that_merge_below_the_band_have_no_horizon() {
let m = 500.0 * SOLAR_MASS;
assert_eq!(horizon(m, m, 8.0, crate::curves::aligo, 10.0), 0.0);
}
#[test]
fn gw170817_spectral_fluence() {
let fluence = energy_spectrum(1.188 * SOLAR_MASS, 900.0) / (4.0 * PI * (40.0 * MPC).powi(2));
assert!((fluence / 2.66691e-7 - 1.0).abs() < 1e-5);
}
}