darkmatter 0.0.2

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

pub fn deflection_angle_point_mass(m: f64, b: f64) -> f64 {
    4.0 * G * m / (C * C * b)
}

pub fn einstein_radius_rad(m_lens: f64, d_l: f64, d_s: f64) -> f64 {
    let d_ls = d_s - d_l;
    if d_ls <= 0.0 || d_l <= 0.0 || d_s <= 0.0 {
        return 0.0;
    }
    (4.0 * G * m_lens * d_ls / (C * C * d_l * d_s)).sqrt()
}

pub fn magnification_point_mass(u: f64) -> f64 {
    let u2 = u * u;
    (u2 + 2.0) / (u * (u2 + 4.0).sqrt())
}

pub fn convergence_from_surface_density(sigma: f64, d_l: f64, d_s: f64) -> f64 {
    let d_ls = d_s - d_l;
    let sigma_cr = critical_surface_density(d_l, d_s, d_ls);
    sigma / sigma_cr
}

pub fn critical_surface_density(d_l: f64, d_s: f64, d_ls: f64) -> f64 {
    C * C * d_s / (4.0 * PI * G * d_l * d_ls)
}

pub fn shear_from_nfw(rho_s: f64, r_s: f64, d_l: f64, d_s: f64, r_projected: f64) -> f64 {
    let sigma_cr = critical_surface_density(d_l, d_s, d_s - d_l);
    let x = r_projected / r_s;
    let sigma_nfw = nfw_projected_surface_density(rho_s, r_s, x);
    let mean_sigma = nfw_mean_projected_density(rho_s, r_s, x);
    (mean_sigma - sigma_nfw) / sigma_cr
}

fn nfw_projected_surface_density(rho_s: f64, r_s: f64, x: f64) -> f64 {
    if x < 1e-10 {
        return 0.0;
    }
    let f = if (x - 1.0).abs() < 1e-6 {
        1.0 / 3.0
    } else if x < 1.0 {
        let term = (1.0 - x * x).sqrt();
        1.0 / (x * x - 1.0) * (1.0 - (1.0 / term) * ((1.0 - term) / x).abs().ln().cosh().recip())
    } else {
        let term = (x * x - 1.0).sqrt();
        1.0 / (x * x - 1.0) * (1.0 - term.atan() / term)
    };
    2.0 * rho_s * r_s * f
}

fn nfw_mean_projected_density(rho_s: f64, r_s: f64, x: f64) -> f64 {
    if x < 1e-10 {
        return 0.0;
    }
    let g = if (x - 1.0).abs() < 1e-6 {
        1.0 + (1.0_f64).ln()
    } else if x < 1.0 {
        let sqrt_term = (1.0 - x * x).abs().sqrt();
        ((x / 2.0).abs().ln() + sqrt_term.atanh()) / (x * x)
    } else {
        let sqrt_term = (x * x - 1.0).sqrt();
        (sqrt_term.atan() + (x / 2.0).abs().ln()) / (x * x)
    };
    4.0 * rho_s * r_s * g
}

pub fn time_delay(
    theta_1: f64,
    theta_2: f64,
    beta: f64,
    d_l: f64,
    d_s: f64,
    d_ls: f64,
    z_l: f64,
) -> f64 {
    let d_dt = (1.0 + z_l) * d_l * d_s / (d_ls * C);
    let phi1 = 0.5 * (theta_1 - beta).powi(2);
    let phi2 = 0.5 * (theta_2 - beta).powi(2);
    d_dt * (phi1 - phi2)
}

pub fn strong_lensing_mass_within_einstein(theta_e: f64, d_l: f64, d_s: f64, d_ls: f64) -> f64 {
    let sigma_cr = critical_surface_density(d_l, d_s, d_ls);
    PI * (theta_e * d_l).powi(2) * sigma_cr
}

pub fn weak_lensing_tangential_shear(delta_sigma: f64, sigma_cr: f64) -> f64 {
    delta_sigma / sigma_cr
}

pub fn flexion_from_dm_substructure(m_sub: f64, d_l: f64, d_s: f64, r: f64) -> f64 {
    let d_ls = d_s - d_l;
    let sigma_cr = critical_surface_density(d_l, d_s, d_ls);
    4.0 * G * m_sub / (C * C * r * r * sigma_cr)
}

pub fn magnification_point_mass_finite_source(u: f64, rho_star: f64) -> f64 {
    if rho_star < 1e-10 {
        return magnification_point_mass(u);
    }
    let u2 = u * u;
    let rho2 = rho_star * rho_star;
    let z = (u2 + 2.0) / (u * (u2 + 4.0).sqrt());
    let correction = 1.0 + rho2 / (2.0 * (u2 + 2.0));
    z * correction
}

