moltric 0.0.2__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.
- moltric/__init__.py +5 -0
- moltric/arbalign.py +698 -0
- moltric/cli.py +181 -0
- moltric/compare_methods.py +505 -0
- moltric/compare_methods_assume_homonuclear.py +544 -0
- moltric/moltric.py +948 -0
- moltric/otmol_alignment.py +836 -0
- moltric/qapot.py +576 -0
- moltric-0.0.2.dist-info/METADATA +130 -0
- moltric-0.0.2.dist-info/RECORD +13 -0
- moltric-0.0.2.dist-info/WHEEL +5 -0
- moltric-0.0.2.dist-info/entry_points.txt +2 -0
- moltric-0.0.2.dist-info/top_level.txt +1 -0
moltric/__init__.py
ADDED
moltric/arbalign.py
ADDED
|
@@ -0,0 +1,698 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import sys
|
|
3
|
+
import numpy as np
|
|
4
|
+
#import hungarian
|
|
5
|
+
from scipy.optimize import linear_sum_assignment
|
|
6
|
+
from scipy.spatial.distance import pdist, squareform # Kazuumi addition
|
|
7
|
+
from collections import Counter
|
|
8
|
+
import operator
|
|
9
|
+
import argparse
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def kabsch(A, B):
|
|
13
|
+
"""
|
|
14
|
+
Kabsch Algorithm as implemented by Jimmy Charnley Kromann
|
|
15
|
+
|
|
16
|
+
Calculate RMSD between two XYZ files
|
|
17
|
+
|
|
18
|
+
by: Jimmy Charnley Kromann <jimmy@charnley.dk> and
|
|
19
|
+
Lars Andersen Bratholm <larsbratholm@gmail.com>
|
|
20
|
+
project: https://github.com/charnley/rmsd
|
|
21
|
+
license: https://github.com/charnley/rmsd/blob/master/LICENSE
|
|
22
|
+
|
|
23
|
+
A - set of coordinates
|
|
24
|
+
B - set of coordinates
|
|
25
|
+
|
|
26
|
+
Performs the kabsch algorithm to calculate the RMSD between A and B
|
|
27
|
+
|
|
28
|
+
Returns an RMSD
|
|
29
|
+
"""
|
|
30
|
+
A_new = np.array(A)
|
|
31
|
+
A_new = A_new - sum(A_new) / len(A_new)
|
|
32
|
+
A = A_new
|
|
33
|
+
B_new = np.array(B)
|
|
34
|
+
B_new = B_new - sum(B_new) / len(B_new)
|
|
35
|
+
B = B_new
|
|
36
|
+
|
|
37
|
+
# Compute covariance matrix
|
|
38
|
+
C = np.dot(np.transpose(A), B)
|
|
39
|
+
|
|
40
|
+
# Compute singular value decomposition (SVD)
|
|
41
|
+
V, S, W = np.linalg.svd(C)
|
|
42
|
+
d = (np.linalg.det(V) * np.linalg.det(W)) < 0.0
|
|
43
|
+
|
|
44
|
+
if d:
|
|
45
|
+
S[-1] = -S[-1]
|
|
46
|
+
V[:, -1] = -V[:, -1]
|
|
47
|
+
|
|
48
|
+
# Compute rotation matrix
|
|
49
|
+
U = np.dot(V, W)
|
|
50
|
+
|
|
51
|
+
# Rotate A
|
|
52
|
+
A = np.dot(A, U)
|
|
53
|
+
|
|
54
|
+
return rmsd(A, B)
|
|
55
|
+
|
|
56
|
+
def rmsd(V, W):
|
|
57
|
+
"""
|
|
58
|
+
V - set of coordinates
|
|
59
|
+
W - set of coordinates
|
|
60
|
+
|
|
61
|
+
Returns root-mean-square deviation from two sets of vectors V and W.
|
|
62
|
+
"""
|
|
63
|
+
D = len(V[0])
|
|
64
|
+
N = len(V)
|
|
65
|
+
rmsd = 0.0
|
|
66
|
+
for v, w in zip(V, W):
|
|
67
|
+
rmsd += sum([(v[i]-w[i])**2.0 for i in range(D)])
|
|
68
|
+
return np.sqrt(rmsd/N)
|
|
69
|
+
|
|
70
|
+
def read_xyz(filename, noHydrogens):
|
|
71
|
+
"""
|
|
72
|
+
Reads an xyz file into lists
|
|
73
|
+
filename - name of xyz file
|
|
74
|
+
noHydrogens - if true, hydrogens are ignored; if not, hydrogens are included
|
|
75
|
+
|
|
76
|
+
Returns a tuple (a, b)
|
|
77
|
+
where a is a list of coordinate labels and b is a set of coordinates
|
|
78
|
+
(i.e) a = ["O", "H", "H"], b = [[x0,y0,z0],[x1,y1,z1],[x2,y2,z2]]
|
|
79
|
+
"""
|
|
80
|
+
xyz = open(filename, "r")
|
|
81
|
+
num_atoms = int(xyz.readline().strip())
|
|
82
|
+
xyz.readline()
|
|
83
|
+
|
|
84
|
+
unsorted_labels = []
|
|
85
|
+
unsorted_coords = []
|
|
86
|
+
for i in range(num_atoms):
|
|
87
|
+
line = xyz.readline().strip().split()
|
|
88
|
+
#if noHydrogens and line[0].upper() == "H" :
|
|
89
|
+
if noHydrogens and line[0].upper().startswith("H") :
|
|
90
|
+
continue
|
|
91
|
+
else:
|
|
92
|
+
unsorted_labels.append(line[0].upper())
|
|
93
|
+
unsorted_coords.append([float(line[1]), float(line[2]), float(line[3])])
|
|
94
|
+
xyz.close()
|
|
95
|
+
NA = len(unsorted_labels)
|
|
96
|
+
return unsorted_labels, unsorted_coords, NA
|
|
97
|
+
|
|
98
|
+
def sorted_xyz(filename, noHydrogens):
|
|
99
|
+
"""
|
|
100
|
+
Reads an xyz file into lists
|
|
101
|
+
filename - name of xyz file
|
|
102
|
+
noHydrogens - if true, hydrogens are ignored; if not, hydrogens are included
|
|
103
|
+
|
|
104
|
+
Returns a tuple (a, b) sorted by atom labels and coordinates
|
|
105
|
+
where a is a list of coordinate labels and b is a set of coordinates
|
|
106
|
+
(i.e) a = ["O", "H", "H"], b = [[x0,y0,z0],[x1,y1,z1],[x2,y2,z2]]
|
|
107
|
+
Sorts the file by atom labels first and coordinates second
|
|
108
|
+
such that atoms of the same label/type are grouped together
|
|
109
|
+
"""
|
|
110
|
+
xyz = open(filename, "r")
|
|
111
|
+
num_atoms = int(xyz.readline().strip())
|
|
112
|
+
xyz.readline()
|
|
113
|
+
|
|
114
|
+
sortedlabels = []
|
|
115
|
+
sortedcoords = []
|
|
116
|
+
sortedorder = []
|
|
117
|
+
sortedlines = []
|
|
118
|
+
atomcount = 0
|
|
119
|
+
for i in range(num_atoms):
|
|
120
|
+
line = xyz.readline().strip().split()
|
|
121
|
+
if noHydrogens and line[0].upper().startswith("H") :
|
|
122
|
+
continue
|
|
123
|
+
else:
|
|
124
|
+
sortedlines.append([line[0].upper(),float(line[1]), float(line[2]), float(line[3]), atomcount])
|
|
125
|
+
atomcount += 1
|
|
126
|
+
xyz.close()
|
|
127
|
+
|
|
128
|
+
# sort by element followed by first coordinate (x) then second coordinate (y)
|
|
129
|
+
sortedlines.sort(key=lambda x: (x[0],x[1],x[2]))
|
|
130
|
+
for i in range(atomcount):
|
|
131
|
+
sortedlabels.append(sortedlines[i][0])
|
|
132
|
+
sortedcoords.append([float(sortedlines[i][1]), float(sortedlines[i][2]), float(sortedlines[i][3])])
|
|
133
|
+
sortedorder.append(sortedlines[i][4])
|
|
134
|
+
xyz.close()
|
|
135
|
+
#print sortedlabels
|
|
136
|
+
NA = len(sortedlabels)
|
|
137
|
+
return sortedlabels, sortedcoords, NA, sortedorder
|
|
138
|
+
|
|
139
|
+
def sorted_coords(coords, z):
|
|
140
|
+
"""
|
|
141
|
+
Reads an xyz file into lists
|
|
142
|
+
filename - name of xyz file
|
|
143
|
+
noHydrogens - if true, hydrogens are ignored; if not, hydrogens are included
|
|
144
|
+
|
|
145
|
+
Returns a tuple (a, b) sorted by atom labels and coordinates
|
|
146
|
+
where a is a list of coordinate labels and b is a set of coordinates
|
|
147
|
+
(i.e) a = ["O", "H", "H"], b = [[x0,y0,z0],[x1,y1,z1],[x2,y2,z2]]
|
|
148
|
+
Sorts the file by atom labels first and coordinates second
|
|
149
|
+
such that atoms of the same label/type are grouped together
|
|
150
|
+
"""
|
|
151
|
+
|
|
152
|
+
num_atoms = coords.shape[0]
|
|
153
|
+
|
|
154
|
+
sortedlabels = []
|
|
155
|
+
sortedcoords = []
|
|
156
|
+
sortedorder = []
|
|
157
|
+
sortedlines = []
|
|
158
|
+
atomcount = 0
|
|
159
|
+
for i in range(num_atoms):
|
|
160
|
+
# line = xyz.readline().strip().split()
|
|
161
|
+
# if noHydrogens and line[0].upper().startswith("H") :
|
|
162
|
+
# continue
|
|
163
|
+
# else:
|
|
164
|
+
# sortedlines.append([line[0].upper(),float(line[1]), float(line[2]), float(line[3]), atomcount])
|
|
165
|
+
sortedlines.append([z[i], coords[i][0], coords[i][1], coords[i][2], atomcount])
|
|
166
|
+
atomcount += 1
|
|
167
|
+
|
|
168
|
+
# sort by element followed by first coordinate (x) then second coordinate (y)
|
|
169
|
+
sortedlines.sort(key=lambda x: (x[0],x[1],x[2]))
|
|
170
|
+
for i in range(atomcount):
|
|
171
|
+
sortedlabels.append(sortedlines[i][0])
|
|
172
|
+
sortedcoords.append([float(sortedlines[i][1]), float(sortedlines[i][2]), float(sortedlines[i][3])])
|
|
173
|
+
sortedorder.append(sortedlines[i][4])
|
|
174
|
+
#print sortedlabels
|
|
175
|
+
NA = len(sortedlabels)
|
|
176
|
+
return sortedlabels, sortedcoords, NA, sortedorder
|
|
177
|
+
|
|
178
|
+
def parse_for_atom(labels, coords, atom):
|
|
179
|
+
"""
|
|
180
|
+
labels - a list of coordinate labels
|
|
181
|
+
coords - a set of coordinates
|
|
182
|
+
atom - the atom that is to be parsed
|
|
183
|
+
|
|
184
|
+
Returns a set of coordinates corresponding to parsed atom
|
|
185
|
+
"""
|
|
186
|
+
atom_coords = []
|
|
187
|
+
for i in range(len(labels)):
|
|
188
|
+
if labels[i] == atom:
|
|
189
|
+
atom_coords.append(coords[i])
|
|
190
|
+
return atom_coords
|
|
191
|
+
|
|
192
|
+
def transform_coords(coords, swap, reflect):
|
|
193
|
+
"""
|
|
194
|
+
coords - a set of coordinates
|
|
195
|
+
swap - the swap transformation (i.e. (0, 2, 1) --> (x, z, y))
|
|
196
|
+
reflect - the reflection transformation (i.e. (1, -1, 1) --> (x, -y, z))
|
|
197
|
+
|
|
198
|
+
Returns the transformed coordinates
|
|
199
|
+
"""
|
|
200
|
+
new_coords = []
|
|
201
|
+
for i in range(len(coords)):
|
|
202
|
+
new_coords.append([coords[i][swap[0]]*reflect[0], \
|
|
203
|
+
coords[i][swap[1]]*reflect[1], \
|
|
204
|
+
coords[i][swap[2]]*reflect[2]])
|
|
205
|
+
return new_coords
|
|
206
|
+
|
|
207
|
+
def transform_atoms(coords, swap, reflect, atom_indices):
|
|
208
|
+
"""
|
|
209
|
+
coords - a set of coordinates
|
|
210
|
+
swap - the swap transformation (i.e. (0, 2, 1) --> (x, z, y))
|
|
211
|
+
reflect - the reflection transformation (i.e. (1, -1, 1) --> (x, -y, z))
|
|
212
|
+
atom_indices - indices of all desired atoms in [coords]
|
|
213
|
+
|
|
214
|
+
Returns coordinates after transforming specific atoms
|
|
215
|
+
"""
|
|
216
|
+
new_coords = [x[:] for x in coords]
|
|
217
|
+
for i in atom_indices:
|
|
218
|
+
new_coords[i][0] = coords[i][swap[0]]*reflect[0]
|
|
219
|
+
new_coords[i][1] = coords[i][swap[1]]*reflect[1]
|
|
220
|
+
new_coords[i][2] = coords[i][swap[2]]*reflect[2]
|
|
221
|
+
return new_coords
|
|
222
|
+
|
|
223
|
+
def permute_coords(coords, permutation):
|
|
224
|
+
"""
|
|
225
|
+
UNUSED at the moment
|
|
226
|
+
|
|
227
|
+
coords - a set of coordinates
|
|
228
|
+
permutation - permutation of atoms (i.e. [0, 2, 3, 1])
|
|
229
|
+
|
|
230
|
+
Returns the permuted coordinates
|
|
231
|
+
"""
|
|
232
|
+
new_coords = []
|
|
233
|
+
for i in permutation:
|
|
234
|
+
new_coords.append(coords[i])
|
|
235
|
+
return new_coords
|
|
236
|
+
|
|
237
|
+
def permute_atoms(coords, permutation, atom_indices):
|
|
238
|
+
"""
|
|
239
|
+
coords - a set of coordinates
|
|
240
|
+
permuation - a permutation of atoms
|
|
241
|
+
atom_indices - indices of all desired atoms in [coords]
|
|
242
|
+
|
|
243
|
+
Returns the coordinates after permuting just the specified atom
|
|
244
|
+
"""
|
|
245
|
+
new_coords = coords[:]
|
|
246
|
+
for i in range(len(permutation)):
|
|
247
|
+
j = atom_indices[permutation[i]]
|
|
248
|
+
k = atom_indices[i]
|
|
249
|
+
new_coords[k] = coords[j]
|
|
250
|
+
return new_coords
|
|
251
|
+
|
|
252
|
+
def permute_all_atoms(labels, coords, permutation):
|
|
253
|
+
"""
|
|
254
|
+
labels - atom labels
|
|
255
|
+
coords - a set of coordinates
|
|
256
|
+
permuation - a permutation of atoms
|
|
257
|
+
|
|
258
|
+
Returns the permuted labels and coordinates
|
|
259
|
+
"""
|
|
260
|
+
new_coords = coords[:]
|
|
261
|
+
new_labels = labels[:]
|
|
262
|
+
for i in range(len(permutation)):
|
|
263
|
+
new_coords[permutation[i]] = coords[i]
|
|
264
|
+
new_labels[permutation[i]] = labels[i]
|
|
265
|
+
return new_labels, new_coords
|
|
266
|
+
|
|
267
|
+
def get_atom_indices(labels, atom):
|
|
268
|
+
"""
|
|
269
|
+
labels - a list of coordinate labels ("Elements")
|
|
270
|
+
atom - the atom whose indices in labels are sought
|
|
271
|
+
Returns a list of all location of [atom] in [labels]
|
|
272
|
+
"""
|
|
273
|
+
indices = []
|
|
274
|
+
for i in range(len(labels)):
|
|
275
|
+
if labels[i] == atom:
|
|
276
|
+
indices.append(i)
|
|
277
|
+
return indices
|
|
278
|
+
|
|
279
|
+
def coords_to_xyz(labels, coords):
|
|
280
|
+
"""
|
|
281
|
+
Displays coordinates
|
|
282
|
+
"""
|
|
283
|
+
s = ""
|
|
284
|
+
for i in range(len(coords)):
|
|
285
|
+
s += labels[i] + " " + str(coords[i][0]) + " " + \
|
|
286
|
+
str(coords[i][1]) + " " + str(coords[i][2]) + "\n"
|
|
287
|
+
return s
|
|
288
|
+
|
|
289
|
+
def write_to_xyz(num_atoms, name, labels, coords):
|
|
290
|
+
"""
|
|
291
|
+
num_atoms - number of atoms
|
|
292
|
+
name - name of file to write coordinates to
|
|
293
|
+
labels - a list of coordinate labels ("Elements")
|
|
294
|
+
coords - a list of XYZ coordinates
|
|
295
|
+
|
|
296
|
+
Writes the Cartesian coordinates to a file called 'name'
|
|
297
|
+
"""
|
|
298
|
+
xyz = open(name, "w")
|
|
299
|
+
xyz.write(str(num_atoms) + "\n")
|
|
300
|
+
xyz.write(name + "\n")
|
|
301
|
+
xyz.write(coords_to_xyz(labels, coords))
|
|
302
|
+
xyz.close()
|
|
303
|
+
|
|
304
|
+
#def main():
|
|
305
|
+
def arbalign(ref_coords,target_coords,inputz,inverse_flag=True,calculate_RMSD_instead=False,print_flag=False):
|
|
306
|
+
|
|
307
|
+
description = """
|
|
308
|
+
This code uses the Kuhn-Munkres or Hungarian algorithm to optimally align two
|
|
309
|
+
arbitrarily ordered isomers. Given two isomers A and B whose Cartesian
|
|
310
|
+
coordinates are given in XYZ format, it will optimally align B on A to minimize
|
|
311
|
+
the Kabsch root-mean-square deviation (RMSD) between structure A and B after
|
|
312
|
+
|
|
313
|
+
1) a Kuhn-Munkres assignment/reordering (quick)
|
|
314
|
+
2) a Kuhn-Munkres assignment/reordering factoring in axes swaps and reflections (~48x slower)
|
|
315
|
+
|
|
316
|
+
We recommend the second method although the first one would still be better
|
|
317
|
+
than RMSD calculations without atom reorderings.
|
|
318
|
+
|
|
319
|
+
A web server with this implementation is available at http://www.arbalign.org
|
|
320
|
+
|
|
321
|
+
While this script is kept as minimal as possible in order to ensure ease of use
|
|
322
|
+
and portability, it does require these two Python packages beyond what's
|
|
323
|
+
included in standard python installations.
|
|
324
|
+
|
|
325
|
+
1) Python Numpy module
|
|
326
|
+
2) Python Hungarian module by Harold Cooper
|
|
327
|
+
(Hungarian: Munkres' Algorithm for the Linear Assignment Problem in Python.
|
|
328
|
+
https://github.com/Hrldcpr/Hungarian)
|
|
329
|
+
This is a wrapper to a fast C++ implementation of the Kuhn-Munkres algorithm.
|
|
330
|
+
The installation instructions are described at https://github.com/Hrldcpr/Hungarian
|
|
331
|
+
|
|
332
|
+
Other optional tools are:
|
|
333
|
+
|
|
334
|
+
1) PrinCoords.py - using principal coordinates generally yields better
|
|
335
|
+
alignment (lower RMSDs). A Python script to convert molecules from arbitrary
|
|
336
|
+
to principal coordinate system is included.
|
|
337
|
+
|
|
338
|
+
2) In cases where one wants to use atom types including connectivity and
|
|
339
|
+
hybridization information, it is necessary to use OpenBabel to convert the
|
|
340
|
+
Cartesian coordinates to SYBYL Mol2 (sy2) and MNA (mna) formats.
|
|
341
|
+
|
|
342
|
+
The best way to take advantage of these two optional tools is probably to use
|
|
343
|
+
the attached driver script (ArbAlign-driver.py) The syntax looks like
|
|
344
|
+
|
|
345
|
+
Usage: ArbAlign-driver.py -<flag> <filename_1.xyz> <filename_2.xyz>"
|
|
346
|
+
: where the <flag> is "
|
|
347
|
+
: -l match by atom or element label "
|
|
348
|
+
: -t match by SYBYL atom type"
|
|
349
|
+
: -c match by NMA atom connectivity type"
|
|
350
|
+
"
|
|
351
|
+
Eg.: ArbAlign-driver.py -b -N cluster1.xyz cluster2.xyz"
|
|
352
|
+
: ArbAlign-driver.py -T cluster1.xyz cluster2.xyz"
|
|
353
|
+
: ArbAlign-driver.py -C cluster1.xyz cluster2.xyz"
|
|
354
|
+
"
|
|
355
|
+
This matches the Cartesian coordinates of the file1 and file2 using the \
|
|
356
|
+
Kuhn-Munkres algorithm based on atom labels (-l), type (-t) or \
|
|
357
|
+
connectivity (-t). "
|
|
358
|
+
It produces s-file1.xyz and s-file2-matched.xyz which are the sorted and \
|
|
359
|
+
matched file1 and file2.xyz, respectively."
|
|
360
|
+
|
|
361
|
+
"""
|
|
362
|
+
|
|
363
|
+
epilog = """
|
|
364
|
+
The code will provide the following:
|
|
365
|
+
1) The initial Kabsch RMSD
|
|
366
|
+
2) The final Kabsch RMSD after the application of the Kuhn-Munkres algorithm
|
|
367
|
+
3) The coordinates corresponding to the best alignment of B on A to a file called B-aligned_to-A.xyz
|
|
368
|
+
|
|
369
|
+
If you find this script useful for any publishable work, please cite the corresponding paper:
|
|
370
|
+
Berhane Temelso, Joel M. Mabey, Toshiro Kubota, Nana Appiah-padi, George C. Shields
|
|
371
|
+
J. Chem. Info. Model. 2017, 57(5), 1045-1054
|
|
372
|
+
"""
|
|
373
|
+
|
|
374
|
+
z = [str(x) for x in inputz]
|
|
375
|
+
for i in range(len(z)):
|
|
376
|
+
if z[i] == "1": z[i] = "H"
|
|
377
|
+
|
|
378
|
+
a_labels = z.copy()
|
|
379
|
+
a_coords = ref_coords.copy()
|
|
380
|
+
b_labels = z.copy()
|
|
381
|
+
b_coords = target_coords.copy()
|
|
382
|
+
|
|
383
|
+
b_init_labels = b_labels
|
|
384
|
+
b_init_coords = b_coords
|
|
385
|
+
|
|
386
|
+
#Calculate the initial unsorted all-atom RMSD as a baseline
|
|
387
|
+
A_all = np.array(a_coords)
|
|
388
|
+
B_all = np.array(b_coords)
|
|
389
|
+
NA_a = A_all.shape[0]
|
|
390
|
+
NA_b = B_all.shape[0]
|
|
391
|
+
|
|
392
|
+
origDMA = pdist(A_all, 'euclidean')
|
|
393
|
+
origDMB = pdist(B_all, 'euclidean')
|
|
394
|
+
if inverse_flag: # Normally, use inverse distances
|
|
395
|
+
origDMA = squareform(1.0e0/origDMA, checks=False)
|
|
396
|
+
origDMB = squareform(1.0e0/origDMB, checks=False)
|
|
397
|
+
else:
|
|
398
|
+
origDMA = squareform(origDMA, checks=False)
|
|
399
|
+
origDMB = squareform(origDMB, checks=False)
|
|
400
|
+
|
|
401
|
+
#If the two molecules are of the same size, get
|
|
402
|
+
if NA_a == NA_b:
|
|
403
|
+
if calculate_RMSD_instead:
|
|
404
|
+
InitRMSD_unsorted = kabsch(A_all,B_all)
|
|
405
|
+
else:
|
|
406
|
+
InitRMSD_unsorted = np.sum((origDMA - origDMB)**2)
|
|
407
|
+
|
|
408
|
+
else:
|
|
409
|
+
# print "Error: unequal number of atoms. " + str(NA_a) + " is not equal to " + str(NA_b)
|
|
410
|
+
# sys.exit()
|
|
411
|
+
raise ValueError("Bad ref and target structures")
|
|
412
|
+
|
|
413
|
+
"""
|
|
414
|
+
If the initial RMSD is zero (<0.001), then the structured are deemed identical already and
|
|
415
|
+
we don't need to do any reordering, swapping, or reflections
|
|
416
|
+
"""
|
|
417
|
+
if InitRMSD_unsorted < 0.001:
|
|
418
|
+
# print "The structures are identical. No reordering, swapping or reflection needed."
|
|
419
|
+
# print "All-atom RMSD: %2.3f" % float(InitRMSD_unsorted)
|
|
420
|
+
# name = str(args.xyz2.split(".xyz")[0]) + "-aligned_to-" + str(args.xyz1)
|
|
421
|
+
# write_to_xyz(num_atoms, name, b_labels, b_coords)
|
|
422
|
+
# print "Best alignment of " + str(args.xyz2) + " on " + str(args.xyz1) + " is written to " + str(name)
|
|
423
|
+
# sys.exit()
|
|
424
|
+
# return A_all, B_all, np.arange(A_all.shape[0])
|
|
425
|
+
return [[InitRMSD_unsorted, (0, 1, 2), (1, 1, 1), np.arange(NA_a)]]
|
|
426
|
+
|
|
427
|
+
#If ignoring hydrogens, the coordinates are written to a file with "noHydrogens.xyz" ending
|
|
428
|
+
# if args.noHydrogens:
|
|
429
|
+
# name = str(args.xyz1.split(".xyz")[0]) + "-noHydrogens" + ".xyz"
|
|
430
|
+
# write_to_xyz(NA_a, name, a_labels, a_coords)
|
|
431
|
+
# print "Coordinates of " + str(args.xyz1) + " without hydrogens is written to " + str(name)
|
|
432
|
+
|
|
433
|
+
"""
|
|
434
|
+
Read in the original coordinates and labels of xyz1 and xyz2,
|
|
435
|
+
and sort them by atom labels so that atoms of the same label/name are grouped together
|
|
436
|
+
|
|
437
|
+
Then, count how many types of atoms, and determine their numerical frequency
|
|
438
|
+
"""
|
|
439
|
+
# a_labels, a_coords, NA_a, order = sorted_xyz(args.xyz1, args.noHydrogens)
|
|
440
|
+
a_labels, a_coords, NA_a, order = sorted_coords(A_all,z)
|
|
441
|
+
Uniq_a = list(set(a_labels))
|
|
442
|
+
list.sort(Uniq_a)
|
|
443
|
+
N_uniq_a = len(Uniq_a)
|
|
444
|
+
Atom_freq_a = dict(Counter(a_labels))
|
|
445
|
+
|
|
446
|
+
# b_labels, b_coords, NA_b, junk = sorted_xyz(args.xyz2, args.noHydrogens)
|
|
447
|
+
b_labels, b_coords, NA_b, junk = sorted_coords(B_all,z)
|
|
448
|
+
Uniq_b = list(set(b_labels))
|
|
449
|
+
list.sort(Uniq_b)
|
|
450
|
+
N_uniq_b = len(Uniq_b)
|
|
451
|
+
Atom_freq_b = dict(Counter(b_labels))
|
|
452
|
+
|
|
453
|
+
"""
|
|
454
|
+
If the number and type of atoms in the two structures are not equal, exit with
|
|
455
|
+
an error message
|
|
456
|
+
"""
|
|
457
|
+
if (NA_a == NA_b) & (Uniq_a == Uniq_b) & (Atom_freq_a == Atom_freq_b) :
|
|
458
|
+
num_atoms = NA_a
|
|
459
|
+
num_uniq = N_uniq_a
|
|
460
|
+
Uniq = Uniq_a #list(set(a_labels))
|
|
461
|
+
Atom_freq = Atom_freq_a
|
|
462
|
+
#del Atom_freq['H']
|
|
463
|
+
#print Atom_freq
|
|
464
|
+
Sorted_Atom_freq = sorted(Atom_freq.items(), key=operator.itemgetter(1), reverse=True)
|
|
465
|
+
# print Sorted_Atom_freq
|
|
466
|
+
"""
|
|
467
|
+
Atom = sorted(Uniq, key=operator.itemgetter(0), reverse=True)
|
|
468
|
+
print Atom
|
|
469
|
+
print num_uniq
|
|
470
|
+
"""
|
|
471
|
+
else:
|
|
472
|
+
# print "Unequal number or type of atoms. Exiting ... "
|
|
473
|
+
# print "Atoms in 1st molecule" +str(Atom_freq_a)
|
|
474
|
+
# print "Atoms in 2nd molecule" +str(Atom_freq_b)
|
|
475
|
+
# sys.exit()
|
|
476
|
+
raise ValueError("Bad ref and target structures")
|
|
477
|
+
|
|
478
|
+
A_all = np.array(a_coords)
|
|
479
|
+
A_all = A_all - sum(A_all) / len(A_all)
|
|
480
|
+
B_all = np.array(b_coords)
|
|
481
|
+
B_all = B_all - sum(B_all) / len(B_all)
|
|
482
|
+
|
|
483
|
+
DMA = pdist(A_all, 'euclidean')
|
|
484
|
+
DMB = pdist(B_all, 'euclidean')
|
|
485
|
+
if inverse_flag: # Normally, use inverse distances
|
|
486
|
+
DMA = squareform(1.0e0/DMA, checks=False)
|
|
487
|
+
DMB = squareform(1.0e0/DMB, checks=False)
|
|
488
|
+
else:
|
|
489
|
+
DMA = squareform(DMA, checks=False)
|
|
490
|
+
DMB = squareform(DMB, checks=False)
|
|
491
|
+
|
|
492
|
+
if calculate_RMSD_instead:
|
|
493
|
+
InitRMSD_sorted = kabsch(A_all,B_all)
|
|
494
|
+
else:
|
|
495
|
+
InitRMSD_sorted = np.sum((DMA - DMB)**2)
|
|
496
|
+
|
|
497
|
+
"""
|
|
498
|
+
Dynamically generate hashes of coordinates and atom indices for every atom type
|
|
499
|
+
"""
|
|
500
|
+
a_Coords = {}
|
|
501
|
+
a_Indices = {}
|
|
502
|
+
b_Coords = {}
|
|
503
|
+
b_Indices = {}
|
|
504
|
+
Perm = {}
|
|
505
|
+
for i in range(len(Uniq)):
|
|
506
|
+
a_Coords[Uniq[i]] = 'a_' + str(Uniq[i]) + 'coords'
|
|
507
|
+
b_Coords[Uniq[i]] = 'b_' + str(Uniq[i]) + 'coords'
|
|
508
|
+
a_Indices[Uniq[i]] = 'a_' + str(Uniq[i]) + 'indices'
|
|
509
|
+
b_Indices[Uniq[i]] = 'b_' + str(Uniq[i]) + 'indices'
|
|
510
|
+
Perm[Uniq[i]] = 'perm_' + str(Uniq[i])
|
|
511
|
+
#print "Atom_freq is " + str(Atom_freq[Uniq[i]])
|
|
512
|
+
vars()[Perm[Uniq[i]]] = []
|
|
513
|
+
#vars()[Perm[Uniq[i]]] = build_perm(Atom_freq[Uniq[i]])
|
|
514
|
+
#for n in range(Atom_freq[Uniq[i]]):
|
|
515
|
+
# vars()[Perm[Uniq[i]]] += [(n,n)]
|
|
516
|
+
vars()[a_Coords[Uniq[i]]] = parse_for_atom(a_labels, a_coords, str(Uniq[i]))
|
|
517
|
+
vars()[a_Indices[Uniq[i]]] = get_atom_indices(a_labels, str(Uniq[i]))
|
|
518
|
+
#print Uniq[i]
|
|
519
|
+
#print vars()[a_Coords[Uniq[i]]]
|
|
520
|
+
vars()[b_Coords[Uniq[i]]] = parse_for_atom(b_labels, b_coords, str(Uniq[i]))
|
|
521
|
+
vars()[b_Indices[Uniq[i]]] = get_atom_indices(b_labels, str(Uniq[i]))
|
|
522
|
+
#print vars()[b_Indices[Uniq[i]]]
|
|
523
|
+
|
|
524
|
+
l = 0
|
|
525
|
+
A = np.array(vars()[a_Coords[Uniq[l]]])
|
|
526
|
+
A = A - sum(A) / len(A)
|
|
527
|
+
B = np.array(vars()[b_Coords[Uniq[l]]])
|
|
528
|
+
B = B - sum(B) / len(B)
|
|
529
|
+
|
|
530
|
+
'''
|
|
531
|
+
For each atom type, we can do a Kuhn-Munkres assignment in the initial
|
|
532
|
+
coordinates or the many swaps and reflections thereof
|
|
533
|
+
|
|
534
|
+
If a single Kuhn-Munkres assignment is requested with a -s or --simple flag,
|
|
535
|
+
no swaps and reflections are considered. Otherwise, the default is to perform
|
|
536
|
+
a combination of 6 axes swaps and 8 reflections and do Kuhn-Munkres assignment
|
|
537
|
+
on all 48 combinations.
|
|
538
|
+
'''
|
|
539
|
+
if False: # args.simple: # will do nothing
|
|
540
|
+
swaps = [(0, 1, 2)]
|
|
541
|
+
reflects = [(1, 1, 1)]
|
|
542
|
+
else: # will perform swaps and reflections
|
|
543
|
+
swaps = [(0, 1, 2), (0, 2, 1), (1, 0, 2), (1, 2, 0), (2, 0, 1), (2, 1, 0)]
|
|
544
|
+
reflects = [(1, 1, 1), (-1, 1, 1), (1, -1, 1), (1, 1, -1), \
|
|
545
|
+
(-1, -1, 1), (-1, 1, -1), (1, -1, -1), (-1, -1, -1)]
|
|
546
|
+
B_t = []
|
|
547
|
+
for i in swaps:
|
|
548
|
+
for j in reflects:
|
|
549
|
+
B_t.append([transform_coords(B, i, j), i, j])
|
|
550
|
+
|
|
551
|
+
rmsds = []
|
|
552
|
+
# Performs the munkres algorithm on each set of transformed coordinates
|
|
553
|
+
for i in range(len(B_t)):
|
|
554
|
+
l = 0
|
|
555
|
+
cost_matrix = np.array([[np.linalg.norm(a - b) \
|
|
556
|
+
for b in B_t[i][0]] for a in A])
|
|
557
|
+
#LAP = hungarian.lap(cost_matrix)
|
|
558
|
+
LAP0, LAP1 = linear_sum_assignment(cost_matrix)
|
|
559
|
+
vars()[Perm[Uniq[l]]] = []
|
|
560
|
+
for j in range(len(LAP1)):
|
|
561
|
+
vars()[Perm[Uniq[l]]] += [(j,LAP1[j])]
|
|
562
|
+
vars()[Perm[Uniq[l]]] = sorted( vars()[Perm[Uniq[l]]], key = lambda x: x[0])
|
|
563
|
+
vars()[Perm[Uniq[l]]] = [x[1] for x in vars()[Perm[Uniq[l]]]]
|
|
564
|
+
|
|
565
|
+
top_perms = [(vars()[Perm[Uniq[l]]], vars()[b_Indices[Uniq[l]]])]
|
|
566
|
+
|
|
567
|
+
# If there's more than one atom type, loop through each unique atom type
|
|
568
|
+
if num_uniq == 1:
|
|
569
|
+
#print str(vars()[b_Indices[Uniq[l]]])
|
|
570
|
+
b_perm = permute_atoms(b_coords, vars()[Perm[Uniq[l]]], vars()[b_Indices[Uniq[l]]])
|
|
571
|
+
b_final = transform_coords(b_perm, B_t[i][1], B_t[i][2])
|
|
572
|
+
# if args.verbose:
|
|
573
|
+
# print str(Uniq[l]) + " Swap: " + str(B_t[i][1]) + " Refl: " + str(B_t[i][2]) + " RMSD: " + str(kabsch(a_coords, b_final)) + " " + str(vars()[Perm[Uniq[l]]])
|
|
574
|
+
|
|
575
|
+
if True:
|
|
576
|
+
permDM = np.arange(NA_a)
|
|
577
|
+
for subperm in top_perms:
|
|
578
|
+
oldpermDM = permDM.copy()
|
|
579
|
+
permDM[subperm[1]] = oldpermDM[np.array(subperm[1])[subperm[0]]]
|
|
580
|
+
|
|
581
|
+
invpermDM = np.arange(NA_a)
|
|
582
|
+
invpermDM[order] = np.arange(NA_a)
|
|
583
|
+
newpermDM = np.array(junk)[permDM][invpermDM]
|
|
584
|
+
|
|
585
|
+
if calculate_RMSD_instead:
|
|
586
|
+
newDMD = kabsch(a_coords, b_final)
|
|
587
|
+
else:
|
|
588
|
+
newDMD = np.sum((origDMA - origDMB[newpermDM,:][:,newpermDM])**2)
|
|
589
|
+
|
|
590
|
+
# rmsds.append([kabsch(a_coords, b_final), B_t[i][1], B_t[i][2], b_final, vars()[Perm[Uniq[l]]]])
|
|
591
|
+
rmsds.append([newDMD, B_t[i][1], B_t[i][2], newpermDM])
|
|
592
|
+
|
|
593
|
+
rmsds = sorted(rmsds, key = lambda x: x[0])
|
|
594
|
+
else:
|
|
595
|
+
perms = top_perms.copy()
|
|
596
|
+
|
|
597
|
+
#print str(vars()[b_Indices[Uniq[l]]])
|
|
598
|
+
b_perm = permute_atoms(b_coords, vars()[Perm[Uniq[l]]], vars()[b_Indices[Uniq[l]]])
|
|
599
|
+
b_trans = transform_coords(b_perm, B_t[i][1], B_t[i][2])
|
|
600
|
+
#print str(b_trans)
|
|
601
|
+
#vars()[b_Coords[Uniq[l+1]]] = parse_for_atom(b_labels, b_trans, Uniq[l+1])
|
|
602
|
+
while l < num_uniq:
|
|
603
|
+
if l > 0:
|
|
604
|
+
vars()[b_Coords[Uniq[l]]] = parse_for_atom(b_labels, b_final, Uniq[l])
|
|
605
|
+
else:
|
|
606
|
+
vars()[b_Coords[Uniq[l]]] = parse_for_atom(b_labels, b_trans, Uniq[l])
|
|
607
|
+
lll = np.array(vars()[b_Coords[Uniq[l]]]) #; print("lol", l, lll.shape, lll)
|
|
608
|
+
mmm = np.array(vars()[a_Coords[Uniq[l]]]) #; print("lolol", mmm.shape, mmm)
|
|
609
|
+
cost_matrix = np.array([[np.linalg.norm(a- b) for b in lll] for a in mmm]) # Kazuumi change
|
|
610
|
+
# cost_matrix = np.array([[np.linalg.norm(a- b) \
|
|
611
|
+
# for b in np.array(vars()[b_Coords[Uniq[l]]])] \
|
|
612
|
+
# for a in np.array(vars()[a_Coords[Uniq[l]]])])
|
|
613
|
+
#LAP = hungarian.lap(cost_matrix)
|
|
614
|
+
LAP0, LAP1 = linear_sum_assignment(cost_matrix)
|
|
615
|
+
vars()[Perm[Uniq[l]]] = []
|
|
616
|
+
for k in range(len(LAP1)):
|
|
617
|
+
vars()[Perm[Uniq[l]]] += [(k,LAP1[k])]
|
|
618
|
+
vars()[Perm[Uniq[l]]] = sorted( vars()[Perm[Uniq[l]]], key = lambda x: x[0])
|
|
619
|
+
vars()[Perm[Uniq[l]]] = [x[1] for x in vars()[Perm[Uniq[l]]]]
|
|
620
|
+
#print str(vars()[b_Indices[Uniq[l]]])
|
|
621
|
+
b_final = permute_atoms(b_trans, vars()[Perm[Uniq[l]]], vars()[b_Indices[Uniq[l]]])
|
|
622
|
+
b_trans = b_final
|
|
623
|
+
l += 1
|
|
624
|
+
q = l - 1
|
|
625
|
+
# if args.verbose:
|
|
626
|
+
# print str(Uniq[q]) + " Swap: " + str(B_t[i][1]) + " Refl: " + str(B_t[i][2]) + " RMSD: " + str(kabsch(a_coords, b_final)) + " " + str(vars()[Perm[Uniq[q]]])
|
|
627
|
+
|
|
628
|
+
# rmsds.append([kabsch(a_coords, b_final), B_t[i][1], B_t[i][2], b_final])
|
|
629
|
+
perms.append((vars()[Perm[Uniq[q]]], vars()[b_Indices[Uniq[q]]]))
|
|
630
|
+
if True:
|
|
631
|
+
permDM = np.arange(NA_a)
|
|
632
|
+
for subperm in perms:
|
|
633
|
+
oldpermDM = permDM.copy()
|
|
634
|
+
permDM[subperm[1]] = oldpermDM[np.array(subperm[1])[subperm[0]]]
|
|
635
|
+
|
|
636
|
+
invpermDM = np.arange(NA_a)
|
|
637
|
+
invpermDM[order] = np.arange(NA_a)
|
|
638
|
+
newpermDM = np.array(junk)[permDM][invpermDM]
|
|
639
|
+
|
|
640
|
+
if False:
|
|
641
|
+
newDMB = pdist(b_final, 'euclidean')
|
|
642
|
+
newDMB = squareform(1.0e0/newDMB, checks=False)
|
|
643
|
+
DMD = np.sum((DMA - newDMB)**2)
|
|
644
|
+
|
|
645
|
+
if calculate_RMSD_instead:
|
|
646
|
+
newDMD = kabsch(a_coords, b_final)
|
|
647
|
+
else:
|
|
648
|
+
newDMD = np.sum((origDMA - origDMB[newpermDM,:][:,newpermDM])**2)
|
|
649
|
+
# DMD = np.sum((origDMA[order,:][:,order] - origDMB[junk,:][:,junk][permDM,:][:,permDM])**2)
|
|
650
|
+
# print(i, q, DMD, newDMD, "huh", perms, permDM) # Kazuumi test
|
|
651
|
+
|
|
652
|
+
# rmsds.append([DMD, B_t[i][1], B_t[i][2], b_final])
|
|
653
|
+
rmsds.append([newDMD, B_t[i][1], B_t[i][2], newpermDM])
|
|
654
|
+
|
|
655
|
+
rmsds = sorted(rmsds, key = lambda x: x[0])
|
|
656
|
+
#print "Permutation: " + str(vars()[Perm[Uniq[q]]])
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
if False: # not args.simple:
|
|
660
|
+
print("Swap Transform: " + str(rmsds[0][1]))
|
|
661
|
+
print("Reflection Transform: " + str(rmsds[0][2]))
|
|
662
|
+
|
|
663
|
+
#print "Permutation: " + str(rmsds[0][4])
|
|
664
|
+
FinalRMSD = float(rmsds[0][0])
|
|
665
|
+
if FinalRMSD < float(InitRMSD_unsorted):
|
|
666
|
+
# if not args.verbose:
|
|
667
|
+
# print "Please use the -v or --verbose options to see optimal reorderings"
|
|
668
|
+
if print_flag:
|
|
669
|
+
print("Initial unsorted RMSD: %2.3f" % float(InitRMSD_unsorted))
|
|
670
|
+
print("Initial sorted RMSD: %2.3f" % float(InitRMSD_sorted))
|
|
671
|
+
print("Best RMSD: %2.3f" % float(rmsds[0][0]))
|
|
672
|
+
else:
|
|
673
|
+
if print_flag:
|
|
674
|
+
print("The initial alignment is already optimal.")
|
|
675
|
+
print("Initial and final RMSD: %2.3f" % float(InitRMSD_unsorted))
|
|
676
|
+
|
|
677
|
+
# if args.noHydrogens:
|
|
678
|
+
# name = str(args.xyz2.split(".xyz")[0]) + "-aligned_to-" + \
|
|
679
|
+
# str(args.xyz1.split(".xyz")[0]) + "-noHydrogens.xyz"
|
|
680
|
+
# else:
|
|
681
|
+
# name = str(args.xyz2.split(".xyz")[0]) + "-aligned_to-" + str(args.xyz1)
|
|
682
|
+
|
|
683
|
+
# if FinalRMSD < float(InitRMSD_unsorted):
|
|
684
|
+
# b_final_labels, b_final_coords = permute_all_atoms(b_labels, rmsds[0][3], order)
|
|
685
|
+
# else:
|
|
686
|
+
# b_final_labels = b_init_labels
|
|
687
|
+
# b_final_coords = b_init_coords
|
|
688
|
+
|
|
689
|
+
if False:
|
|
690
|
+
newpermDM = rmsds[0][3]
|
|
691
|
+
newDMD = np.sum((origDMA - origDMB[newpermDM,:][:,newpermDM])**2)
|
|
692
|
+
print(newpermDM, newDMD)
|
|
693
|
+
|
|
694
|
+
return rmsds
|
|
695
|
+
|
|
696
|
+
# write_to_xyz(num_atoms, name, b_final_labels, b_final_coords)
|
|
697
|
+
# print("Best alignment of " + str(args.xyz2) + " with " + str(args.xyz1) + " is written to " + str(name))
|
|
698
|
+
|