darkmatter 0.0.1

Dark matter simulation engine — gravitational fields, particle dynamics, halo stability, and cosmological constants
Documentation
use crate::constants::physical::C;
use std::f64::consts::PI;

pub fn reduced_mass(m1: f64, m2: f64) -> f64 {
    m1 * m2 / (m1 + m2)
}

pub fn max_recoil_energy(m_dm: f64, m_nucleus: f64, v: f64) -> f64 {
    let mu = reduced_mass(m_dm, m_nucleus);
    2.0 * mu * mu * v * v / m_nucleus
}

pub fn recoil_spectrum_rate(
    rho_local: f64,
    m_dm: f64,
    m_nucleus: f64,
    sigma_0: f64,
    v_0: f64,
    e_r: f64,
) -> f64 {
    let mu = reduced_mass(m_dm, m_nucleus);
    let v_min = (m_nucleus * e_r / (2.0 * mu * mu)).sqrt();
    if v_min > v_0 * 3.0 {
        return 0.0;
    }
    let n_dm = rho_local / m_dm;
    let form_factor = helm_form_factor_sq(e_r, m_nucleus);
    let a_sq = m_nucleus / (1.66054e-27);
    let prefactor = n_dm * sigma_0 * a_sq * a_sq / (2.0 * mu * mu);
    prefactor * form_factor * velocity_integral(v_min, v_0)
}

pub fn helm_form_factor_sq(e_r: f64, m_nucleus: f64) -> f64 {
    let a_nucleon = m_nucleus / 1.66054e-27;
    let hbar_c = 197.327e-15;
    let r_n = 1.2e-15 * a_nucleon.powf(1.0 / 3.0);
    let s = 0.9e-15;
    let r_eff = (r_n * r_n - 5.0 * s * s).sqrt();
    let q = (2.0 * m_nucleus * e_r).sqrt();
    let qr = q * r_eff / (hbar_c * 1e15 / C);
    if qr < 1e-10 {
        return 1.0;
    }
    let j1 = qr.sin() / (qr * qr) - qr.cos() / qr;
    let ff = 3.0 * j1 / qr * (-q * q * s * s / (2.0 * hbar_c * hbar_c * 1e30 / (C * C))).exp();
    ff * ff
}

fn velocity_integral(v_min: f64, v_0: f64) -> f64 {
    let v_esc = 544e3;
    if v_min >= v_esc {
        return 0.0;
    }
    let k = PI.sqrt() * v_0;
    let erf_term =
        crate::utils::math::erf_approx(v_min / v_0) - crate::utils::math::erf_approx(v_esc / v_0);
    let exp_term = (-v_esc * v_esc / (v_0 * v_0)).exp();
    k * (erf_term.abs() - 2.0 * (v_esc - v_min) / (PI.sqrt() * v_0) * exp_term)
}

pub fn annual_modulation_amplitude(v_earth_orbital: f64, v_sun: f64, v_0: f64) -> f64 {
    v_earth_orbital * v_sun / (v_0 * v_0)
}

pub fn spin_independent_cross_section(
    sigma_p: f64,
    a: f64,
    m_dm: f64,
    m_proton: f64,
    m_nucleus: f64,
) -> f64 {
    let mu_p = reduced_mass(m_dm, m_proton);
    let mu_a = reduced_mass(m_dm, m_nucleus);
    sigma_p * (mu_a / mu_p).powi(2) * a * a
}

pub fn spin_dependent_cross_section(
    sigma_p: f64,
    j_nucleus: f64,
    s_p: f64,
    s_n: f64,
    a_p: f64,
    a_n: f64,
) -> f64 {
    let factor = (j_nucleus + 1.0) / j_nucleus;
    sigma_p * factor * (s_p * a_p + s_n * a_n).powi(2)
}

pub fn event_rate_per_kg_day(
    rho_local_gev_cm3: f64,
    m_dm_gev: f64,
    sigma_cm2: f64,
    a_target: f64,
    v_0_km_s: f64,
) -> f64 {
    let rho = rho_local_gev_cm3 * 1e6;
    let n_dm = rho / m_dm_gev;
    let v_0 = v_0_km_s * 1e5;
    let n_target = 6.022e23 / a_target * 1000.0;
    n_dm * n_target * sigma_cm2 * v_0 * 86400.0
}

pub fn exclusion_limit_sigma(
    n_events: f64,
    exposure_kg_day: f64,
    rho_local_gev_cm3: f64,
    m_dm_gev: f64,
    a_target: f64,
    v_0_km_s: f64,
) -> f64 {
    let rho = rho_local_gev_cm3 * 1e6;
    let n_dm = rho / m_dm_gev;
    let v_0 = v_0_km_s * 1e5;
    let n_target = 6.022e23 / a_target * 1000.0;
    n_events / (exposure_kg_day * n_dm * n_target * v_0 * 86400.0)
}