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 settle_analytic(mol: &mut WaterMol, dt: f32, cell: &SimBox, virial: &mut Virial) {
let dt_inv = 1.0 / dt;
let r0_o = mol.o.posit;
let r0_h0 = mol.o.posit + cell.min_image(mol.h0.posit - mol.o.posit);
let r0_h1 = mol.o.posit + cell.min_image(mol.h1.posit - mol.o.posit);
let r_o = r0_o + mol.o.vel * dt;
let r_h0 = r0_h0 + mol.h0.vel * dt;
let r_h1 = r0_h1 + mol.h1.vel * dt;
let com = (r_o * O_MASS + r_h0 * H_MASS + r_h1 * H_MASS) / MASS_WATER_MOL;
let d_o = r_o - com;
let d_h0 = r_h0 - com;
let d_h1 = r_h1 - com;
let mut ax = d_h1 - d_h0;
let az = d_o.cross(ax);
let mut ay = az.cross(ax);
ax = ax.to_normalized();
ay = ay.to_normalized();
let x_h0_new = -RB;
let x_h1_new = RB;
let y_o_new = RC;
let y_h_new = -(RA - RC);
let final_o = com + (ay * y_o_new);
let final_h0 = com + (ax * x_h0_new) + (ay * y_h_new);
let final_h1 = com + (ax * x_h1_new) + (ay * y_h_new);
mol.o.posit = cell.wrap(final_o);
mol.h0.posit = mol.o.posit + cell.min_image(final_h0 - final_o);
mol.h1.posit = mol.o.posit + cell.min_image(final_h1 - final_o);
let v_o_new = (final_o - r0_o) * dt_inv;
let v_h0_new = (final_h0 - r0_h0) * dt_inv;
let v_h1_new = (final_h1 - r0_h1) * dt_inv;
mol.o.vel = v_o_new;
mol.h0.vel = v_h0_new;
mol.h1.vel = v_h1_new;
let fc_o = (final_o - r_o) * (O_MASS * dt_inv * dt_inv);
let fc_h0 = (final_h0 - r_h0) * (H_MASS * dt_inv * dt_inv);
let fc_h1 = (final_h1 - r_h1) * (H_MASS * dt_inv * dt_inv);
let fc_h0 = (final_h0 - r_h0) * (H_MASS * dt_inv * dt_inv);
let fc_h1 = (final_h1 - r_h1) * (H_MASS * dt_inv * dt_inv);
let r_o_h0 = final_h0 - final_o;
let r_o_h1 = final_h1 - final_o;
virial.constraints += (r_o_h0.dot(fc_h0) + r_o_h1.dot(fc_h1)) as f64;
mol.update_virtual_site();
}
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();
}
pub(crate) fn settle_drift(mol: &mut WaterMol, dt: f32, cell: &SimBox, virial_constr: &mut f64) {
let params = &SETTLE_PARAMS;
let inv_dt = 1.0 / dt;
let x_o = mol.o.posit - mol.o.vel * dt;
let x_h1 = mol.h0.posit - mol.h0.vel * dt;
let x_h2 = mol.h1.posit - mol.h1.vel * dt;
let xp_o = mol.o.posit;
let doh2 = cell.min_image(mol.h0.posit - xp_o);
let doh3 = cell.min_image(mol.h1.posit - xp_o);
let dist21 = cell.min_image(x_h1 - x_o);
let dist31 = cell.min_image(x_h2 - x_o);
let a1 = (doh2 + doh3) * -params.wh;
let b1 = doh2 + a1;
let c1 = doh3 + a1;
let xakszd = dist21.y * dist31.z - dist21.z * dist31.y;
let yakszd = dist21.z * dist31.x - dist21.x * dist31.z;
let zakszd = dist21.x * dist31.y - dist21.y * dist31.x;
let xaksxd = a1.y * zakszd - a1.z * yakszd;
let yaksxd = a1.z * xakszd - a1.x * zakszd;
let zaksxd = a1.x * yakszd - a1.y * xakszd;
let xaksyd = yakszd * zaksxd - zakszd * yaksxd;
let yaksyd = zakszd * xaksxd - xakszd * zaksxd;
let zaksyd = xakszd * yaksxd - yakszd * xaksxd;
let axlng = 1.0 / (xaksxd * xaksxd + yaksxd * yaksxd + zaksxd * zaksxd).sqrt();
let aylng = 1.0 / (xaksyd * xaksyd + yaksyd * yaksyd + zaksyd * zaksyd).sqrt();
let azlng = 1.0 / (xakszd * xakszd + yakszd * yakszd + zakszd * zakszd).sqrt();
let trns1 = Vec3::new(xaksxd * axlng, yaksxd * axlng, zaksxd * axlng);
let trns2 = Vec3::new(xaksyd * aylng, yaksyd * aylng, zaksyd * aylng);
let trns3 = Vec3::new(xakszd * azlng, yakszd * azlng, zakszd * azlng);
let b0d_x = trns1.dot(dist21);
let b0d_y = trns2.dot(dist21); let c0d_x = trns1.dot(dist31);
let c0d_y = trns2.dot(dist31);
let a1d_z = trns3.dot(a1);
let b1d_z = trns3.dot(b1);
let c1d_z = trns3.dot(c1);
let sinphi = a1d_z / params.ra;
let tmp2 = 1.0 - sinphi * sinphi;
if tmp2 <= 1e-12 {
eprintln!("SETTLE Error: Water molecule distorted.");
return;
}
let cosphi = tmp2.sqrt();
let sinpsi = (b1d_z - c1d_z) * params.irc2 / cosphi;
let cospsi = (1.0 - sinpsi * sinpsi).sqrt();
let a2d_y = params.ra * cosphi;
let b2d_x = -params.rc * cospsi;
let t1 = -params.rb * cosphi;
let t2 = params.rc * sinpsi * sinphi;
let b2d_y = t1 - t2;
let c2d_y = t1 + t2;
let alpha = b2d_x * (b0d_x - c0d_x) + b0d_y * b2d_y + c0d_y * c2d_y;
let beta = b2d_x * (c0d_y - b0d_y) + b0d_x * b2d_y + c0d_x * c2d_y;
let gamma = b0d_x * trns2.dot(b1) - trns1.dot(b1) * b0d_y + c0d_x * trns2.dot(c1)
- trns1.dot(c1) * c0d_y;
let al2be2 = alpha * alpha + beta * beta;
let tmp_sq = al2be2 - gamma * gamma;
let sinthe = (alpha * gamma - beta * tmp_sq.sqrt()) / al2be2;
let costhe = (1.0 - sinthe * sinthe).sqrt();
let a3d_x = -a2d_y * sinthe;
let a3d_y = a2d_y * costhe;
let a3d_z = a1d_z;
let b3d_x = b2d_x * costhe - b2d_y * sinthe;
let b3d_y = b2d_x * sinthe + b2d_y * costhe;
let b3d_z = b1d_z;
let c3d_x = -b2d_x * costhe - c2d_y * sinthe;
let c3d_y = -b2d_x * sinthe + c2d_y * costhe;
let c3d_z = c1d_z;
let a3 = trns1 * a3d_x + trns2 * a3d_y + trns3 * a3d_z;
let b3 = trns1 * b3d_x + trns2 * b3d_y + trns3 * b3d_z;
let c3 = trns1 * c3d_x + trns2 * c3d_y + trns3 * c3d_z;
let dx_o = a3 - a1;
let dx_h1 = b3 - b1;
let dx_h2 = c3 - c1;
mol.o.posit += dx_o;
mol.h0.posit += dx_h1;
mol.h1.posit += dx_h2;
mol.o.vel += dx_o * inv_dt;
mol.h0.vel += dx_h1 * inv_dt;
mol.h1.vel += dx_h2 * inv_dt;
let f_o = dx_o * (O_MASS * inv_dt * inv_dt);
let f_h1 = dx_h1 * (H_MASS * inv_dt * inv_dt);
let f_h2 = dx_h2 * (H_MASS * inv_dt * inv_dt);
*virial_constr +=
(mol.o.posit.dot(f_o) + mol.h0.posit.dot(f_h1) + mol.h1.posit.dot(f_h2)) as f64;
mol.update_virtual_site();
}