use lin_alg::f32::Vec3;
use crate::{
barostat::{SimBox, Virial},
solvent::{H_MASS, H_O_H_θ, MASS_WATER_MOL, O_EP_R, O_H_R, O_MASS, WaterMol},
};
pub(crate) const RESET_ANGLE_RATIO: usize = 1_000;
pub(crate) const RA: f32 = 0.5395199719801114; const RB: f32 = 0.6856075890450577; const RC: f32 = RA * (2.0 * H_MASS) / (O_MASS + 2.0 * H_MASS);
fn rodrigues_rotate(r: Vec3, omega: Vec3, dt: f32) -> Vec3 {
let omega_dt = omega * dt;
let theta = omega_dt.magnitude();
if theta < 1e-12 {
let wxr = omega_dt.cross(r);
return r + wxr + omega_dt.cross(wxr) * 0.5;
}
let n = omega_dt / theta; let c = theta.cos();
let s = theta.sin();
r * c + n.cross(r) * s + n * (n.dot(r)) * (1.0 - c)
}
fn solve_symmetric3(ixx: f32, iyy: f32, izz: f32, ixy: f32, ixz: f32, iyz: f32, b: Vec3) -> Vec3 {
let det = ixx * (iyy * izz - iyz * iyz) - ixy * (ixy * izz - iyz * ixz)
+ ixz * (ixy * iyz - iyy * ixz);
const TOL: f32 = 1.0e-12;
if det.abs() < TOL {
return Vec3::new_zero();
}
let inv_det = 1.0 / det;
let inv00 = (iyy * izz - iyz * iyz) * inv_det;
let inv01 = (ixz * iyz - ixy * izz) * inv_det;
let inv02 = (ixy * iyz - ixz * iyy) * inv_det;
let inv11 = (ixx * izz - ixz * ixz) * inv_det;
let inv12 = (ixz * ixy - ixx * iyz) * inv_det;
let inv22 = (ixx * iyy - ixy * ixy) * inv_det;
Vec3::new(
inv00 * b.x + inv01 * b.y + inv02 * b.z,
inv01 * b.x + inv11 * b.y + inv12 * b.z,
inv02 * b.x + inv12 * b.y + inv22 * b.z,
)
}
pub(crate) fn integrate_rigid_water(mol: &mut WaterMol, dt: f32, cell: &SimBox) -> f64 {
let o_pos = mol.o.posit;
let h0_pos_local = o_pos + cell.min_image(mol.h0.posit - o_pos);
let h1_pos_local = o_pos + cell.min_image(mol.h1.posit - o_pos);
let r_com =
(mol.o.posit * O_MASS + h0_pos_local * H_MASS + h1_pos_local * H_MASS) / MASS_WATER_MOL;
let v_com = (mol.o.vel * O_MASS + mol.h0.vel * H_MASS + mol.h1.vel * H_MASS) / MASS_WATER_MOL;
let (rO, rH0, rH1) = (o_pos - r_com, h0_pos_local - r_com, h1_pos_local - r_com);
let (vO, vH0, vH1) = (mol.o.vel - v_com, mol.h0.vel - v_com, mol.h1.vel - v_com);
let L = rO.cross(vO) * O_MASS + rH0.cross(vH0) * H_MASS + rH1.cross(vH1) * H_MASS;
let accI = |r: Vec3, m: f32| {
let x = r.x;
let y = r.y;
let z = r.z;
let r2 = r.dot(r);
(
m * (r2 - x * x),
m * (r2 - y * y),
m * (r2 - z * z),
-m * x * y,
-m * x * z,
-m * y * z,
)
};
let (iOxx, iOyy, iOzz, iOxy, iOxz, iOyz) = accI(rO, O_MASS);
let (iH0x, iH0y, iH0z, iH0xy, iH0xz, iH0yz) = accI(rH0, H_MASS);
let (iH1x, iH1y, iH1z, iH1xy, iH1xz, iH1yz) = accI(rH1, H_MASS);
let (ixx, iyy, izz, ixy, ixz, iyz) = (
iOxx + iH0x + iH1x,
iOyy + iH0y + iH1y,
iOzz + iH0z + iH1z,
iOxy + iH0xy + iH1xy,
iOxz + iH0xz + iH1xz,
iOyz + iH0yz + iH1yz,
);
let ω = solve_symmetric3(ixx, iyy, izz, ixy, ixz, iyz, L);
let Δ = v_com * dt;
let rO2 = rodrigues_rotate(rO, ω, dt);
let rH02 = rodrigues_rotate(rH0, ω, dt);
let rH12 = rodrigues_rotate(rH1, ω, dt);
let new_o = r_com + Δ + rO2;
let new_h0 = r_com + Δ + rH02;
let new_h1 = r_com + Δ + rH12;
mol.o.posit = cell.wrap(new_o);
mol.h0.posit = mol.o.posit + cell.min_image(new_h0 - mol.o.posit);
mol.h1.posit = mol.o.posit + cell.min_image(new_h1 - mol.o.posit);
let vO2 = ω.cross(rO2);
let vH02 = ω.cross(rH02);
let vH12 = ω.cross(rH12);
mol.o.vel = v_com + vO2;
mol.h0.vel = v_com + vH02;
mol.h1.vel = v_com + vH12;
{
let bisector = (mol.h0.posit - mol.o.posit) + (mol.h1.posit - mol.o.posit);
mol.m.posit = mol.o.posit + bisector.to_normalized() * O_EP_R;
mol.m.vel = (mol.h0.vel + mol.h1.vel) * 0.5;
}
let dv_h0 = vH02 - vH0;
let dv_h1 = vH12 - vH1;
let r_oh0 = rH02 - rO2; let r_oh1 = rH12 - rO2; (r_oh0.dot(dv_h0 * (H_MASS / dt)) + r_oh1.dot(dv_h1 * (H_MASS / dt))) as f64
}
pub(crate) fn reset_angle(mol: &mut WaterMol, cell: &SimBox) {
let o_pos = mol.o.posit;
let h0_local = o_pos + cell.min_image(mol.h0.posit - o_pos);
let h1_local = o_pos + cell.min_image(mol.h1.posit - o_pos);
let u = (h0_local + h1_local - o_pos * 2.0).to_normalized();
let mut v = (h0_local - h1_local).to_normalized();
v = (v - u * u.dot(v)).to_normalized();
let c = H_O_H_θ * 0.5;
let new_h0 = o_pos + (u * c.cos() + v * c.sin()) * O_H_R;
let new_h1 = o_pos + (u * c.cos() - v * c.sin()) * O_H_R;
mol.h0.posit = mol.o.posit + cell.min_image(new_h0 - mol.o.posit);
mol.h1.posit = mol.o.posit + cell.min_image(new_h1 - mol.o.posit);
}
pub struct SettleParams {
pub wh: f32, pub ra: f32, pub rb: f32, pub rc: f32, pub irc2: f32, pub d_oh: f32, pub d_hh: f32, }
impl SettleParams {
pub fn new() -> Self {
let m_o = O_MASS;
let m_h = H_MASS;
let d_oh = O_H_R;
let d_hh = (2.0 * d_oh * d_oh * (1.0 - H_O_H_θ.cos())).sqrt();
let wohh = m_o + 2.0 * m_h;
let wh = m_h / wohh;
let rc = d_hh / 2.0;
let ra = 2.0 * m_h * (d_oh * d_oh - rc * rc).sqrt() / wohh;
let rb = (d_oh * d_oh - rc * rc).sqrt() - ra;
let irc2 = 1.0 / d_hh;
Self {
wh,
ra,
rb,
rc,
irc2,
d_oh,
d_hh,
}
}
}
lazy_static::lazy_static! {
pub static ref SETTLE_PARAMS: SettleParams = SettleParams::new();
}