darkmatter 0.0.2

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

pub fn dynamical_friction_timescale(
    m_satellite: f64,
    m_cluster: f64,
    r_orbit: f64,
    v_orbit: f64,
    ln_lambda: f64,
) -> f64 {
    let rho = 3.0 * m_cluster / (4.0 * PI * r_orbit.powi(3));
    let coulomb = 4.0 * PI * G * G * m_satellite * rho * ln_lambda / (v_orbit * v_orbit * v_orbit);
    m_satellite * v_orbit / coulomb
}

pub fn tidal_stripping_radius(m_satellite: f64, m_cluster: f64, d_cluster_center: f64) -> f64 {
    d_cluster_center * (m_satellite / (3.0 * m_cluster)).powf(1.0 / 3.0)
}

pub fn ram_pressure_stripping_condition(
    rho_icm: f64,
    v_galaxy: f64,
    sigma_star: f64,
    sigma_gas: f64,
) -> f64 {
    let p_ram = rho_icm * v_galaxy * v_galaxy;
    let p_restoring = 2.0 * PI * G * sigma_star * sigma_gas;
    p_ram / p_restoring
}

pub fn galaxy_harassment_energy(
    m_perturber: f64,
    b_impact: f64,
    v_encounter: f64,
    r_galaxy: f64,
) -> f64 {
    let delta_v = 2.0 * G * m_perturber / (b_impact * v_encounter);
    0.5 * delta_v * delta_v * r_galaxy
}

pub fn merger_timescale_lacey_cole(
    m1: f64,
    m2: f64,
    r_vir: f64,
    v_vir: f64,
    ln_lambda: f64,
) -> f64 {
    let mass_ratio = m2 / m1;
    let f_orbit = 0.5;
    let orbital_energy = r_vir * v_vir * v_vir / (G * (m1 + m2));
    f_orbit * 1.17 / mass_ratio / ln_lambda * orbital_energy * r_vir / v_vir
}

pub fn bullet_cluster_velocity_estimate(m1: f64, m2: f64, r_sep: f64) -> f64 {
    (2.0 * G * (m1 + m2) / r_sep).sqrt()
}

pub fn bullet_cluster_constraints_sidm(v_collision: f64, separation_dm_gas: f64) -> f64 {
    separation_dm_gas / (v_collision * v_collision) * C * C
}

pub fn dm_self_interaction_offset(sigma_over_m: f64, sigma_dm: f64, v_collision: f64) -> f64 {
    let tau = sigma_over_m * sigma_dm;
    tau * v_collision
}

pub const SUBHALO_MASS_FUNCTION_SLOPE: f64 = -0.9;

pub fn subhalo_abundance_in_cluster(m_cluster_solar: f64, m_sub_min_solar: f64) -> f64 {
    let alpha = SUBHALO_MASS_FUNCTION_SLOPE;
    let m_ratio = m_sub_min_solar / m_cluster_solar;
    0.01 * m_ratio.powf(alpha + 1.0) * m_cluster_solar / m_sub_min_solar
}

pub fn velocity_dispersion_from_mass(m_200: f64, r_200: f64) -> f64 {
    (G * m_200 / (3.0 * r_200)).sqrt()
}

pub fn crossing_time(r_vir: f64, sigma_v: f64) -> f64 {
    2.0 * r_vir / sigma_v
}

pub fn relaxation_criterion(offset: f64, r_200: f64) -> bool {
    offset / r_200 < 0.07
}

pub fn bautz_morgan_type(bcg_dominance: f64) -> &'static str {
    if bcg_dominance > 2.0 {
        "I"
    } else if bcg_dominance > 1.5 {
        "I-II"
    } else if bcg_dominance > 1.0 {
        "II"
    } else {
        "III"
    }
}

pub fn infall_velocity_at_turnaround(m_cluster: f64, r_turnaround: f64) -> f64 {
    (2.0 * G * m_cluster / r_turnaround).sqrt()
}

pub fn virial_shock_temperature(v_infall: f64) -> f64 {
    let mu = 0.6;
    let mp = 1.673e-27;
    let k_b = 1.381e-23;
    3.0 * mu * mp * v_infall * v_infall / (16.0 * k_b)
}

pub fn cluster_pair_binding_energy(m1: f64, m2: f64, separation: f64) -> f64 {
    G * m1 * m2 / separation
}

pub fn supercluster_mass_estimate(n_clusters: f64, avg_mass: f64) -> f64 {
    n_clusters * avg_mass * SOLAR_MASS
}

pub fn filament_bridge_dm_density(m1: f64, m2: f64, separation: f64, d_from_center: f64) -> f64 {
    let m_eff = (m1 * m2).sqrt();
    let rho = G * m_eff / (C * C * separation * separation);
    rho * (-d_from_center * d_from_center / (0.1 * separation * separation)).exp()
}