1use lin_alg::{f32::Vec3, f64::Vec3 as Vec3F64};
4
5use crate::{
6 MdState,
7 solvent::{H_MASS, MASS_WATER_MOL, O_MASS},
8};
9
10const EPS: f64 = 1e-6;
11
12impl MdState {
13 fn dynamic_mass_and_momentum(&self) -> (f64, Vec3F64) {
14 let mut mass_sum = 0.0;
15 let mut p_sum = Vec3F64::new_zero();
16
17 for a in &self.atoms {
18 if a.static_ {
19 continue;
20 }
21 mass_sum += a.mass as f64;
22 let p: Vec3F64 = (a.vel * a.mass).into();
23 p_sum += p;
24 }
25
26 for w in &self.water {
27 mass_sum += MASS_WATER_MOL as f64;
28
29 let p_o: Vec3F64 = (w.o.vel * O_MASS).into();
30 let p_h0: Vec3F64 = (w.h0.vel * H_MASS).into();
31 let p_h1: Vec3F64 = (w.h1.vel * H_MASS).into();
32
33 p_sum += p_o + p_h0 + p_h1;
34 }
35
36 (mass_sum, p_sum)
37 }
38
39 fn shift_dynamic_positions(&mut self, displacement: Vec3) {
40 for a in &mut self.atoms {
41 if a.static_ {
42 continue;
43 }
44 a.posit += displacement;
45 }
46
47 for w in &mut self.water {
48 w.o.posit += displacement;
49 w.h0.posit += displacement;
50 w.h1.posit += displacement;
51 w.m.posit += displacement;
52 }
53 }
54
55 pub fn zero_linear_momentum(&mut self) {
58 let (mass_sum, p_sum) = self.dynamic_mass_and_momentum();
59
60 if mass_sum <= EPS {
61 return;
62 }
63
64 let vel_com: Vec3 = (p_sum / mass_sum).into();
65
66 for a in &mut self.atoms {
68 if a.static_ {
69 continue;
70 }
71 a.vel -= vel_com;
72 }
73
74 for w in &mut self.water {
77 w.o.vel -= vel_com;
78 w.h0.vel -= vel_com;
79 w.h1.vel -= vel_com;
80 }
81 }
82
83 pub(crate) fn zero_linear_momentum_acceleration_corrected(&mut self, interval_dt: f32) {
88 if interval_dt <= 0.0 {
89 self.zero_linear_momentum();
90 return;
91 }
92
93 let (mass_sum, p_sum) = self.dynamic_mass_and_momentum();
94 if mass_sum <= EPS {
95 return;
96 }
97
98 let vel_com: Vec3 = (p_sum / mass_sum).into();
99
100 self.shift_dynamic_positions(-(vel_com * (0.5 * interval_dt)));
103 self.zero_linear_momentum();
104 }
105
106 pub fn zero_angular_momentum(&mut self) {
110 let mut mass_sum = 0.0;
111 let mut m_r_sum = Vec3F64::new_zero();
112
113 for a in &self.atoms {
114 if a.static_ {
115 continue;
116 }
117 mass_sum += a.mass as f64;
118
119 let m_r: Vec3F64 = (a.posit * a.mass).into();
120 m_r_sum += m_r;
121 }
122
123 for w in &self.water {
124 mass_sum += MASS_WATER_MOL as f64;
125
126 let mr_o: Vec3F64 = (w.o.posit * O_MASS).into();
127 let mr_h0: Vec3F64 = (w.h0.posit * H_MASS).into();
128 let mr_h1: Vec3F64 = (w.h1.posit * H_MASS).into();
129
130 m_r_sum += mr_o + mr_h0 + mr_h1;
131 }
132
133 if mass_sum <= EPS {
134 return;
135 }
136 let rot_com: Vec3 = (m_r_sum / mass_sum).into();
137
138 let mut i_xx = 0.0;
140 let mut i_xy = 0.0;
141 let mut i_xz = 0.0;
142 let mut i_yy = 0.0;
143 let mut i_yz = 0.0;
144 let mut i_zz = 0.0;
145
146 let mut L = Vec3::new_zero();
147
148 for a in &self.atoms {
149 if a.static_ {
150 continue;
151 }
152 let m = a.mass;
153
154 let r = a.posit - rot_com;
155 let vxr = r.cross(a.vel);
156 L += vxr * m;
157
158 let rx = r.x;
159 let ry = r.y;
160 let rz = r.z;
161 let r2 = rx * rx + ry * ry + rz * rz;
162
163 i_xx += m * (r2 - rx * rx);
164 i_yy += m * (r2 - ry * ry);
165 i_zz += m * (r2 - rz * rz);
166 i_xy -= m * (rx * ry);
167 i_xz -= m * (rx * rz);
168 i_yz -= m * (ry * rz);
169 }
170
171 for w in &self.water {
172 for a in [&w.o, &w.h0, &w.h1] {
173 let m = a.mass;
174
175 let r = a.posit - rot_com;
176 let vxr = r.cross(a.vel);
177 L += vxr * m;
178
179 let rx = r.x;
180 let ry = r.y;
181 let rz = r.z;
182 let r2 = rx * rx + ry * ry + rz * rz;
183
184 i_xx += m * (r2 - rx * rx);
185 i_yy += m * (r2 - ry * ry);
186 i_zz += m * (r2 - rz * rz);
187 i_xy -= m * (rx * ry);
188 i_xz -= m * (rx * rz);
189 i_yz -= m * (ry * rz);
190 }
191 }
192
193 let I = [[i_xx, i_xy, i_xz], [i_xy, i_yy, i_yz], [i_xz, i_yz, i_zz]];
195
196 let eps = 1.0e-6f32;
198 let Ireg = [
199 [I[0][0] + eps, I[0][1], I[0][2]],
200 [I[1][0], I[1][1] + eps, I[1][2]],
201 [I[2][0], I[2][1], I[2][2] + eps],
202 ];
203
204 let det = Ireg[0][0] * (Ireg[1][1] * Ireg[2][2] - Ireg[1][2] * Ireg[2][1])
206 - Ireg[0][1] * (Ireg[1][0] * Ireg[2][2] - Ireg[1][2] * Ireg[2][0])
207 + Ireg[0][2] * (Ireg[1][0] * Ireg[2][1] - Ireg[1][1] * Ireg[2][0]);
208
209 if det.abs() < 1e-12 {
210 return;
211 }
212
213 let inv_det = 1.0 / det;
214 let inv = [
215 [
216 (Ireg[1][1] * Ireg[2][2] - Ireg[1][2] * Ireg[2][1]) * inv_det,
217 (Ireg[0][2] * Ireg[2][1] - Ireg[0][1] * Ireg[2][2]) * inv_det,
218 (Ireg[0][1] * Ireg[1][2] - Ireg[0][2] * Ireg[1][1]) * inv_det,
219 ],
220 [
221 (Ireg[1][2] * Ireg[2][0] - Ireg[1][0] * Ireg[2][2]) * inv_det,
222 (Ireg[0][0] * Ireg[2][2] - Ireg[0][2] * Ireg[2][0]) * inv_det,
223 (Ireg[0][2] * Ireg[1][0] - Ireg[0][0] * Ireg[1][2]) * inv_det,
224 ],
225 [
226 (Ireg[1][0] * Ireg[2][1] - Ireg[1][1] * Ireg[2][0]) * inv_det,
227 (Ireg[0][1] * Ireg[2][0] - Ireg[0][0] * Ireg[2][1]) * inv_det,
228 (Ireg[0][0] * Ireg[1][1] - Ireg[0][1] * Ireg[1][0]) * inv_det,
229 ],
230 ];
231
232 let omega = Vec3 {
233 x: inv[0][0] * L.x + inv[0][1] * L.y + inv[0][2] * L.z,
234 y: inv[1][0] * L.x + inv[1][1] * L.y + inv[1][2] * L.z,
235 z: inv[2][0] * L.x + inv[2][1] * L.y + inv[2][2] * L.z,
236 };
237
238 if !omega.x.is_finite() || !omega.y.is_finite() || !omega.z.is_finite() {
240 return;
241 }
242 if omega.magnitude_squared() < 1e-16 {
243 return;
244 }
245
246 for a in &mut self.atoms {
248 if a.static_ {
249 continue;
250 }
251 let r = a.posit - rot_com;
252 a.vel -= omega.cross(r);
253 }
254
255 for w in &mut self.water {
257 let r_o = w.o.posit - rot_com;
258 let r_h0 = w.h0.posit - rot_com;
259 let r_h1 = w.h1.posit - rot_com;
260
261 w.o.vel -= omega.cross(r_o);
262 w.h0.vel -= omega.cross(r_h0);
263 w.h1.vel -= omega.cross(r_h1);
264 }
265
266 self.zero_linear_momentum();
268 }
269}