darkmatter 0.0.2

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

pub fn blumenthal_contraction_radius(
    r_initial: f64,
    m_dm_initial: f64,
    m_baryon_final: f64,
    m_dm_conserved: f64,
) -> f64 {
    let m_total_initial = m_dm_initial;
    let m_total_final = m_dm_conserved + m_baryon_final;
    if m_total_final < 1e-100 {
        return r_initial;
    }
    r_initial * m_total_initial / m_total_final
}

pub fn adiabatic_contraction_nfw(
    r_f: f64,
    m_nfw_fn: &dyn Fn(f64) -> f64,
    m_baryon_fn: &dyn Fn(f64) -> f64,
    tol: f64,
    max_iter: usize,
) -> f64 {
    let mut r_i = r_f;
    for _ in 0..max_iter {
        let m_i = m_nfw_fn(r_i);
        let m_b = m_baryon_fn(r_f);
        let r_new = r_f * (m_i + m_b) / m_i;
        if (r_new - r_i).abs() / r_i.max(1e-30) < tol {
            return r_new;
        }
        r_i = r_new;
    }
    r_i
}

pub fn gnedin_contraction_radius(
    r_initial: f64,
    r_bar: f64,
    m_dm_initial: f64,
    m_baryon_final: f64,
    a_gnedin: f64,
) -> f64 {
    let r_eff = a_gnedin * r_initial + (1.0 - a_gnedin) * r_bar;
    let m_total = m_dm_initial + m_baryon_final;
    if m_total < 1e-100 {
        return r_initial;
    }
    r_eff * m_dm_initial / m_total
}

pub fn contracted_density_profile(
    r_f: f64,
    r_f_dr: f64,
    rho_initial_fn: &dyn Fn(f64) -> f64,
    m_nfw_fn: &dyn Fn(f64) -> f64,
    m_baryon_fn: &dyn Fn(f64) -> f64,
    tol: f64,
    max_iter: usize,
) -> f64 {
    let r_i_1 = adiabatic_contraction_nfw(r_f, m_nfw_fn, m_baryon_fn, tol, max_iter);
    let r_i_2 = adiabatic_contraction_nfw(r_f_dr, m_nfw_fn, m_baryon_fn, tol, max_iter);
    let dr_f = r_f_dr - r_f;
    let dr_i = r_i_2 - r_i_1;
    if dr_f.abs() < 1e-50 {
        return rho_initial_fn(r_i_1);
    }
    rho_initial_fn(r_i_1) * (r_i_1 / r_f).powi(2) * (dr_i / dr_f).abs()
}

pub fn baryon_feedback_expansion(r: f64, r_core_kpc: f64, expansion_factor: f64) -> f64 {
    let r_core = r_core_kpc * 3.0857e19;
    r * (1.0 + expansion_factor * (-r * r / (2.0 * r_core * r_core)).exp())
}

pub fn cusp_to_core_transformation(rho_nfw: f64, r: f64, r_core_kpc: f64) -> f64 {
    let r_core = r_core_kpc * 3.0857e19;
    let suppression = r * r / (r * r + r_core * r_core);
    rho_nfw * suppression
}

pub fn dekel_zhao_contracted_profile(
    r: f64,
    rho_s: f64,
    r_s: f64,
    alpha: f64,
    beta: f64,
    gamma: f64,
) -> f64 {
    let x = r / r_s;
    rho_s / (x.powf(gamma) * (1.0 + x.powf(alpha)).powf((beta - gamma) / alpha))
}

pub fn pseudo_adiabatic_contraction(
    r_final: f64,
    m_dm_initial_fn: &dyn Fn(f64) -> f64,
    m_baryon_fn: &dyn Fn(f64) -> f64,
    nu: f64,
) -> f64 {
    let m_b = m_baryon_fn(r_final);
    let m_dm = m_dm_initial_fn(r_final);
    let r_initial = r_final * (m_dm + m_b) / m_dm;
    r_final + nu * (r_initial - r_final)
}

pub fn response_to_baryon_potential(
    r: f64,
    rho_dm_fn: &dyn Fn(f64) -> f64,
    phi_baryon_fn: &dyn Fn(f64) -> f64,
    sigma_v: f64,
) -> f64 {
    let phi_b = phi_baryon_fn(r);
    let rho_0 = rho_dm_fn(r);
    rho_0 * (-phi_b / (sigma_v * sigma_v)).exp()
}

pub fn concentration_mass_relation_contracted(m_200_solar: f64, f_baryon: f64) -> f64 {
    let log_m = (m_200_solar / 1e12).log10();
    let c_base = 10.0_f64.powf(0.905 - 0.101 * log_m);
    c_base * (1.0 + 2.0 * f_baryon)
}

pub fn circular_velocity_contracted(m_enclosed_solar: f64, r_kpc: f64) -> f64 {
    let m = m_enclosed_solar * 1.989e30;
    let r = r_kpc * 3.0857e19;
    (G * m / r).sqrt() / 1e3
}

pub fn nfw_enclosed_mass_contracted(rho_s: f64, r_s: f64, r: f64) -> f64 {
    let x = r / r_s;
    4.0 * PI * rho_s * r_s.powi(3) * ((1.0 + x).ln() - x / (1.0 + x))
}