Skip to main content

dynamics/
com_zero.rs

1//! Zero center of mass linear drift and rotation for all atoms in the system.
2
3use lin_alg::{f32::Vec3, f64::Vec3 as Vec3F64};
4
5use crate::{
6    MdState,
7    water::{H_MASS, MASS_WATER_MOL, O_MASS},
8};
9
10const EPS: f64 = 1e-6;
11
12impl MdState {
13    /// Remove center-of-mass drift. This can help stabilize system energy.
14    /// We perform the sums here as f64.
15    pub fn zero_linear_momentum(&mut self) {
16        let mut mass_sum = 0.0;
17        let mut p_sum = Vec3F64::new_zero(); // Σ m v
18
19        for a in &self.atoms {
20            mass_sum += a.mass as f64;
21            let p: Vec3F64 = (a.vel * a.mass).into();
22            p_sum += p;
23        }
24
25        for w in &self.water {
26            mass_sum += MASS_WATER_MOL as f64;
27
28            let p_o: Vec3F64 = (w.o.vel * O_MASS).into();
29            let p_h0: Vec3F64 = (w.h0.vel * H_MASS).into();
30            let p_h1: Vec3F64 = (w.h1.vel * H_MASS).into();
31
32            p_sum += p_o + p_h0 + p_h1;
33        }
34
35        if mass_sum <= EPS {
36            return;
37        }
38
39        let vel_com: Vec3 = (p_sum / mass_sum).into();
40
41        // Subtract uniformly so Σ m v' = 0
42        for a in &mut self.atoms {
43            a.vel -= vel_com;
44        }
45
46        // I don't think we need to use SHAKE/RATTLE here, as the velocity
47        // change is uniform for the water atoms in a given mol.
48        for w in &mut self.water {
49            w.o.vel -= vel_com;
50            w.h0.vel -= vel_com;
51            w.h1.vel -= vel_com;
52        }
53    }
54
55    // todo: Assess if you want this for multi-molecule systems.
56    /// Remove rigid-body rotation.
57    /// Computes ω from I ω = L about the atoms' COM, then sets v' = v - ω × (r - r_cm).
58    pub fn zero_angular_momentum(&mut self) {
59        let mut mass_sum = 0.0;
60        let mut m_r_sum = Vec3F64::new_zero();
61
62        for a in &self.atoms {
63            mass_sum += a.mass as f64;
64
65            let m_r: Vec3F64 = (a.posit * a.mass).into();
66            m_r_sum += m_r;
67        }
68
69        for w in &self.water {
70            mass_sum += MASS_WATER_MOL as f64;
71
72            let mr_o: Vec3F64 = (w.o.posit * O_MASS).into();
73            let mr_h0: Vec3F64 = (w.h0.posit * H_MASS).into();
74            let mr_h1: Vec3F64 = (w.h1.posit * H_MASS).into();
75
76            m_r_sum += mr_o + mr_h0 + mr_h1;
77        }
78
79        if mass_sum <= EPS {
80            return;
81        }
82        let rot_com: Vec3 = (m_r_sum / mass_sum).into();
83
84        // Build inertia tensor I and angular momentum L about r_cm
85        let mut i_xx = 0.0;
86        let mut i_xy = 0.0;
87        let mut i_xz = 0.0;
88        let mut i_yy = 0.0;
89        let mut i_yz = 0.0;
90        let mut i_zz = 0.0;
91
92        let mut L = Vec3::new_zero();
93
94        for a in &self.atoms {
95            let m = a.mass;
96
97            let r = a.posit - rot_com;
98            let vxr = r.cross(a.vel);
99            L += vxr * m;
100
101            let rx = r.x;
102            let ry = r.y;
103            let rz = r.z;
104            let r2 = rx * rx + ry * ry + rz * rz;
105
106            i_xx += m * (r2 - rx * rx);
107            i_yy += m * (r2 - ry * ry);
108            i_zz += m * (r2 - rz * rz);
109            i_xy -= m * (rx * ry);
110            i_xz -= m * (rx * rz);
111            i_yz -= m * (ry * rz);
112        }
113
114        for w in &self.water {
115            for a in [&w.o, &w.h0, &w.h1] {
116                let m = a.mass;
117
118                let r = a.posit - rot_com;
119                let vxr = r.cross(a.vel);
120                L += vxr * m;
121
122                let rx = r.x;
123                let ry = r.y;
124                let rz = r.z;
125                let r2 = rx * rx + ry * ry + rz * rz;
126
127                i_xx += m * (r2 - rx * rx);
128                i_yy += m * (r2 - ry * ry);
129                i_zz += m * (r2 - rz * rz);
130                i_xy -= m * (rx * ry);
131                i_xz -= m * (rx * rz);
132                i_yz -= m * (ry * rz);
133            }
134        }
135
136        // Inertia tensor (symmetric)
137        let I = [[i_xx, i_xy, i_xz], [i_xy, i_yy, i_yz], [i_xz, i_yz, i_zz]];
138
139        // Solve I * ω = L  (3x3). Add tiny Tikhonov for degeneracy.
140        let eps = 1.0e-6f32;
141        let Ireg = [
142            [I[0][0] + eps, I[0][1], I[0][2]],
143            [I[1][0], I[1][1] + eps, I[1][2]],
144            [I[2][0], I[2][1], I[2][2] + eps],
145        ];
146
147        // Inverse of 3x3 (cofactor / det)
148        let det = Ireg[0][0] * (Ireg[1][1] * Ireg[2][2] - Ireg[1][2] * Ireg[2][1])
149            - Ireg[0][1] * (Ireg[1][0] * Ireg[2][2] - Ireg[1][2] * Ireg[2][0])
150            + Ireg[0][2] * (Ireg[1][0] * Ireg[2][1] - Ireg[1][1] * Ireg[2][0]);
151
152        if det.abs() < 1e-12 {
153            return;
154        }
155
156        let inv_det = 1.0 / det;
157        let inv = [
158            [
159                (Ireg[1][1] * Ireg[2][2] - Ireg[1][2] * Ireg[2][1]) * inv_det,
160                (Ireg[0][2] * Ireg[2][1] - Ireg[0][1] * Ireg[2][2]) * inv_det,
161                (Ireg[0][1] * Ireg[1][2] - Ireg[0][2] * Ireg[1][1]) * inv_det,
162            ],
163            [
164                (Ireg[1][2] * Ireg[2][0] - Ireg[1][0] * Ireg[2][2]) * inv_det,
165                (Ireg[0][0] * Ireg[2][2] - Ireg[0][2] * Ireg[2][0]) * inv_det,
166                (Ireg[0][2] * Ireg[1][0] - Ireg[0][0] * Ireg[1][2]) * inv_det,
167            ],
168            [
169                (Ireg[1][0] * Ireg[2][1] - Ireg[1][1] * Ireg[2][0]) * inv_det,
170                (Ireg[0][1] * Ireg[2][0] - Ireg[0][0] * Ireg[2][1]) * inv_det,
171                (Ireg[0][0] * Ireg[1][1] - Ireg[0][1] * Ireg[1][0]) * inv_det,
172            ],
173        ];
174
175        let omega = Vec3 {
176            x: inv[0][0] * L.x + inv[0][1] * L.y + inv[0][2] * L.z,
177            y: inv[1][0] * L.x + inv[1][1] * L.y + inv[1][2] * L.z,
178            z: inv[2][0] * L.x + inv[2][1] * L.y + inv[2][2] * L.z,
179        };
180
181        // If ω is tiny, nothing to do
182        if !omega.x.is_finite() || !omega.y.is_finite() || !omega.z.is_finite() {
183            return;
184        }
185        if omega.magnitude_squared() < 1e-16 {
186            return;
187        }
188
189        // v' = v - ω × (r - r_cm)
190        for a in &mut self.atoms {
191            let r = a.posit - rot_com;
192            a.vel -= omega.cross(r);
193        }
194
195        // todo: Do we need to shake/rattle here? likely.
196        for w in &mut self.water {
197            let r_o = w.o.posit - rot_com;
198            let r_h0 = w.h0.posit - rot_com;
199            let r_h1 = w.h1.posit - rot_com;
200
201            w.o.vel -= omega.cross(r_o);
202            w.h0.vel -= omega.cross(r_h0);
203            w.h1.vel -= omega.cross(r_h1);
204        }
205
206        // Clean up any translation introduced by roundoff
207        self.zero_linear_momentum();
208    }
209}