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 nfw(r: f64, rho_s: f64, r_s: f64) -> f64 {
    let x = r / r_s;
    rho_s / (x * (1.0 + x) * (1.0 + x))
}

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

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

pub fn nfw_concentration_duffy(mass_solar: f64, redshift: f64) -> f64 {
    let pivot = 2e12;
    5.71 * (mass_solar / pivot).powf(-0.084) * (1.0 + redshift).powf(-0.47)
}

pub fn hernquist(r: f64, total_mass: f64, a: f64) -> f64 {
    total_mass * a / (2.0 * PI * r * (r + a).powi(3))
}

pub fn hernquist_enclosed_mass(r: f64, total_mass: f64, a: f64) -> f64 {
    total_mass * r * r / ((r + a) * (r + a))
}

pub fn hernquist_potential(r: f64, total_mass: f64, a: f64) -> f64 {
    -G * total_mass / (r + a)
}

pub fn burkert(r: f64, rho_0: f64, r_0: f64) -> f64 {
    rho_0 / ((1.0 + r / r_0) * (1.0 + (r / r_0) * (r / r_0)))
}

pub fn burkert_enclosed_mass(r: f64, rho_0: f64, r_0: f64) -> f64 {
    let x = r / r_0;
    PI * rho_0 * r_0 * r_0 * r_0 * ((1.0 + x * x).ln() + 2.0 * (1.0 + x).ln() - 2.0 * x.atan())
}

pub fn isothermal(sigma_v: f64, r: f64) -> f64 {
    sigma_v * sigma_v / (2.0 * PI * G * r * r)
}

pub fn isothermal_enclosed_mass(sigma_v: f64, r: f64) -> f64 {
    2.0 * sigma_v * sigma_v * r / G
}

pub fn einasto(r: f64, rho_e: f64, r_e: f64, alpha: f64) -> f64 {
    let d_n = 3.0 * alpha - 1.0 / 3.0 + 0.0079 / alpha;
    rho_e * (-d_n * ((r / r_e).powf(1.0 / alpha) - 1.0)).exp()
}

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

pub fn pseudo_isothermal(r: f64, rho_0: f64, r_c: f64) -> f64 {
    rho_0 / (1.0 + (r / r_c) * (r / r_c))
}

pub fn pseudo_isothermal_enclosed_mass(r: f64, rho_0: f64, r_c: f64) -> f64 {
    4.0 * PI * rho_0 * r_c * r_c * (r - r_c * (r / r_c).atan())
}

pub fn plummer(r: f64, total_mass: f64, b: f64) -> f64 {
    3.0 * total_mass / (4.0 * PI * b * b * b) * (1.0 + (r / b) * (r / b)).powf(-2.5)
}

pub fn dekel_zhao(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))
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub struct HaloProfile {
    pub virial_mass: f64,
    pub virial_radius: f64,
    pub concentration: f64,
    pub scale_radius: f64,
    pub rho_s: f64,
}

impl HaloProfile {
    pub fn from_nfw(virial_mass: f64, concentration: f64, redshift: f64) -> Self {
        let h_si = crate::constants::cosmic::HUBBLE_CONSTANT_SI;
        let omega_m = crate::constants::cosmic::OMEGA_MATTER;
        let omega_l = crate::constants::cosmic::OMEGA_LAMBDA;
        let hz = h_si * (omega_m * (1.0 + redshift).powi(3) + omega_l).sqrt();
        let rho_crit = 3.0 * hz * hz / (8.0 * PI * G);
        let delta_vir = 200.0;
        let r_vir = (3.0 * virial_mass / (4.0 * PI * delta_vir * rho_crit)).powf(1.0 / 3.0);
        let r_s = r_vir / concentration;
        let c = concentration;
        let rho_s = delta_vir * rho_crit * c * c * c / (3.0 * ((1.0 + c).ln() - c / (1.0 + c)));
        Self {
            virial_mass,
            virial_radius: r_vir,
            concentration,
            scale_radius: r_s,
            rho_s,
        }
    }

    pub fn density_at(&self, r: f64) -> f64 {
        nfw(r, self.rho_s, self.scale_radius)
    }

    pub fn enclosed_mass_at(&self, r: f64) -> f64 {
        nfw_enclosed_mass(r, self.rho_s, self.scale_radius)
    }

    pub fn circular_velocity_at(&self, r: f64) -> f64 {
        (G * self.enclosed_mass_at(r) / r).sqrt()
    }

    pub fn potential_at(&self, r: f64) -> f64 {
        nfw_potential(r, self.rho_s, self.scale_radius)
    }

    pub fn v_max(&self) -> f64 {
        let r_max = 2.163 * self.scale_radius;
        self.circular_velocity_at(r_max)
    }
}