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 ADDED
@@ -0,0 +1,5 @@
1
+ from .moltric import *
2
+ from .arbalign import arbalign # Kazuumi's python3 translation for ArbAlign
3
+ from .otmol_alignment import otmol_alignment # Copy of OTMol for benchmarking
4
+
5
+ __version__ = "0.0.2"
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
+