sciforge 0.0.3

A comprehensive scientific computing library in pure Rust with zero dependencies
Documentation
pub fn lennard_jones(r: f64, epsilon: f64, sigma: f64) -> f64 {
    let sr6 = (sigma / r).powi(6);
    4.0 * epsilon * (sr6 * sr6 - sr6)
}

pub fn lennard_jones_force(r: f64, epsilon: f64, sigma: f64) -> f64 {
    let sr = sigma / r;
    let sr6 = sr.powi(6);
    24.0 * epsilon / r * (2.0 * sr6 * sr6 - sr6)
}

pub fn coulomb_interaction(q1: f64, q2: f64, r: f64, epsilon_r: f64) -> f64 {
    let k = 8.9875e9;
    k * q1 * q2 / (epsilon_r * r * r)
}

pub fn debye_huckel(q: f64, r: f64, kappa: f64, epsilon_r: f64) -> f64 {
    let k = 8.9875e9;
    (k * q / (epsilon_r * r)) * (-kappa * r).exp()
}

pub fn verlet_step(
    positions: &mut [f64],
    velocities: &mut [f64],
    forces: &[f64],
    masses: &[f64],
    dt: f64,
) {
    let n = positions.len();
    for i in 0..n {
        let a = forces[i] / masses[i];
        positions[i] += velocities[i] * dt + 0.5 * a * dt * dt;
        velocities[i] += a * dt;
    }
}

pub fn kinetic_energy(velocities: &[f64], masses: &[f64]) -> f64 {
    let n = velocities.len().min(masses.len());
    let mut ke = 0.0;
    for i in 0..n {
        ke += 0.5 * masses[i] * velocities[i] * velocities[i];
    }
    ke
}

pub fn temperature_from_ke(ke: f64, n_particles: usize, n_dim: usize) -> f64 {
    use crate::constants::K_B;
    let dof = (n_particles * n_dim) as f64;
    2.0 * ke / (dof * K_B)
}

pub fn morse_potential(r: f64, d_e: f64, a: f64, r_e: f64) -> f64 {
    let x = 1.0 - (-a * (r - r_e)).exp();
    d_e * x * x
}

pub fn harmonic_bond(r: f64, k: f64, r0: f64) -> f64 {
    0.5 * k * (r - r0).powi(2)
}

pub fn harmonic_angle(theta: f64, k: f64, theta0: f64) -> f64 {
    0.5 * k * (theta - theta0).powi(2)
}

pub fn dihedral_potential(phi: f64, k: f64, n: f64, delta: f64) -> f64 {
    k * (1.0 + (n * phi - delta).cos())
}

pub fn velocity_verlet_step(
    positions: &mut [f64],
    velocities: &mut [f64],
    forces_old: &[f64],
    forces_new: &[f64],
    masses: &[f64],
    dt: f64,
) {
    let n = positions.len().min(velocities.len()).min(masses.len());
    for i in 0..n {
        let a_old = forces_old[i] / masses[i];
        positions[i] += velocities[i] * dt + 0.5 * a_old * dt * dt;
        let a_new = forces_new[i] / masses[i];
        velocities[i] += 0.5 * (a_old + a_new) * dt;
    }
}

pub fn berendsen_thermostat(
    velocities: &mut [f64],
    current_temp: f64,
    target_temp: f64,
    tau: f64,
    dt: f64,
) {
    if current_temp < 1e-30 {
        return;
    }
    let lambda = (1.0 + dt / tau * (target_temp / current_temp - 1.0)).sqrt();
    for v in velocities.iter_mut() {
        *v *= lambda;
    }
}

pub fn nose_hoover_friction(ke: f64, target_ke: f64, q: f64) -> f64 {
    (ke - target_ke) / q
}

pub fn switching_function(r: f64, r_on: f64, r_off: f64) -> f64 {
    if r <= r_on {
        return 1.0;
    }
    if r >= r_off {
        return 0.0;
    }
    let x = (r_off * r_off - r * r) / (r_off * r_off - r_on * r_on);
    x * x * (3.0 - 2.0 * x)
}

pub fn pair_correlation_bin(
    distances: &[f64],
    r_min: f64,
    r_max: f64,
    n_particles: usize,
    volume: f64,
) -> f64 {
    let count = distances
        .iter()
        .filter(|&&d| d >= r_min && d < r_max)
        .count() as f64;
    let shell_vol = 4.0 / 3.0 * std::f64::consts::PI * (r_max.powi(3) - r_min.powi(3));
    let density = n_particles as f64 / volume;
    count / (n_particles as f64 * shell_vol * density)
}

pub fn pressure_virial(n: usize, volume: f64, temperature: f64, virial_sum: f64) -> f64 {
    use crate::constants::K_B;
    let nkbt = n as f64 * K_B * temperature;
    (nkbt + virial_sum / 3.0) / volume
}

pub fn mean_free_path(density: f64, cross_section: f64) -> f64 {
    1.0 / (density * cross_section).max(1e-30)
}

pub fn born_mayer_repulsion(a: f64, b: f64, r: f64) -> f64 {
    a * (-b * r).exp()
}

pub fn buckingham_potential(a: f64, b: f64, c: f64, r: f64) -> f64 {
    a * (-b * r).exp() - c / r.powi(6)
}