@@ -83,7 +83,7 @@ def _parse_ff(itpfile):
83
83
if m :
84
84
atypes .append ((m .group (1 ), m .group (2 ), m .group (3 ), float (m .group (4 )), float (m .group (5 ))))
85
85
continue
86
- elif sect == 'dihedrealtypes ' :
86
+ elif sect == 'dihedraltypes ' :
87
87
m = re .match ('\s*(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\d+)\s+(\S+)\s+(\S+)' ,l )
88
88
if m :
89
89
if m .group (5 ) == '4' :
@@ -99,7 +99,7 @@ def _parse_ff(itpfile):
99
99
100
100
101
101
def _match_type (atom ,pattern ):
102
- return atom == pattern or (len (pattern ) > 1 and atom [0 ] == pattern [0 ] and pattern [1 ] == '*' )
102
+ return atom == pattern or (len (pattern ) > 1 and atom [0 ] == pattern [0 ] and pattern [1 ] == '*' ) or pattern == 'X'
103
103
104
104
105
105
class Molecule :
@@ -118,35 +118,73 @@ def _match_bonds(self,btypes):
118
118
self .bonds_kb = np .empty (self .bonds .shape [0 ],dtype = np .float32 )
119
119
120
120
for i in range (self .bonds .shape [0 ]):
121
+ matched = False
121
122
for b in btypes :
122
123
t0 = self .atypes [self .bonds [i ,0 ]]
123
124
t1 = self .atypes [self .bonds [i ,1 ]]
124
- if (_match_type (t0 ,b [0 ]) and _match_type (t1 ,b [1 ])) or (_match_type (t0 ,b [1 ]) and _match_type (t1 ,b [0 ])):
125
+ if ((_match_type (t0 ,b [0 ]) and _match_type (t1 ,b [1 ]))
126
+ or (_match_type (t0 ,b [1 ]) and _match_type (t1 ,b [0 ]))):
125
127
self .bonds_b0 [i ] = b [2 ]
126
128
self .bonds_kb [i ] = b [3 ]
129
+ matched = True
127
130
break # first match only
131
+ if not matched :
132
+ self .bonds_b0 [i ] = np .nan
133
+ self .bonds_kb [i ] = np .nan
134
+ log .warn (f"bond { i } ({ self .bonds [i ]} ) unmatched" )
135
+
128
136
129
137
def _match_angles (self ,atypes ):
130
138
self .angles_th0 = np .empty (self .angles .shape [0 ],dtype = np .float32 )
131
139
self .angles_cth = np .empty (self .angles .shape [0 ],dtype = np .float32 )
132
140
133
141
for i in range (self .angles .shape [0 ]):
142
+ matched = False
134
143
for a in atypes :
135
144
t0 = self .atypes [self .angles [i ,0 ]]
136
145
t1 = self .atypes [self .angles [i ,1 ]]
137
146
t2 = self .atypes [self .angles [i ,2 ]]
138
- if (_match_type (t0 ,a [0 ]) and _match_type (t1 ,a [1 ]) and _match_type (t2 ,a [2 ])) or (_match_type (t0 ,a [2 ]) and _match_type (t1 ,a [1 ]) and _match_type (t2 ,a [0 ])):
147
+ if ((_match_type (t0 ,a [0 ]) and _match_type (t1 ,a [1 ]) and _match_type (t2 ,a [2 ]))
148
+ or (_match_type (t0 ,a [2 ]) and _match_type (t1 ,a [1 ]) and _match_type (t2 ,a [0 ]))):
139
149
self .angles_th0 [i ] = a [3 ] / 180. * np .pi
140
150
self .angles_cth [i ] = a [4 ]
151
+ matched = True
141
152
break # first match only
153
+ if not matched :
154
+ self .angles_th [i ] = np .nan
155
+ self .angles_cth [i ] = np .nan
156
+ log .warn (f"angle { i } ({ self .angles [i ]} ) unmatched" )
157
+
142
158
143
159
self .angles_2rth0 = 2. * np .reciprocal (self .angles_th0 )
144
160
145
161
146
162
147
163
def _match_dihed (self ,d4types ,d9types ):
148
- # TODO
149
- pass
164
+ self .dihed9_phase = np .empty (self .dihed9 .shape [0 ],dtype = np .float32 )
165
+ self .dihed9_kd = np .empty (self .dihed9 .shape [0 ],dtype = np .float32 )
166
+
167
+ # XXX: type4 are not matched to FF, they are always included if present in topology
168
+
169
+ for i in range (self .dihed9 .shape [0 ]):
170
+ matched = False
171
+ for d in d9types :
172
+ t0 = self .atypes [self .dihed9 [i ,0 ]]
173
+ t1 = self .atypes [self .dihed9 [i ,1 ]]
174
+ t2 = self .atypes [self .dihed9 [i ,2 ]]
175
+ t3 = self .atypes [self .dihed9 [i ,3 ]]
176
+
177
+ if ((_match_type (t0 ,d [0 ]) and _match_type (t1 ,d [1 ]) and _match_type (t2 ,d [2 ]) and _match_type (t3 ,d [3 ]))
178
+ or (_match_type (t0 ,d [3 ]) and _match_type (t1 ,d [2 ]) and _match_type (t2 ,d [1 ]) and _match_type (t3 ,d [0 ]))):
179
+ self .dihed9_phase [i ] = d [4 ] / 180. * np .pi
180
+ self .dihed9_kd [i ] = d [5 ]
181
+ matched = True
182
+ break
183
+ if not matched :
184
+ self .dihed9_phase [i ] = np .nan
185
+ self .dihed9_kd [i ] = np .nan
186
+ log .warn (f"dihed9 { i } ({ self .dihed9 [i ]} ) unmatched" )
187
+
150
188
151
189
152
190
# ....
@@ -173,6 +211,8 @@ def _match_dihed(self,d4types,d9types):
173
211
174
212
175
213
214
+ # XXX: unmatched bonds/angles/dihedrals (i.e. nans in their properties) are not handled yet
215
+
176
216
# geoms[atom][xyz][conf]
177
217
def _ic_bonds (self ,geoms ):
178
218
out = np .empty ([self .bonds .shape [0 ],geoms .shape [2 ]],dtype = np .float32 )
@@ -191,15 +231,64 @@ def _ic_angles(self,geoms):
191
231
for i in range (self .angles .shape [0 ]):
192
232
v1 = geoms [self .angles [i ,0 ],:,:] - geoms [self .angles [i ,1 ],:,:]
193
233
v2 = geoms [self .angles [i ,2 ],:,:] - geoms [self .angles [i ,1 ],:,:]
194
- rn1 = np .reciprocal ( np . linalg .norm (v1 ,axis = 0 ) )
195
- rn2 = np .reciprocal ( np . linalg .norm (v2 ,axis = 0 ) )
234
+ n1 = np .linalg .norm (v1 ,axis = 0 )
235
+ n2 = np .linalg .norm (v2 ,axis = 0 )
196
236
dot = np .einsum ('ij,ij->j' ,v1 ,v2 )
197
- aa = np .arccos (dot * rn1 * rn2 )
237
+ dot /= n1 * n2
238
+ aa = np .arccos (dot )
198
239
out [i ] = (aa - .75 * self .angles_th0 [i ]) * self .angles_2rth0 [i ] # map 0.75 a0 -- 1.25 a0 to 0 -- 1
199
240
200
241
return out
201
242
202
- # TODO dihedrals 4/9, nbdistance
243
+
244
+ def _ic_dihedral (self ,geoms ,atoms ):
245
+ a12 = geoms [atoms [1 ],:,:] - geoms [atoms [0 ],:,:]
246
+ a23 = geoms [atoms [2 ],:,:] - geoms [atoms [1 ],:,:]
247
+ a34 = geoms [atoms [3 ],:,:] - geoms [atoms [2 ],:,:]
248
+
249
+ a12 /= np .linalg .norm (a12 ,axis = 0 )
250
+ a23 /= np .linalg .norm (a23 ,axis = 0 )
251
+ a34 /= np .linalg .norm (a34 ,axis = 0 )
252
+
253
+ vp1 = np .cross (a12 ,a23 ,axis = 0 )
254
+ vp2 = np .cross (a23 ,a34 ,axis = 0 )
255
+ vp3 = np .cross (vp1 ,a23 ,axis = 0 )
256
+
257
+ sp1 = np .einsum ('ij,ij->j' ,vp1 ,vp2 )
258
+ sp2 = np .einsum ('ij,ij->j' ,vp3 ,vp2 )
259
+
260
+ """ original:
261
+
262
+ aa = np.arctan2(sp1,sp2) - np.pi * .5
263
+ return np.sin(aa), np.cos(aa)
264
+
265
+ this is the same, IMHO, without expensive trigon:"""
266
+
267
+ return (1. - sp2 )* .5 , (1. + sp1 )* .5
268
+
269
+
270
+ def _ic_dihed4 (self ,geoms ):
271
+ out = np .empty ([self .dihed4 .shape [0 ] * 2 , geoms .shape [2 ]],dtype = np .float32 )
272
+
273
+ for i in range (self .dihed4 .shape [0 ]):
274
+ s ,c = self ._ic_dihedral (geoms ,self .dihed4 [i ])
275
+ out [2 * i ] = s
276
+ out [2 * i + 1 ] = c
277
+
278
+ return out
279
+
280
+
281
+ def _ic_dihed9 (self ,geoms ):
282
+ out = np .empty ([self .dihed9 .shape [0 ] * 2 , geoms .shape [2 ]],dtype = np .float32 )
283
+
284
+ for i in range (self .dihed9 .shape [0 ]):
285
+ s ,c = self ._ic_dihedral (geoms ,self .dihed9 [i ])
286
+ out [2 * i ] = s
287
+ out [2 * i + 1 ] = c
288
+
289
+ return out
290
+
291
+ # TODO nbdistance
203
292
204
293
def intcoord (self ,geoms ):
205
294
if self .nb is not None :
@@ -210,6 +299,8 @@ def intcoord(self,geoms):
210
299
return np .concatenate ((
211
300
self ._ic_bonds (geoms ),
212
301
self ._ic_angles (geoms ),
302
+ self ._ic_dihed4 (geoms ),
303
+ self ._ic_dihed9 (geoms ),
213
304
nb
214
305
),axis = 0 )
215
306
@@ -228,7 +319,9 @@ def intcoord(self,geoms):
228
319
print (mol .dihed9 )
229
320
230
321
231
- print (_parse_ff (os .path .dirname (os .path .abspath (__file__ )) + '/ffbonded.itp' ))
322
+ ff = os .path .dirname (os .path .abspath (__file__ )) + '/ffbonded.itp'
323
+ btypes ,atypes ,d4types ,d9types = _parse_ff (ff )
324
+ print (d9types )
232
325
233
326
print (mol .bonds_b0 )
234
327
print (mol .angles_th0 )
0 commit comments