OpenMD 3.2
Molecular Dynamics in the Open
Loading...
Searching...
No Matches
RPYBlocks.hpp
Go to the documentation of this file.
1/*
2 * Copyright (c) 2004-present, The University of Notre Dame. All rights
3 * reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * 3. Neither the name of the copyright holder nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 *
31 * SUPPORT OPEN SCIENCE! If you use OpenMD or its source code in your
32 * research, please cite the following paper when you publish your work:
33 *
34 * [1] Drisko et al., J. Open Source Softw. 9, 7004 (2024).
35 */
36
37/*! \file hydrodynamics/RPYBlocks.hpp
38 \brief Pairwise Rotne-Prager-Yamakawa mobility blocks for a bead model.
39
40 The six mobility blocks needed to embed a flexible atomic bead model in a
41 linearized background flow:
42
43 tt, tr, rt, rr force/torque -> translation/rotation (carry 1/eta)
44 td, rd ambient strain -> translation/rotation (eta- AND
45 pi-INDEPENDENT)
46
47 The grand velocity mobility M (the 6N x 6N assembly of tt/tr/rt/rr) is
48 inverted to the grand resistance R = M^{-1}; the drag on the beads is
49
50 f_hydro = R ( v_inf_eff - v ), v_inf_eff = v_inf + G : E_inf,
51
52 with v_inf the affine flow at the bead (VelocityField::getVelocity) and
53 (G:E_inf)_i = sum_{j!=i}[mu_td_ij:E ; mu_rd_ij:E] the dipolar disturbance.
54
55 Three regimes per pair, with boundaries:
56
57 far field R > a_i + a_j
58 partial overlap |a_i - a_j| < R <= a_i + a_j
59 full immersion R <= |a_i - a_j|
60
61 VISCOSITY: tt/tr/rt/rr carry 1/eta; td/rd are eta- AND
62 pi-independent, because the 1/(pi eta) of the stresslet propagator
63 cancels the (pi eta) of a sphere's stresslet response to
64 strain. The (5/6) a_j^3 / R^2 structure of the far-field td block
65 is exactly that cancellation.
66
67 VERIFIED against ZWMS (Zuk, Wajnryb, Mizerski, Szymczak, J. Fluid
68 Mech. 741, R5, 2014), Eqs. 3.1-3.9, in all three regimes, and
69 continuity across both regime boundaries confirmed
70 numerically. The full-immersion rt/tr prefactor (see rtPrefactor)
71 follows Eq. 3.5, R/(8 pi eta a_i^3). The td/rd tensors are defined
72 only up to their symmetric, traceless part, which is harmless here
73 since they are always contracted with the symmetric traceless E.
74*/
75
76#ifndef HYDRODYNAMICS_RPYBLOCKS_HPP
77#define HYDRODYNAMICS_RPYBLOCKS_HPP
78
79#include <cmath>
80
82#include "math/Vector3.hpp"
83#include "utils/Constants.hpp"
84
85namespace OpenMD::RPY {
86
87 enum Regime { FarField, PartialOverlap, FullImmersion };
88
89 //! Classify a pair by centre-to-centre distance R and radii a_i, a_j.
90 inline Regime classify(RealType R, RealType ai, RealType aj) {
91 if (R > ai + aj) return FarField;
92 if (R > std::fabs(ai - aj)) return PartialOverlap;
93 return FullImmersion;
94 }
95
96 //! cross-product (skew) matrix S(v) such that S(v) * w = v x w
97 inline Mat3x3d skew(const Vector3d& v) {
98 Mat3x3d S(0.0);
99 S(0, 1) = -v.z();
100 S(0, 2) = v.y();
101 S(1, 0) = v.z();
102 S(1, 2) = -v.x();
103 S(2, 0) = -v.y();
104 S(2, 1) = v.x();
105 return S;
106 }
107
108 //! Heaviside step used by the full-immersion rt/td branches: 1 if x>0 else 0.
109 inline RealType heavi(RealType x) { return (x > 0.0) ? 1.0 : 0.0; }
110
111 // ---- self blocks (i == j) --------------------------------------------- //
112
113 inline Mat3x3d muTT_self(RealType a, RealType eta) {
114 return (1.0 / (6.0 * Constants::PI * eta * a)) *
116 }
117 inline Mat3x3d muRR_self(RealType a, RealType eta) {
118 return (1.0 / (8.0 * Constants::PI * eta * a * a * a)) *
120 }
121 // mu_tr_self = mu_rt_self = 0; mu_td_self : E = mu_rd_self : E = 0.
122
123 // ---- off-diagonal blocks (rij = r_i - r_j, rhat = rij/|rij|) ----------- //
124
125 //! translation-translation, carries 1/eta
126 inline Mat3x3d muTT(const Vector3d& rij, RealType ai, RealType aj,
127 RealType eta) {
128 RealType R = rij.length();
129 Vector3d rh = rij / R;
130 RealType R2 = R * R, R3 = R2 * R;
132 Mat3x3d op = outProduct(rh, rh);
133 RealType eye = 0.0, hat = 0.0;
134
135 switch (classify(R, ai, aj)) {
136 case FarField: {
137 RealType c = 1.0 / (8.0 * Constants::PI * eta * R);
138 RealType asum = (ai * ai + aj * aj) / R2;
139 eye = c * (1.0 + asum / 3.0);
140 hat = c * (1.0 - asum);
141 break;
142 }
143 case PartialOverlap: {
144 RealType d2 = (ai - aj) * (ai - aj);
145 RealType invc = 1.0 / (6.0 * Constants::PI * eta * ai * aj);
146 eye = ((16.0 * R3 * (ai + aj) - (d2 + 3.0 * R2) * (d2 + 3.0 * R2)) /
147 (32.0 * R3)) *
148 invc;
149 hat = (3.0 * (d2 - R2) * (d2 - R2) / (32.0 * R3)) * invc;
150 break;
151 }
152 case FullImmersion: {
153 RealType asup = std::max(ai, aj);
154 eye = 1.0 / (6.0 * Constants::PI * eta * asup);
155 hat = 0.0;
156 break;
157 }
158 }
159 return eye * I + hat * op;
160 }
161
162 //! rotation-rotation, carries 1/eta
163 inline Mat3x3d muRR(const Vector3d& rij, RealType ai, RealType aj,
164 RealType eta) {
165 RealType R = rij.length();
166 Vector3d rh = rij / R;
167 RealType R2 = R * R, R3 = R2 * R, R4 = R2 * R2, R6 = R3 * R3;
169 Mat3x3d op = outProduct(rh, rh);
170 RealType eye = 0.0, hat = 0.0;
171
172 switch (classify(R, ai, aj)) {
173 case FarField: {
174 RealType c = 1.0 / (16.0 * Constants::PI * eta * R3);
175 eye = -c;
176 hat = 3.0 * c;
177 break;
178 }
179 case PartialOverlap: {
180 RealType d2 = (ai - aj) * (ai - aj);
181 RealType d4 = d2 * d2;
182 RealType calA = (5.0 * R6 - 27.0 * R4 * (ai * ai + aj * aj) +
183 32.0 * R3 * (ai * ai * ai + aj * aj * aj) -
184 9.0 * R2 * (ai * ai - aj * aj) * (ai * ai - aj * aj) -
185 d4 * (ai * ai + 4.0 * ai * aj + aj * aj)) /
186 (64.0 * R3);
187 RealType calB = (3.0 * (d2 - R2) * (d2 - R2) *
188 (ai * ai + 4.0 * ai * aj + aj * aj - R2)) /
189 (64.0 * R3);
190 RealType invc =
191 1.0 / (8.0 * Constants::PI * eta * ai * ai * ai * aj * aj * aj);
192 eye = calA * invc;
193 hat = calB * invc;
194 break;
195 }
196 case FullImmersion: {
197 RealType asup = std::max(ai, aj);
198 eye = 1.0 / (8.0 * Constants::PI * eta * asup * asup * asup);
199 hat = 0.0;
200 break;
201 }
202 }
203 return eye * I + hat * op;
204 }
205
206 //! rt prefactor pf such that mu_rt = -pf * skew(rhat) (Omega_i from F_j).
207 inline RealType rtPrefactor(RealType R, RealType ai, RealType aj,
208 RealType eta) {
209 RealType R2 = R * R;
210 switch (classify(R, ai, aj)) {
211 case FarField:
212 return 1.0 / (8.0 * Constants::PI * eta * R2);
213 case PartialOverlap: {
214 RealType num = (ai - aj + R) * (ai - aj + R) *
215 (aj * aj + 2.0 * aj * (ai + R) - 3.0 * (ai - R) * (ai - R));
216 return (num / (8.0 * R2)) /
217 (16.0 * Constants::PI * eta * ai * ai * ai * aj);
218 }
219 case FullImmersion:
220 // ZWMS Eq. 3.5: theta(a_i - a_j) * R / zeta_i^rr, zeta_i^rr = 8 pi eta a_i^3.
221 return heavi(ai - aj) * R / (8.0 * Constants::PI * eta * ai * ai * ai);
222 }
223 return 0.0;
224 }
225
226 //! rotation-translation: Omega_i from F_j. carries 1/eta.
227 inline Mat3x3d muRT(const Vector3d& rij, RealType ai, RealType aj,
228 RealType eta) {
229 RealType R = rij.length();
230 Vector3d rh = rij / R;
231 return (-rtPrefactor(R, ai, aj, eta)) * skew(rh);
232 }
233
234 //! translation-rotation: U_i from T_j. Uses the i<->j swapped prefactor
235 //! (equal to muRT only in the far field). carries 1/eta.
236 inline Mat3x3d muTR(const Vector3d& rij, RealType ai, RealType aj,
237 RealType eta) {
238 RealType R = rij.length();
239 Vector3d rh = rij / R;
240 return (-rtPrefactor(R, aj, ai, eta)) * skew(rh);
241 }
242
243 // ---- dipolar blocks, contracted with the (uniform) strain E ----------- //
244 // eta- AND pi-INDEPENDENT. E must be symmetric and traceless.
245
246 //! mu_td_ij : E -> translational disturbance velocity at bead i
247 //! = hat1 (E rhat) + hat3 (rhat . E . rhat) rhat
248 inline Vector3d muTD_dot_E(const Vector3d& rij, RealType ai, RealType aj,
249 const Mat3x3d& E) {
250 RealType R = rij.length();
251 Vector3d rh = rij / R;
252 RealType R2 = R * R, R4 = R2 * R2, R5 = R4 * R, R6 = R4 * R2;
253 RealType ai2 = ai * ai, aj2 = aj * aj;
254 RealType hat1 = 0.0, hat3 = 0.0;
255
256 switch (classify(R, ai, aj)) {
257 case FarField:
258 hat1 = (5.0 * aj / 6.0) * (-2.0 * (5.0 * ai2 * aj2 + 3.0 * aj2 * aj2)) /
259 (5.0 * R4);
260 hat3 = (5.0 * aj / 6.0) * aj2 * (5.0 * ai2 + 3.0 * aj2 - 3.0 * R2) / R4;
261 break;
262 case PartialOverlap: {
263 RealType d = ai - aj;
264 RealType dm = aj - ai;
265 RealType dm5 =
266 dm * dm * dm * dm * dm; // (a_j - a_i)^5
267 RealType calC = (10.0 * R6 - 24.0 * R5 * ai - 15.0 * R4 * (aj2 - ai2) +
268 dm5 * (ai + 5.0 * aj)) /
269 (40.0 * ai * aj * R4);
270 RealType calD = ((d * d - R2) * (d * d - R2) *
271 (d * (ai + 5.0 * aj) - R2)) /
272 (16.0 * ai * aj * R4);
273 hat1 = (5.0 * aj / 6.0) * calC;
274 hat3 = (5.0 * aj / 6.0) * calD;
275 break;
276 }
277 case FullImmersion:
278 hat1 = -heavi(aj - ai) * R;
279 hat3 = 0.0;
280 break;
281 }
282
283 Vector3d Erh = E * rh;
284 RealType rEr = dot(rh, Erh);
285 return hat1 * Erh + (hat3 * rEr) * rh;
286 }
287
288 //! mu_rd_ij : E -> angular-velocity disturbance at bead i
289 //! = p [ (E rhat) x rhat ]
290 inline Vector3d muRD_dot_E(const Vector3d& rij, RealType ai, RealType aj,
291 const Mat3x3d& E) {
292 RealType R = rij.length();
293 Vector3d rh = rij / R;
294 RealType R2 = R * R, R3 = R2 * R;
295 RealType p = 0.0;
296
297 switch (classify(R, ai, aj)) {
298 case FarField:
299 p = -2.5 * (aj / R) * (aj / R) * (aj / R);
300 break;
301 case PartialOverlap: {
302 RealType d2 = (ai - aj) * (ai - aj);
303 RealType calB = (3.0 * (d2 - R2) * (d2 - R2) *
304 (ai * ai + 4.0 * ai * aj + aj * aj - R2)) /
305 (64.0 * R3);
306 p = -5.0 * calB / (3.0 * ai * ai * ai);
307 break;
308 }
309 case FullImmersion:
310 p = 0.0;
311 break;
312 }
313
314 Vector3d Erh = E * rh;
315 return p * cross(Erh, rh);
316 }
317
318} // namespace OpenMD::RPY
319
320#endif // HYDRODYNAMICS_RPYBLOCKS_HPP
Mat3x3d muRT(const Vector3d &rij, RealType ai, RealType aj, RealType eta)
rotation-translation: Omega_i from F_j. carries 1/eta.
Vector3d muRD_dot_E(const Vector3d &rij, RealType ai, RealType aj, const Mat3x3d &E)
mu_rd_ij : E -> angular-velocity disturbance at bead i = p [ (E rhat) x rhat ]
Mat3x3d muRR(const Vector3d &rij, RealType ai, RealType aj, RealType eta)
rotation-rotation, carries 1/eta
RealType heavi(RealType x)
Heaviside step used by the full-immersion rt/td branches: 1 if x>0 else 0.
Mat3x3d muTT(const Vector3d &rij, RealType ai, RealType aj, RealType eta)
translation-translation, carries 1/eta
Regime classify(RealType R, RealType ai, RealType aj)
Classify a pair by centre-to-centre distance R and radii a_i, a_j.
Definition RPYBlocks.hpp:90
RealType rtPrefactor(RealType R, RealType ai, RealType aj, RealType eta)
rt prefactor pf such that mu_rt = -pf * skew(rhat) (Omega_i from F_j).
Vector3d muTD_dot_E(const Vector3d &rij, RealType ai, RealType aj, const Mat3x3d &E)
mu_td_ij : E -> translational disturbance velocity at bead i = hat1 (E rhat) + hat3 (rhat ....
Mat3x3d muTR(const Vector3d &rij, RealType ai, RealType aj, RealType eta)
translation-rotation: U_i from T_j. Uses the i<->j swapped prefactor (equal to muRT only in the far f...
Mat3x3d skew(const Vector3d &v)
cross-product (skew) matrix S(v) such that S(v) * w = v x w
Definition RPYBlocks.hpp:97
static SquareMatrix< Real, Dim > identity()
Real & z()
Returns reference of the third element of Vector3.
Definition Vector3.hpp:123
Real & x()
Returns reference of the first element of Vector3.
Definition Vector3.hpp:99
Real & y()
Returns reference of the second element of Vector3.
Definition Vector3.hpp:111
Real length() const
Returns the length of this vector.
Definition Vector.hpp:397
Vector3< Real > cross(const Vector3< Real > &v1, const Vector3< Real > &v2)
Returns the cross product of two Vectors.
Definition Vector3.hpp:139
Real dot(const DynamicVector< Real > &v1, const DynamicVector< Real > &v2)
Returns the dot product of two DynamicVectors.