-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrmsd.py
196 lines (157 loc) · 5.56 KB
/
rmsd.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# encoding: utf-8
__doc__ = """
RMSD calculators and optimal rotation of sets of coordinates.
Two algorithms are provided:
1. The standard SVD decomposition using the numpy library.
2. The qcp quaternion-based method that doesn't need numpy.
The qcp method is particularly useful because it allows the
library to function without numpy such as when run with pypy.
"""
import v3
import pdbatoms
import math
import pyqcprot
try:
import numpy
is_numpy = True
except:
is_numpy = False
def numpy_svd_rmsd_rot(in_crds1, in_crds2):
"""
Returns rmsd and optional rotation between 2 sets of [nx3] arrays.
This requires numpy for svd decomposition.
The transform direction: transform(m, ref_crd) => target_crd.
"""
crds1 = numpy.array(in_crds1)
crds2 = numpy.array(in_crds2)
assert(crds1.shape[1] == 3)
assert(crds1.shape == crds2.shape)
n_vec = numpy.shape(crds1)[0]
correlation_matrix = numpy.dot(numpy.transpose(crds1), crds2)
v, s, w = numpy.linalg.svd(correlation_matrix)
is_reflection = (numpy.linalg.det(v) * numpy.linalg.det(w)) < 0.0
if is_reflection:
s[-1] = - s[-1]
E0 = sum(sum(crds1 * crds1)) + \
sum(sum(crds2 * crds2))
rmsd_sq = (E0 - 2.0*sum(s)) / float(n_vec)
rmsd_sq = max([rmsd_sq, 0.0])
rmsd = numpy.sqrt(rmsd_sq)
if is_reflection:
v[-1,:] = -v[-1,:]
rot33 = numpy.dot(v, w)
m = v3.identity()
m[:3,:3] = rot33.transpose()
return rmsd, m
def pyqcprot_rmsd_rot(crds1, crds2):
"""
Returns rmsd and optional rotation between 2 sets of [nx3] arrays.
This requires Joshua Adelman's pyqcrot library for quaternion-based
calculation of Theobauld. The transform direction:
transform(m, ref_crd) => target_crd.
"""
rms, rot9 = lib.pyqcprot.calc_rms_rot(crds1, crds2)
matrix = v3.identity()
for i in range(3):
for j in range(3):
v3.matrix_elem(matrix, i, j, rot9[i*3+j])
return rms, matrix
def calc_rmsd_rot(crds1, crds2):
"""
Returns the rmsd and optimal rotation and chooses the method
depending on whether numpy is installed.
"""
if is_numpy:
return numpy_svd_rmsd_rot(crds1, crds2)
else:
return pyqcprot_rmsd_rot(crds1, crds2)
def sum_rmsd(crds1, crds2):
"""
Returns the direct rmsd between two sets of vectors *without*
doing any optimal rotation. If calculated between optimal sets,
should give the proper RMSD.
"""
sum_squared = 0.0
for crd1, crd2 in zip(crds1, crds2):
sum_squared += v3.distance(crd1, crd2)**2
return math.sqrt(sum_squared/float(len(crds1)))
def get_superposable_atoms(soup, segments, atom_types):
"""
Returns a list of atom indices to a soup, built from segments.
Args:
segments (list): list of pairs of residue names in the soup,
such as ['A:1','A:3'], interpreted as the
two ends of a fragment in soup that we want
the atom index of
atom_types (list): list of atom_types in the residues that
we want to generate the indices from.
"""
result = []
allowed_i = []
residues = soup.residues()
if segments:
for res_tag_i, res_tag_j in segments:
i = soup.get_i_residue(str(res_tag_i))
j = soup.get_i_residue(str(res_tag_j))
if i > j:
i, j = j, i
allowed_i.extend(range(i,j+1))
else:
allowed_i = range(len(residues))
for i, residue in enumerate(residues):
if i in allowed_i:
result.extend([a for a in residue.atoms()
if a.type in atom_types])
return result
def rmsd_of_soups(
soup1, soup2, segments1=[], segments2=[],
atom_types=['CA'], transform_pdb1=None):
"""
Returns the RMSD between two PDB structures and optionally
writes the best transformed structure of pdb1 in transform_pdb.
By default, it chooses the CA atoms in the soup.
Args:
segments1 (list): list of pairs of residue names in pdb1,
such as ['A:1','A:3'], interpreted as the
two ends of a fragment in soup that we want
the atom index of
segments2 (list): same as above but for pdb2
atom_types (list): list of atom_types in the residues that
we want to generate the indices from.
"""
atoms1 = get_superposable_atoms(soup1, segments1, atom_types)
atoms2 = get_superposable_atoms(soup2, segments2, atom_types)
crds1 = [a.pos for a in atoms1]
crds2 = [a.pos for a in atoms2]
center1 = v3.get_center(crds1)
center2 = v3.get_center(crds2)
soup1.transform(v3.translation(-center1))
soup2.transform(v3.translation(-center2))
rmsd, transform_1_to_2 = calc_rmsd_rot(crds1, crds2)
if not transform_pdb1:
return rmsd
soup1.transform(transform_1_to_2)
soup1.transform(v3.translation(center2))
soup2.transform(v3.translation(center2))
soup1.write_pdb(transform_pdb1)
return sum_rmsd(crds1, crds2)
def rmsd_of_pdbs(
pdb1, pdb2, segments1=[], segments2=[],
atom_types=['CA'], transform_pdb1=None):
"""
Returns the RMSD between two PDB structures and optionally
writes the best transformed structure of pdb1 in transform_pdb.
Args:
segments1 (list): list of pairs of residue names in pdb1,
such as ['A:1','A:3'], interpreted as the
two ends of a fragment in soup that we want
the atom index of
segments2 (list): same as above but for pdb2
atom_types (list): list of atom_types in the residues that
we want to generate the indices from.
"""
soup1 = pdbatoms.Soup(pdb1)
soup2 = pdbatoms.Soup(pdb2)
return rmsd_of_soups(
soup1, soup2, segments1, segments2,
atom_types, transform_pdb1)