mimicpy 0.2.0__py3-none-any.whl → 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mimicpy/__init__.py +1 -1
- mimicpy/__main__.py +726 -2
- mimicpy/_authors.py +2 -2
- mimicpy/_version.py +2 -2
- mimicpy/coords/__init__.py +1 -1
- mimicpy/coords/base.py +1 -1
- mimicpy/coords/cpmdgeo.py +1 -1
- mimicpy/coords/gro.py +1 -1
- mimicpy/coords/pdb.py +1 -1
- mimicpy/core/__init__.py +1 -1
- mimicpy/core/prepare.py +3 -3
- mimicpy/core/selector.py +1 -1
- mimicpy/force_matching/__init__.py +34 -0
- mimicpy/force_matching/bonded_forces.py +628 -0
- mimicpy/force_matching/compare_top.py +809 -0
- mimicpy/force_matching/dresp.py +435 -0
- mimicpy/force_matching/nonbonded_forces.py +32 -0
- mimicpy/force_matching/opt_ff.py +2114 -0
- mimicpy/force_matching/qm_region.py +1960 -0
- mimicpy/plugins/__main_installer__.py +76 -0
- mimicpy/{__main_vmd__.py → plugins/__main_vmd__.py} +2 -2
- mimicpy/plugins/pymol.py +56 -0
- mimicpy/plugins/vmd.tcl +78 -0
- mimicpy/scripts/__init__.py +1 -1
- mimicpy/scripts/cpmd.py +1 -1
- mimicpy/scripts/fm_input.py +265 -0
- mimicpy/scripts/fmdata.py +120 -0
- mimicpy/scripts/mdp.py +1 -1
- mimicpy/scripts/ndx.py +1 -1
- mimicpy/scripts/script.py +1 -1
- mimicpy/topology/__init__.py +1 -1
- mimicpy/topology/itp.py +603 -35
- mimicpy/topology/mpt.py +1 -1
- mimicpy/topology/top.py +254 -15
- mimicpy/topology/topol_dict.py +233 -4
- mimicpy/utils/__init__.py +1 -1
- mimicpy/utils/atomic_numbers.py +1 -1
- mimicpy/utils/constants.py +17 -3
- mimicpy/utils/elements.py +1 -1
- mimicpy/utils/errors.py +1 -1
- mimicpy/utils/file_handler.py +1 -1
- mimicpy/utils/strings.py +1 -1
- mimicpy-0.3.0.dist-info/METADATA +156 -0
- mimicpy-0.3.0.dist-info/RECORD +50 -0
- {mimicpy-0.2.0.dist-info → mimicpy-0.3.0.dist-info}/WHEEL +1 -1
- mimicpy-0.3.0.dist-info/entry_points.txt +4 -0
- mimicpy-0.2.0.dist-info/METADATA +0 -86
- mimicpy-0.2.0.dist-info/RECORD +0 -38
- mimicpy-0.2.0.dist-info/entry_points.txt +0 -3
- {mimicpy-0.2.0.dist-info → mimicpy-0.3.0.dist-info/licenses}/COPYING +0 -0
- {mimicpy-0.2.0.dist-info → mimicpy-0.3.0.dist-info/licenses}/COPYING.LESSER +0 -0
- {mimicpy-0.2.0.dist-info → mimicpy-0.3.0.dist-info}/top_level.txt +0 -0
- {mimicpy-0.2.0.dist-info → mimicpy-0.3.0.dist-info}/zip-safe +0 -0
|
@@ -0,0 +1,628 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def compute_bond_force(xi, xj, bond_length, force_constant,
|
|
5
|
+
flag='force', derivative=None):
|
|
6
|
+
"""
|
|
7
|
+
Computes forces or derivatives related to a bond.
|
|
8
|
+
For harmonic bonds: V(r) = 1/2 * k * (r - r_0)^2
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
xi, xj: Coordinates of the two atoms
|
|
12
|
+
bond_length: Equilibrium bond length (r_0)
|
|
13
|
+
force_constant: Force constant (k)
|
|
14
|
+
flag: 'force' or 'derivative'
|
|
15
|
+
derivative: 'force_constant' or 'bond_length'
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
tuple: (fi, fj) Computed forces or derivatives
|
|
19
|
+
"""
|
|
20
|
+
# Compute distance vector and length
|
|
21
|
+
R_ij = xj - xi
|
|
22
|
+
r_ij = get_magnitude(R_ij)
|
|
23
|
+
|
|
24
|
+
# Check for zero length vector
|
|
25
|
+
if r_ij == 0:
|
|
26
|
+
raise ValueError("Zero length bond vector encountered")
|
|
27
|
+
|
|
28
|
+
# Compute unit vector
|
|
29
|
+
R_ij_unit = R_ij / r_ij
|
|
30
|
+
|
|
31
|
+
# Compute force derivatives with respect to atomic positions
|
|
32
|
+
# These are the derivatives of r with respect to atomic positions
|
|
33
|
+
dr_dri = -R_ij_unit # Derivative of r with respect to ri
|
|
34
|
+
dr_drj = R_ij_unit # Derivative of r with respect to rj
|
|
35
|
+
|
|
36
|
+
if flag == 'force':
|
|
37
|
+
# Force is negative gradient of potential: F = -k * (r - r_0) * dr/dr
|
|
38
|
+
F_i = -force_constant * (r_ij - bond_length) * dr_dri
|
|
39
|
+
F_j = -force_constant * (r_ij - bond_length) * dr_drj
|
|
40
|
+
return F_i, F_j
|
|
41
|
+
|
|
42
|
+
elif flag == 'derivative':
|
|
43
|
+
if derivative == 'force_constant':
|
|
44
|
+
# Derivative of force with respect to force constant
|
|
45
|
+
# dF/dk = -(r - r_0) * dr/dr
|
|
46
|
+
dk_i = -(r_ij - bond_length) * dr_dri
|
|
47
|
+
dk_j = -(r_ij - bond_length) * dr_drj
|
|
48
|
+
return dk_i, dk_j
|
|
49
|
+
|
|
50
|
+
elif derivative == 'bond_length':
|
|
51
|
+
# Derivative of force with respect to equilibrium bond length
|
|
52
|
+
# dF/dr_0 = k * dr/dr
|
|
53
|
+
dr0_i = force_constant * dr_dri
|
|
54
|
+
dr0_j = force_constant * dr_drj
|
|
55
|
+
return dr0_i, dr0_j
|
|
56
|
+
|
|
57
|
+
def compute_angle_force(xi, xj, xk, angle, force_constant, flag='force', derivative=None):
|
|
58
|
+
"""
|
|
59
|
+
Computes forces or derivatives related to an angle.
|
|
60
|
+
For harmonic angles: V(theta) = 1/2 * k * (theta - theta_0)^2
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
xi, xj, xk: Coordinates of the three atoms
|
|
64
|
+
angle: Equilibrium angle (theta_0)
|
|
65
|
+
force_constant: Force constant (k)
|
|
66
|
+
flag: 'force' or 'derivative'
|
|
67
|
+
derivative: 'force_constant' or 'angle'
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
tuple: (fi, fj, fk) Computed forces or derivatives
|
|
71
|
+
"""
|
|
72
|
+
# Compute distance vectors and distances
|
|
73
|
+
R_ij = xj - xi
|
|
74
|
+
R_kj = xj - xk
|
|
75
|
+
r_ij = np.linalg.norm(R_ij)
|
|
76
|
+
r_kj = np.linalg.norm(R_kj)
|
|
77
|
+
|
|
78
|
+
# Check for zero length vectors
|
|
79
|
+
if r_ij == 0 or r_kj == 0:
|
|
80
|
+
raise ValueError("Zero length vector encountered in angle force computation")
|
|
81
|
+
|
|
82
|
+
# Compute angle
|
|
83
|
+
dot_r_ij_kj = np.dot(R_ij, R_kj)
|
|
84
|
+
prod_r_ij_kj = r_ij * r_kj
|
|
85
|
+
cos_theta = np.clip(dot_r_ij_kj/prod_r_ij_kj, -1.0, 1.0)
|
|
86
|
+
theta = np.arccos(cos_theta)
|
|
87
|
+
|
|
88
|
+
# Compute sin(theta) safely
|
|
89
|
+
sin_theta = np.sqrt(1.0 - cos_theta**2)
|
|
90
|
+
if sin_theta < 1e-10: # Near linear angles
|
|
91
|
+
raise ValueError("Near linear angle encountered")
|
|
92
|
+
|
|
93
|
+
# Compute force derivatives with respect to atomic positions
|
|
94
|
+
# These are the derivatives of theta with respect to atomic positions
|
|
95
|
+
f_i = 1/(r_ij * sin_theta) * (R_kj/r_kj - cos_theta * R_ij/r_ij)
|
|
96
|
+
f_k = 1/(r_kj * sin_theta) * (R_ij/r_ij - cos_theta * R_kj/r_kj)
|
|
97
|
+
f_j = -f_i - f_k # Force on central atom
|
|
98
|
+
|
|
99
|
+
if flag == 'force':
|
|
100
|
+
# Force is negative gradient of potential: F = -k * (theta - theta_0) * dtheta/dr
|
|
101
|
+
F_i = -force_constant * (theta - angle) * f_i
|
|
102
|
+
F_k = -force_constant * (theta - angle) * f_k
|
|
103
|
+
F_j = -force_constant * (theta - angle) * f_j
|
|
104
|
+
return F_i, F_j, F_k
|
|
105
|
+
|
|
106
|
+
elif flag == 'derivative':
|
|
107
|
+
if derivative == 'force_constant':
|
|
108
|
+
# Derivative of force with respect to force constant
|
|
109
|
+
# dF/dk = -(theta - theta_0) * dtheta/dr
|
|
110
|
+
dk_i = -(theta - angle) * f_i
|
|
111
|
+
dk_k = -(theta - angle) * f_k
|
|
112
|
+
dk_j = -(theta - angle) * f_j
|
|
113
|
+
return dk_i, dk_j, dk_k
|
|
114
|
+
|
|
115
|
+
elif derivative == 'angle':
|
|
116
|
+
# Derivative of force with respect to equilibrium angle
|
|
117
|
+
# dF/dtheta_0 = k * dtheta/dr
|
|
118
|
+
dtheta_i = force_constant * f_i
|
|
119
|
+
dtheta_k = force_constant * f_k
|
|
120
|
+
dtheta_j = force_constant * f_j
|
|
121
|
+
return dtheta_i, dtheta_j, dtheta_k
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def get_magnitude(vector):
|
|
125
|
+
return np.linalg.norm(vector)
|
|
126
|
+
|
|
127
|
+
def distance_vector(R1, R2):
|
|
128
|
+
r = R1 - R2
|
|
129
|
+
d = get_magnitude(r)
|
|
130
|
+
return d, r
|
|
131
|
+
|
|
132
|
+
def compute_RB_dihedral_force(xi, xj, xk, xl,
|
|
133
|
+
force_constant, flag='force', derivative=None):
|
|
134
|
+
"""
|
|
135
|
+
Computes forces or derivatives related to a Ryckaert-Bellemans (RB) dihedral angle.
|
|
136
|
+
|
|
137
|
+
The RB potential is defined as:
|
|
138
|
+
V(φ) = C0 + C1*cos(φ-180°) + C2*cos²(φ-180°) + C3*cos³(φ-180°) + C4*cos⁴(φ-180°) + C5*cos⁵(φ-180°)
|
|
139
|
+
|
|
140
|
+
where φ is the dihedral angle and C0-C5 are the force constants.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
xi, xj, xk, xl: Coordinates of the four atoms defining the dihedral angle
|
|
144
|
+
force_constant: Array of force constants [C0, C1, C2, C3, C4, C5]
|
|
145
|
+
flag: 'force' or 'derivative'
|
|
146
|
+
derivative: 'C0', 'C1', 'C2', 'C3', 'C4', or 'C5' for parameter derivatives
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
tuple: (fi, fj, fk, fl) Computed forces or derivatives for each atom
|
|
150
|
+
"""
|
|
151
|
+
|
|
152
|
+
# Compute distance vectors and distances
|
|
153
|
+
R_ij = xj - xi
|
|
154
|
+
R_kj = xj - xk
|
|
155
|
+
R_kl = xl - xk
|
|
156
|
+
|
|
157
|
+
r_kj = np.linalg.norm(R_kj)
|
|
158
|
+
|
|
159
|
+
# Projection of vectors on the plane orthogonal to R_kj
|
|
160
|
+
R_im = R_ij - np.dot(R_ij, R_kj) * R_kj / r_kj**2
|
|
161
|
+
R_ln = -R_kl + np.dot(R_kl, R_kj) * R_kj / r_kj**2
|
|
162
|
+
|
|
163
|
+
# Magnitudes of the projections
|
|
164
|
+
r_im = np.linalg.norm(R_im)
|
|
165
|
+
r_ln = np.linalg.norm(R_ln)
|
|
166
|
+
|
|
167
|
+
# Compute cross product and sign of the dihedral angle
|
|
168
|
+
cosphi = np.dot(R_im, R_ln) / (r_im * r_ln)
|
|
169
|
+
cosphi = np.clip(cosphi, -1.0, 1.0) # Ensure cosphi is in valid range
|
|
170
|
+
|
|
171
|
+
# For RB potential, we need cos(phi-180°) = -cos(phi)
|
|
172
|
+
cosphi_rb = -cosphi
|
|
173
|
+
|
|
174
|
+
# Calculate forces
|
|
175
|
+
di = (R_ln/r_ln - R_im/r_im * cosphi) * 1/r_im
|
|
176
|
+
dl = (R_im/r_im - R_ln/r_ln * cosphi) * 1/r_ln
|
|
177
|
+
dj = (np.dot(R_ij, R_kj) / r_kj**2 - 1.0) * di - (np.dot(R_kl, R_kj) / r_kj**2) * dl
|
|
178
|
+
dk = -di - dj - dl
|
|
179
|
+
|
|
180
|
+
if flag == 'force':
|
|
181
|
+
# RB potential: V(phi) = C0 + C1*cos(phi-180°) + C2*cos²(phi-180°) + C3*cos³(phi-180°) + C4*cos⁴(phi-180°) + C5*cos⁵(phi-180°)
|
|
182
|
+
# Force factor is the negative derivative of V with respect to cos(phi-180°)
|
|
183
|
+
factor = -(force_constant[1] # C1
|
|
184
|
+
+ force_constant[2] * 2 * cosphi_rb # C2
|
|
185
|
+
+ force_constant[3] * 3 * cosphi_rb**2 # C3
|
|
186
|
+
+ force_constant[4] * 4 * cosphi_rb**3 # C4
|
|
187
|
+
+ force_constant[5] * 5 * cosphi_rb**4) # C5
|
|
188
|
+
|
|
189
|
+
fi = factor * di
|
|
190
|
+
fj = factor * dj
|
|
191
|
+
fk = factor * dk
|
|
192
|
+
fl = factor * dl
|
|
193
|
+
return fi, fj, fk, fl
|
|
194
|
+
|
|
195
|
+
elif flag=='derivative':
|
|
196
|
+
# For derivatives, we need dF/dC_n where F is the force
|
|
197
|
+
# F = -d/d(cos(phi-180°)) of V * di
|
|
198
|
+
# So dF/dC_n = -d/d(cos(phi-180°)) of (C_n * cos^n(phi-180°)) * di
|
|
199
|
+
if derivative == 'C0':
|
|
200
|
+
factor = 0.0 # C0 doesn't contribute to forces
|
|
201
|
+
elif derivative == 'C1':
|
|
202
|
+
factor = -1.0 # -d/d(cos(phi-180°)) of C1*cos(phi-180°)
|
|
203
|
+
elif derivative == 'C2':
|
|
204
|
+
factor = -2 * cosphi_rb # -d/d(cos(phi-180°)) of C2*cos²(phi-180°)
|
|
205
|
+
elif derivative == 'C3':
|
|
206
|
+
factor = -3 * cosphi_rb**2 # -d/d(cos(phi-180°)) of C3*cos³(phi-180°)
|
|
207
|
+
elif derivative == 'C4':
|
|
208
|
+
factor = -4 * cosphi_rb**3 # -d/d(cos(phi-180°)) of C4*cos⁴(phi-180°)
|
|
209
|
+
elif derivative == 'C5':
|
|
210
|
+
factor = -5 * cosphi_rb**4 # -d/d(cos(phi-180°)) of C5*cos⁵(phi-180°)
|
|
211
|
+
else:
|
|
212
|
+
raise ValueError(f'Unknown derivative parameter: {derivative}')
|
|
213
|
+
|
|
214
|
+
# The derivatives of the forces with respect to the coefficients
|
|
215
|
+
fi = factor * di
|
|
216
|
+
fj = factor * dj
|
|
217
|
+
fk = factor * dk
|
|
218
|
+
fl = factor * dl
|
|
219
|
+
return fi, fj, fk, fl
|
|
220
|
+
|
|
221
|
+
def compute_dihedral_force(xi, xj, xk, xl, phase, force_constant, m, flag='force', derivative=None):
|
|
222
|
+
"""
|
|
223
|
+
Computes forces or derivatives related to a dihedral angle.
|
|
224
|
+
For periodic dihedrals (types 1, 4, 9): V(phi) = k[1 + cos(n*phi - phi_0)]
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
xi, xj, xk, xl: Coordinates of the four atoms
|
|
228
|
+
phase: Phase shift (phi_0)
|
|
229
|
+
force_constant: Force constant (k)
|
|
230
|
+
m: Multiplicity (n)
|
|
231
|
+
flag: 'force' or 'derivative'
|
|
232
|
+
derivative: Not used for periodic dihedrals
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
tuple: (fi, fj, fk, fl) Computed forces or derivatives
|
|
236
|
+
"""
|
|
237
|
+
# Compute distance vectors and distances
|
|
238
|
+
R_ij = xj - xi
|
|
239
|
+
R_kj = xj - xk
|
|
240
|
+
R_kl = xl - xk
|
|
241
|
+
|
|
242
|
+
r_kj = np.linalg.norm(R_kj)
|
|
243
|
+
|
|
244
|
+
R_mj = np.cross(R_ij, R_kj)
|
|
245
|
+
R_nk = np.cross(R_kj, R_kl)
|
|
246
|
+
r_mj = np.linalg.norm(R_mj)
|
|
247
|
+
r_nk = np.linalg.norm(R_nk)
|
|
248
|
+
|
|
249
|
+
# Projection of vectors on the plane orthogonal to R_kj
|
|
250
|
+
R_im = R_ij - np.dot(R_ij, R_kj) * R_kj / r_kj**2
|
|
251
|
+
R_ln = -R_kl + np.dot(R_kl, R_kj) * R_kj / r_kj**2
|
|
252
|
+
|
|
253
|
+
# Magnitudes of the projections
|
|
254
|
+
r_im = np.linalg.norm(R_im)
|
|
255
|
+
r_ln = np.linalg.norm(R_ln)
|
|
256
|
+
|
|
257
|
+
# Compute cross product and sign of the dihedral angle
|
|
258
|
+
cosphi = np.dot(R_im, R_ln) / (r_im * r_ln)
|
|
259
|
+
cosphi = np.clip(cosphi, -1.0, 1.0) # Ensure cosphi is in valid range
|
|
260
|
+
phi = np.sign(np.dot(R_ij, R_nk))*np.arccos(cosphi)
|
|
261
|
+
sinphi = np.sin(m*phi - phase)
|
|
262
|
+
|
|
263
|
+
# Calculate force derivatives with respect to atomic positions
|
|
264
|
+
di = m * sinphi * r_kj * R_mj / r_mj**2
|
|
265
|
+
dl = - m * sinphi * r_kj * R_nk / r_nk**2
|
|
266
|
+
dj = (np.dot(R_ij, R_kj) / r_kj**2 - 1.0) * di - (np.dot(R_kl, R_kj) / r_kj**2) * dl
|
|
267
|
+
dk = -di - dj - dl
|
|
268
|
+
|
|
269
|
+
if flag == 'force':
|
|
270
|
+
# Force is negative gradient of potential
|
|
271
|
+
F_i = -force_constant * di
|
|
272
|
+
F_l = -force_constant * dl
|
|
273
|
+
F_j = -force_constant * dj
|
|
274
|
+
F_k = -force_constant * dk
|
|
275
|
+
return F_i, F_j, F_k, F_l
|
|
276
|
+
|
|
277
|
+
elif flag=='derivative':
|
|
278
|
+
# For periodic dihedrals, we only need derivative with respect to force constant
|
|
279
|
+
# dF/dk = -di, -dj, -dk, -dl
|
|
280
|
+
return -di, -dj, -dk, -dl
|
|
281
|
+
|
|
282
|
+
def get_jac_indx(atom_idx):
|
|
283
|
+
return 3*atom_idx
|
|
284
|
+
|
|
285
|
+
def compute_bonds_forces(bonds, qm_coordinates,
|
|
286
|
+
ff_optimize,
|
|
287
|
+
bond2params, flag='force'):
|
|
288
|
+
|
|
289
|
+
function_map = {
|
|
290
|
+
1: compute_bond_force,
|
|
291
|
+
}
|
|
292
|
+
if flag == 'force':
|
|
293
|
+
forces = np.zeros(qm_coordinates.shape)
|
|
294
|
+
for bond in bonds:
|
|
295
|
+
idxs = bond2params.get(bond['index'])
|
|
296
|
+
b = bond['parameters'][0]
|
|
297
|
+
kb = bond['parameters'][1]
|
|
298
|
+
if idxs[0] is not None:
|
|
299
|
+
b = ff_optimize[idxs[0]]
|
|
300
|
+
if idxs[1] is not None:
|
|
301
|
+
kb = ff_optimize[idxs[1]]
|
|
302
|
+
|
|
303
|
+
# Get atom coordinates using global indices
|
|
304
|
+
i_coords = qm_coordinates[bond['atoms'][0]]
|
|
305
|
+
j_coords = qm_coordinates[bond['atoms'][1]]
|
|
306
|
+
|
|
307
|
+
fi, fj = function_map.get(bond['function'])(i_coords, j_coords,
|
|
308
|
+
b, kb,
|
|
309
|
+
flag=flag)
|
|
310
|
+
|
|
311
|
+
# Add forces to the correct atoms
|
|
312
|
+
forces[bond['atoms'][0]] += fi
|
|
313
|
+
forces[bond['atoms'][1]] += fj
|
|
314
|
+
|
|
315
|
+
return forces
|
|
316
|
+
elif flag == 'derivative':
|
|
317
|
+
jac_mat = np.zeros((3*qm_coordinates.shape[0],ff_optimize.shape[0]))
|
|
318
|
+
|
|
319
|
+
for bond in bonds:
|
|
320
|
+
if not bond['optimize']:
|
|
321
|
+
continue
|
|
322
|
+
|
|
323
|
+
idxs = bond2params.get(bond['index'])
|
|
324
|
+
b = bond['parameters'][0]
|
|
325
|
+
kb = bond['parameters'][1]
|
|
326
|
+
|
|
327
|
+
if idxs[0] is not None:
|
|
328
|
+
b = ff_optimize[idxs[0]]
|
|
329
|
+
if idxs[1] is not None:
|
|
330
|
+
kb = ff_optimize[idxs[1]]
|
|
331
|
+
|
|
332
|
+
idx0 = get_jac_indx(bond['atoms'][0])
|
|
333
|
+
idx1 = get_jac_indx(bond['atoms'][1])
|
|
334
|
+
|
|
335
|
+
if idxs[0] is not None:
|
|
336
|
+
bi, bj = function_map.get(bond['function'])(qm_coordinates[bond['atoms'][0]],
|
|
337
|
+
qm_coordinates[bond['atoms'][1]],
|
|
338
|
+
b, kb,
|
|
339
|
+
flag=flag, derivative='bond_length')
|
|
340
|
+
|
|
341
|
+
jac_mat[idx0:idx0+3, idxs[0]] += bi
|
|
342
|
+
jac_mat[idx1:idx1+3, idxs[0]] += bj
|
|
343
|
+
|
|
344
|
+
if idxs[1] is not None:
|
|
345
|
+
ki, kj = function_map.get(bond['function'])(qm_coordinates[bond['atoms'][0]],
|
|
346
|
+
qm_coordinates[bond['atoms'][1]],
|
|
347
|
+
b, kb,
|
|
348
|
+
flag=flag, derivative='force_constant')
|
|
349
|
+
|
|
350
|
+
jac_mat[idx0:idx0+3, idxs[1]] += ki
|
|
351
|
+
jac_mat[idx1:idx1+3, idxs[1]] += kj
|
|
352
|
+
|
|
353
|
+
return jac_mat
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def compute_angles_forces(angles, qm_coordinates,
|
|
358
|
+
ff_optimize,
|
|
359
|
+
bond2params, flag='force'):
|
|
360
|
+
|
|
361
|
+
function_map = {
|
|
362
|
+
1: compute_angle_force,
|
|
363
|
+
}
|
|
364
|
+
if flag == 'force':
|
|
365
|
+
|
|
366
|
+
forces = np.zeros(qm_coordinates.shape)
|
|
367
|
+
|
|
368
|
+
for angle in angles:
|
|
369
|
+
idxs = bond2params.get(angle['index'])
|
|
370
|
+
theta = angle['parameters'][0]
|
|
371
|
+
cth = angle['parameters'][1]
|
|
372
|
+
if idxs[0] is not None:
|
|
373
|
+
theta = ff_optimize[idxs[0]]
|
|
374
|
+
if idxs[1] is not None:
|
|
375
|
+
cth = ff_optimize[idxs[1]]
|
|
376
|
+
fi, fj, fk = function_map.get(angle['function'])(qm_coordinates[angle['atoms'][0]],
|
|
377
|
+
qm_coordinates[angle['atoms'][1]],
|
|
378
|
+
qm_coordinates[angle['atoms'][2]],
|
|
379
|
+
theta, cth,
|
|
380
|
+
flag=flag)
|
|
381
|
+
|
|
382
|
+
forces[angle['atoms'][0]] += fi
|
|
383
|
+
forces[angle['atoms'][1]] += fj
|
|
384
|
+
forces[angle['atoms'][2]] += fk
|
|
385
|
+
|
|
386
|
+
return forces
|
|
387
|
+
|
|
388
|
+
elif flag == 'derivative':
|
|
389
|
+
jac_mat = np.zeros((3*qm_coordinates.shape[0],ff_optimize.shape[0]))
|
|
390
|
+
|
|
391
|
+
for angle in angles:
|
|
392
|
+
|
|
393
|
+
if not angle['optimize']:
|
|
394
|
+
continue
|
|
395
|
+
idxs = bond2params.get(angle['index'])
|
|
396
|
+
theta = angle['parameters'][0]
|
|
397
|
+
cth = angle['parameters'][1]
|
|
398
|
+
|
|
399
|
+
if idxs[0] is not None:
|
|
400
|
+
theta = ff_optimize[idxs[0]]
|
|
401
|
+
if idxs[1] is not None:
|
|
402
|
+
cth = ff_optimize[idxs[1]]
|
|
403
|
+
|
|
404
|
+
idx0 = get_jac_indx(angle['atoms'][0])
|
|
405
|
+
idx1 = get_jac_indx(angle['atoms'][1])
|
|
406
|
+
idx2 = get_jac_indx(angle['atoms'][2])
|
|
407
|
+
|
|
408
|
+
if idxs[0] is not None:
|
|
409
|
+
agi, agj, agk = function_map.get(angle['function'])(qm_coordinates[angle['atoms'][0]],
|
|
410
|
+
qm_coordinates[angle['atoms'][1]],
|
|
411
|
+
qm_coordinates[angle['atoms'][2]],
|
|
412
|
+
theta, cth,
|
|
413
|
+
flag='derivative',
|
|
414
|
+
derivative='angle')
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
jac_mat[idx0: idx0 +3, idxs[0]] += agi
|
|
418
|
+
jac_mat[idx1 : idx1 +3, idxs[0]] += agj
|
|
419
|
+
jac_mat[idx2 : idx2 +3, idxs[0]] += agk
|
|
420
|
+
|
|
421
|
+
if idxs[1] is not None:
|
|
422
|
+
ki, kj, kk = function_map.get(angle['function'])(qm_coordinates[angle['atoms'][0]],
|
|
423
|
+
qm_coordinates[angle['atoms'][1]],
|
|
424
|
+
qm_coordinates[angle['atoms'][2]],
|
|
425
|
+
theta, cth,
|
|
426
|
+
flag='derivative',
|
|
427
|
+
derivative='force_constant')
|
|
428
|
+
|
|
429
|
+
jac_mat[idx0: idx0 +3, idxs[1]] += ki
|
|
430
|
+
jac_mat[idx1 : idx1 +3, idxs[1]] += kj
|
|
431
|
+
jac_mat[idx2 : idx2 +3, idxs[1]] += kk
|
|
432
|
+
|
|
433
|
+
return jac_mat
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def compute_dihedrals_forces(dihedrals, qm_coordinates,
|
|
438
|
+
ff_optimize,
|
|
439
|
+
bond2params, flag='force'):
|
|
440
|
+
|
|
441
|
+
function_map = {
|
|
442
|
+
4: compute_dihedral_force,
|
|
443
|
+
3: compute_RB_dihedral_force
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
if flag == 'force':
|
|
448
|
+
forces = np.zeros(qm_coordinates.shape)
|
|
449
|
+
for dihedral in dihedrals:
|
|
450
|
+
if dihedral['function'] in [1, 4, 9]:
|
|
451
|
+
kd = dihedral['parameters'][1]
|
|
452
|
+
idxs = bond2params.get(dihedral['index'])
|
|
453
|
+
if idxs[1] is not None:
|
|
454
|
+
kd = ff_optimize[idxs[1]]
|
|
455
|
+
fi, fj, fk, fl= compute_dihedral_force(qm_coordinates[dihedral['atoms'][0]],
|
|
456
|
+
qm_coordinates[dihedral['atoms'][1]],
|
|
457
|
+
qm_coordinates[dihedral['atoms'][2]],
|
|
458
|
+
qm_coordinates[dihedral['atoms'][3]],
|
|
459
|
+
dihedral['parameters'][0], kd,
|
|
460
|
+
dihedral['parameters'][2], flag=flag)
|
|
461
|
+
forces[dihedral['atoms'][0]] += fi
|
|
462
|
+
forces[dihedral['atoms'][1]] += fj
|
|
463
|
+
forces[dihedral['atoms'][2]] += fk
|
|
464
|
+
forces[dihedral['atoms'][3]] += fl
|
|
465
|
+
|
|
466
|
+
elif dihedral['function'] == 3:
|
|
467
|
+
C_params = []
|
|
468
|
+
idxs = bond2params.get(dihedral['index'])
|
|
469
|
+
for i, c in enumerate(idxs):
|
|
470
|
+
if c is not None:
|
|
471
|
+
C_params.append(ff_optimize[c])
|
|
472
|
+
else:
|
|
473
|
+
C_params.append(dihedral['parameters'][i])
|
|
474
|
+
fi, fj, fk, fl= compute_RB_dihedral_force(qm_coordinates[dihedral['atoms'][0]],
|
|
475
|
+
qm_coordinates[dihedral['atoms'][1]],
|
|
476
|
+
qm_coordinates[dihedral['atoms'][2]],
|
|
477
|
+
qm_coordinates[dihedral['atoms'][3]],
|
|
478
|
+
C_params,flag=flag)
|
|
479
|
+
|
|
480
|
+
forces[dihedral['atoms'][0]] += fi
|
|
481
|
+
forces[dihedral['atoms'][1]] += fj
|
|
482
|
+
forces[dihedral['atoms'][2]] += fk
|
|
483
|
+
forces[dihedral['atoms'][3]] += fl
|
|
484
|
+
|
|
485
|
+
return forces
|
|
486
|
+
|
|
487
|
+
elif flag == 'derivative':
|
|
488
|
+
jac_mat = np.zeros((3*qm_coordinates.shape[0],ff_optimize.shape[0]))
|
|
489
|
+
for dihedral in dihedrals:
|
|
490
|
+
if not dihedral['optimize']:
|
|
491
|
+
continue
|
|
492
|
+
idxs = bond2params.get(dihedral['index'])
|
|
493
|
+
|
|
494
|
+
if dihedral['function'] in [1, 4, 9]:
|
|
495
|
+
# Only compute derivatives if the force constant is being optimized
|
|
496
|
+
if idxs[1] is not None:
|
|
497
|
+
ki, kj, kk, kl= compute_dihedral_force(qm_coordinates[dihedral['atoms'][0]],
|
|
498
|
+
qm_coordinates[dihedral['atoms'][1]],
|
|
499
|
+
qm_coordinates[dihedral['atoms'][2]],
|
|
500
|
+
qm_coordinates[dihedral['atoms'][3]],
|
|
501
|
+
dihedral['parameters'][0], ff_optimize[idxs[1]],
|
|
502
|
+
dihedral['parameters'][2], flag='derivative')
|
|
503
|
+
idx0 = get_jac_indx(dihedral['atoms'][0])
|
|
504
|
+
idx1 = get_jac_indx(dihedral['atoms'][1])
|
|
505
|
+
idx2 = get_jac_indx(dihedral['atoms'][2])
|
|
506
|
+
idx3 = get_jac_indx(dihedral['atoms'][3])
|
|
507
|
+
|
|
508
|
+
jac_mat[idx0: idx0 +3, idxs[1]] += ki
|
|
509
|
+
jac_mat[idx1 : idx1 +3, idxs[1]] += kj
|
|
510
|
+
jac_mat[idx2 : idx2 +3, idxs[1]] += kk
|
|
511
|
+
jac_mat[idx3 : idx3 +3, idxs[1]] += kl
|
|
512
|
+
|
|
513
|
+
elif dihedral['function'] == 3:
|
|
514
|
+
if not dihedral['optimize']:
|
|
515
|
+
continue
|
|
516
|
+
C_params = []
|
|
517
|
+
idxs = bond2params.get(dihedral['index'])
|
|
518
|
+
for i, c in enumerate(idxs):
|
|
519
|
+
if c is not None:
|
|
520
|
+
C_params.append(ff_optimize[c])
|
|
521
|
+
else:
|
|
522
|
+
C_params.append(dihedral['parameters'][i])
|
|
523
|
+
|
|
524
|
+
for i, c in enumerate(idxs):
|
|
525
|
+
if c is None:
|
|
526
|
+
continue
|
|
527
|
+
ki, kj, kk, kl= compute_RB_dihedral_force(qm_coordinates[dihedral['atoms'][0]],
|
|
528
|
+
qm_coordinates[dihedral['atoms'][1]],
|
|
529
|
+
qm_coordinates[dihedral['atoms'][2]],
|
|
530
|
+
qm_coordinates[dihedral['atoms'][3]],
|
|
531
|
+
C_params, flag='derivative',derivative=f'C{i}')
|
|
532
|
+
|
|
533
|
+
idx0 = get_jac_indx(dihedral['atoms'][0])
|
|
534
|
+
idx1 = get_jac_indx(dihedral['atoms'][1])
|
|
535
|
+
idx2 = get_jac_indx(dihedral['atoms'][2])
|
|
536
|
+
idx3 = get_jac_indx(dihedral['atoms'][3])
|
|
537
|
+
jac_mat[idx0: idx0 +3, c] += ki
|
|
538
|
+
jac_mat[idx1 : idx1 +3, c] += kj
|
|
539
|
+
jac_mat[idx2 : idx2 +3, c] += kk
|
|
540
|
+
jac_mat[idx3 : idx3 +3, c] += kl
|
|
541
|
+
|
|
542
|
+
return jac_mat
|
|
543
|
+
|
|
544
|
+
def compute_bonded_forces(ff_optimize,
|
|
545
|
+
qm_coordinates,
|
|
546
|
+
bonds,
|
|
547
|
+
angles,
|
|
548
|
+
dihedrals,
|
|
549
|
+
bond2params,
|
|
550
|
+
qm_atoms_count=None):
|
|
551
|
+
"""
|
|
552
|
+
Compute bonded forces for all interactions in the QM region.
|
|
553
|
+
|
|
554
|
+
Args:
|
|
555
|
+
ff_optimize (numpy.ndarray): Array of optimized force field parameters
|
|
556
|
+
qm_coordinates (numpy.ndarray): Array of coordinates (QM only or QM+MM)
|
|
557
|
+
bonds (list): List of bond dictionaries
|
|
558
|
+
angles (list): List of angle dictionaries
|
|
559
|
+
dihedrals (list): List of dihedral dictionaries
|
|
560
|
+
bond2params (dict): Mapping of interaction indices to parameter indices
|
|
561
|
+
qm_atoms_count (int, optional): Number of QM atoms if coordinates include MM atoms
|
|
562
|
+
|
|
563
|
+
Returns:
|
|
564
|
+
numpy.ndarray: Array of forces for QM atoms only
|
|
565
|
+
"""
|
|
566
|
+
# Initialize forces array for all atoms in coordinates
|
|
567
|
+
forces = np.zeros(qm_coordinates.shape)
|
|
568
|
+
|
|
569
|
+
# Process bonds
|
|
570
|
+
b_forces = compute_bonds_forces(bonds, qm_coordinates,
|
|
571
|
+
ff_optimize,
|
|
572
|
+
bond2params, 'force')
|
|
573
|
+
forces += b_forces
|
|
574
|
+
|
|
575
|
+
# Process angles
|
|
576
|
+
a_forces = compute_angles_forces(angles, qm_coordinates,
|
|
577
|
+
ff_optimize,
|
|
578
|
+
bond2params, 'force')
|
|
579
|
+
forces += a_forces
|
|
580
|
+
|
|
581
|
+
# Process dihedrals
|
|
582
|
+
d_forces = compute_dihedrals_forces(dihedrals, qm_coordinates,
|
|
583
|
+
ff_optimize,
|
|
584
|
+
bond2params, 'force')
|
|
585
|
+
forces += d_forces
|
|
586
|
+
|
|
587
|
+
# If qm_atoms_count is provided, return only QM atom forces
|
|
588
|
+
if qm_atoms_count is not None:
|
|
589
|
+
return forces[:qm_atoms_count]
|
|
590
|
+
|
|
591
|
+
return forces
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
def jacobian_ff(ff_optimize,
|
|
595
|
+
qm_coordinates,
|
|
596
|
+
bond2params,
|
|
597
|
+
bonds, angles, dihedrals,
|
|
598
|
+
qm_atoms_count=None):
|
|
599
|
+
|
|
600
|
+
"""
|
|
601
|
+
ff_optimize: b1, k1, b2, k2, ...
|
|
602
|
+
"""
|
|
603
|
+
|
|
604
|
+
number_of_parameters = ff_optimize.shape[0]
|
|
605
|
+
# If qm_atoms_count is provided, compute Jacobian only for QM atoms
|
|
606
|
+
if qm_atoms_count is not None:
|
|
607
|
+
number_of_residuals = 3 * qm_atoms_count
|
|
608
|
+
else:
|
|
609
|
+
number_of_residuals = 3 * qm_coordinates.shape[0]
|
|
610
|
+
|
|
611
|
+
# compute the bonded forces
|
|
612
|
+
jacobian_matrix = np.zeros((number_of_residuals,number_of_parameters))
|
|
613
|
+
|
|
614
|
+
# Compute Jacobian for all atoms in coordinates
|
|
615
|
+
full_jacobian = np.zeros((3 * qm_coordinates.shape[0], number_of_parameters))
|
|
616
|
+
|
|
617
|
+
full_jacobian += compute_bonds_forces(bonds, qm_coordinates,
|
|
618
|
+
ff_optimize, bond2params, flag='derivative')
|
|
619
|
+
full_jacobian += compute_angles_forces(angles, qm_coordinates,
|
|
620
|
+
ff_optimize, bond2params, flag='derivative')
|
|
621
|
+
full_jacobian += compute_dihedrals_forces(dihedrals, qm_coordinates,
|
|
622
|
+
ff_optimize, bond2params, flag='derivative')
|
|
623
|
+
|
|
624
|
+
# If qm_atoms_count is provided, return only QM atom Jacobian
|
|
625
|
+
if qm_atoms_count is not None:
|
|
626
|
+
return full_jacobian[:3*qm_atoms_count, :]
|
|
627
|
+
|
|
628
|
+
return full_jacobian
|