use crate::dynamics_live::state::DynamicAtom;
#[derive(Debug, Clone)]
pub struct LangevinConfig {
pub target_temp_k: f64,
pub gamma: f64,
}
impl Default for LangevinConfig {
fn default() -> Self {
Self {
target_temp_k: 300.0,
gamma: 0.001,
}
}
}
const KB_EV: f64 = 8.617_333_262e-5;
pub fn langevin_o_step(
atoms: &mut [DynamicAtom],
config: &LangevinConfig,
dt_fs: f64,
rng_seed: u64,
) {
let gamma = config.gamma;
let kt = KB_EV * config.target_temp_k;
let c1 = (-gamma * dt_fs).exp();
let mut state = rng_seed.wrapping_add(1);
for atom in atoms.iter_mut() {
let c2 = ((1.0 - c1 * c1) * kt / atom.mass).sqrt();
for d in 0..3 {
let r = box_muller_normal(&mut state);
atom.velocity[d] = c1 * atom.velocity[d] + c2 * r;
}
}
}
pub fn langevin_baoa_step(
atoms: &mut [DynamicAtom],
config: &LangevinConfig,
dt_fs: f64,
rng_seed: u64,
) {
let half_dt = 0.5 * dt_fs;
let accel_factor = 0.009_648_533;
for atom in atoms.iter_mut() {
let inv_m = accel_factor / atom.mass;
for d in 0..3 {
atom.velocity[d] += half_dt * atom.force[d] * inv_m;
}
}
for atom in atoms.iter_mut() {
for d in 0..3 {
atom.position[d] += half_dt * atom.velocity[d];
}
}
langevin_o_step(atoms, config, dt_fs, rng_seed);
for atom in atoms.iter_mut() {
for d in 0..3 {
atom.position[d] += half_dt * atom.velocity[d];
}
}
}
pub fn langevin_b_step(atoms: &mut [DynamicAtom], dt_fs: f64) {
let half_dt = 0.5 * dt_fs;
let accel_factor = 0.009_648_533;
for atom in atoms.iter_mut() {
let inv_m = accel_factor / atom.mass;
for d in 0..3 {
atom.velocity[d] += half_dt * atom.force[d] * inv_m;
}
}
}
fn box_muller_normal(state: &mut u64) -> f64 {
let u1 = xorshift64_uniform(state);
let u2 = xorshift64_uniform(state);
(-2.0 * u1.ln()).sqrt() * (2.0 * std::f64::consts::PI * u2).cos()
}
fn xorshift64_uniform(state: &mut u64) -> f64 {
let mut x = *state;
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
*state = x;
(x as f64) / (u64::MAX as f64)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::dynamics_live::state::DynamicAtom;
fn hydrogen_atom() -> DynamicAtom {
DynamicAtom {
element: 1,
mass: 1.008,
charge: 0.0,
position: [0.0, 0.0, 0.0],
velocity: [0.01, -0.01, 0.005],
force: [0.0, 0.0, 0.0],
}
}
#[test]
fn test_langevin_o_step_thermalizes() {
let mut atoms = vec![hydrogen_atom(); 100];
let config = LangevinConfig {
target_temp_k: 300.0,
gamma: 0.01,
};
for step in 0..1000 {
langevin_o_step(&mut atoms, &config, 1.0, step);
}
let v_sq_sum: f64 = atoms
.iter()
.map(|a| a.velocity[0].powi(2) + a.velocity[1].powi(2) + a.velocity[2].powi(2))
.sum();
assert!(v_sq_sum > 0.0);
}
}