Skip to content

Commit 8d30264

Browse files
committed
dihedrals
1 parent a5d2fae commit 8d30264

File tree

2 files changed

+136
-35
lines changed

2 files changed

+136
-35
lines changed

pkgtest.ipynb

Lines changed: 32 additions & 24 deletions
Large diffs are not rendered by default.

src/asmsa/__init__.py

Lines changed: 104 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ def _parse_ff(itpfile):
8383
if m:
8484
atypes.append((m.group(1), m.group(2), m.group(3), float(m.group(4)), float(m.group(5))))
8585
continue
86-
elif sect == 'dihedrealtypes':
86+
elif sect == 'dihedraltypes':
8787
m = re.match('\s*(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\d+)\s+(\S+)\s+(\S+)',l)
8888
if m:
8989
if m.group(5) == '4':
@@ -99,7 +99,7 @@ def _parse_ff(itpfile):
9999

100100

101101
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'
103103

104104

105105
class Molecule:
@@ -118,35 +118,73 @@ def _match_bonds(self,btypes):
118118
self.bonds_kb = np.empty(self.bonds.shape[0],dtype=np.float32)
119119

120120
for i in range(self.bonds.shape[0]):
121+
matched = False
121122
for b in btypes:
122123
t0 = self.atypes[self.bonds[i,0]]
123124
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]))):
125127
self.bonds_b0[i] = b[2]
126128
self.bonds_kb[i] = b[3]
129+
matched = True
127130
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+
128136

129137
def _match_angles(self,atypes):
130138
self.angles_th0 = np.empty(self.angles.shape[0],dtype=np.float32)
131139
self.angles_cth = np.empty(self.angles.shape[0],dtype=np.float32)
132140

133141
for i in range(self.angles.shape[0]):
142+
matched = False
134143
for a in atypes:
135144
t0 = self.atypes[self.angles[i,0]]
136145
t1 = self.atypes[self.angles[i,1]]
137146
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]))):
139149
self.angles_th0[i] = a[3] / 180. * np.pi
140150
self.angles_cth[i] = a[4]
151+
matched = True
141152
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+
142158

143159
self.angles_2rth0 = 2. * np.reciprocal(self.angles_th0)
144160

145161

146162

147163
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+
150188

151189

152190
# ....
@@ -173,6 +211,8 @@ def _match_dihed(self,d4types,d9types):
173211

174212

175213

214+
# XXX: unmatched bonds/angles/dihedrals (i.e. nans in their properties) are not handled yet
215+
176216
# geoms[atom][xyz][conf]
177217
def _ic_bonds(self,geoms):
178218
out = np.empty([self.bonds.shape[0],geoms.shape[2]],dtype=np.float32)
@@ -191,15 +231,64 @@ def _ic_angles(self,geoms):
191231
for i in range(self.angles.shape[0]):
192232
v1 = geoms[self.angles[i,0],:,:] - geoms[self.angles[i,1],:,:]
193233
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)
196236
dot = np.einsum('ij,ij->j',v1,v2)
197-
aa = np.arccos(dot * rn1 * rn2)
237+
dot /= n1 * n2
238+
aa = np.arccos(dot)
198239
out[i] = (aa - .75 * self.angles_th0[i]) * self.angles_2rth0[i] # map 0.75 a0 -- 1.25 a0 to 0 -- 1
199240

200241
return out
201242

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
203292

204293
def intcoord(self,geoms):
205294
if self.nb is not None:
@@ -210,6 +299,8 @@ def intcoord(self,geoms):
210299
return np.concatenate((
211300
self._ic_bonds(geoms),
212301
self._ic_angles(geoms),
302+
self._ic_dihed4(geoms),
303+
self._ic_dihed9(geoms),
213304
nb
214305
),axis=0)
215306

@@ -228,7 +319,9 @@ def intcoord(self,geoms):
228319
print(mol.dihed9)
229320

230321

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)
232325

233326
print(mol.bonds_b0)
234327
print(mol.angles_th0)

0 commit comments

Comments
 (0)