pub fn microlensing_light_curve(t: f64, t0: f64, u_min: f64, t_e: f64) -> f64 {
    let tau = (t - t0) / t_e;
    let u = (u_min * u_min + tau * tau).sqrt();
    magnification_point_mass(u)
}

pub fn microlensing_einstein_crossing_time_days(
    m_lens_solar: f64,
    d_l_kpc: f64,
    d_s_kpc: f64,
    v_transverse_km_s: f64,
) -> f64 {
    let m_kg = m_lens_solar * 1.989e30;
    let d_l = d_l_kpc * 3.0857e19;
    let d_s = d_s_kpc * 3.0857e19;
    let theta_e = einstein_radius_rad(m_kg, d_l, d_s);
    let r_e = theta_e * d_l;
    r_e / (v_transverse_km_s * 1e3) / 86400.0
}

pub fn solve_sis_images(theta_s: f64, theta_e: f64) -> (f64, f64) {
    let theta_plus = theta_s + theta_e;
    let theta_minus = theta_s - theta_e;
    (theta_plus, theta_minus)
}

pub fn sis_magnification(theta: f64, theta_e: f64) -> f64 {
    if theta.abs() < 1e-15 {
        return f64::INFINITY;
    }
    theta / (theta - theta_e)
}

pub fn caustic_radius_sis(theta_e: f64) -> f64 {
    theta_e
}

pub fn nfw_convergence(rho_s: f64, r_s: f64, d_l: f64, d_s: f64, r_projected: f64) -> f64 {
    let d_ls = d_s - d_l;
    let sigma_cr = critical_surface_density(d_l, d_s, d_ls);
    let x = r_projected / r_s;
    nfw_projected_surface_density(rho_s, r_s, x) / sigma_cr
}

pub fn tangential_critical_curve_radius_nfw(rho_s: f64, r_s: f64, d_l: f64, d_s: f64) -> f64 {
    let d_ls = d_s - d_l;
    let sigma_cr = critical_surface_density(d_l, d_s, d_ls);
    let kappa_s = rho_s * r_s / sigma_cr;
    let mut x_lo = 0.01_f64;
    let mut x_hi = (10.0 * kappa_s.sqrt()).max(10.0);
    for _ in 0..60 {
        let x_mid = 0.5 * (x_lo + x_hi);
        let kappa = nfw_projected_surface_density(rho_s, r_s, x_mid) / sigma_cr;
        let mean_kappa = nfw_mean_projected_density(rho_s, r_s, x_mid) / sigma_cr;
        let det = (1.0 - kappa) * (1.0 - 2.0 * mean_kappa + kappa);
        if det > 0.0 {
            x_hi = x_mid;
        } else {
            x_lo = x_mid;
        }
    }
    0.5 * (x_lo + x_hi) * r_s
}

pub fn galaxy_galaxy_lensing_delta_sigma(
    m_halo_solar: f64,
    concentration: f64,
    r_projected_kpc: f64,
) -> f64 {
    let r_s_kpc = virial_radius_from_mass(m_halo_solar) / concentration;
    let x = r_projected_kpc / r_s_kpc;
    let rho_s = m_halo_solar * 1.989e30
        / (4.0
            * PI
            * r_s_kpc.powi(3)
            * 3.0857e19_f64.powi(3)
            * ((1.0 + concentration).ln() - concentration / (1.0 + concentration)));
    let sigma = nfw_projected_surface_density(rho_s, r_s_kpc * 3.0857e19, x);
    let mean_sigma = nfw_mean_projected_density(rho_s, r_s_kpc * 3.0857e19, x);
    mean_sigma - sigma
}

fn virial_radius_from_mass(m_solar: f64) -> f64 {
    let m = m_solar * 1.989e30;
    let rho_crit = 9.47e-27;
    (3.0 * m / (4.0 * PI * 200.0 * rho_crit)).powf(1.0 / 3.0) / 3.0857e19
}

pub fn reduced_shear(gamma: f64, kappa: f64) -> f64 {
    if (1.0 - kappa).abs() < 1e-15 {
        return f64::INFINITY;
    }
    gamma / (1.0 - kappa)
}

pub fn magnification_from_convergence_shear(kappa: f64, gamma: f64) -> f64 {
    let det = (1.0 - kappa).powi(2) - gamma * gamma;
    if det.abs() < 1e-30 {
        return f64::INFINITY;
    }
    1.0 / det.abs()
}

pub fn optical_depth_microlensing(f_dm: f64, d_s_kpc: f64, rho_dm_avg_kg_m3: f64) -> f64 {
    let d_s = d_s_kpc * 3.0857e19;
    4.0 * PI * G * rho_dm_avg_kg_m3 * d_s * d_s * f_dm / (3.0 * C * C)
}