1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 """Module for handling all types of structural superimpositions."""
24
25
26 from copy import deepcopy
27 from math import pi
28 from numpy import diag, dot, eye, float64, outer, sign, sqrt, transpose, zeros
29 from numpy.linalg import det, norm, svd
30
31
32 from maths_fns.rotation_matrix import R_to_axis_angle
33
34
36 """Class for finding the optimal pivot point for motions between the given models."""
37
39 """Set up the class for pivot point optimisation.
40
41 @keyword models: The list of models to use. If set to None, then all models will be used.
42 @type models: list of int or None
43 @keyword coord: The array of molecular coordinates. The first dimension corresponds to the model, the second the atom, the third the coordinate.
44 @type coord: rank-3 numpy array
45 """
46
47
48 self.models = models
49 self.coord = coord
50
51
52 self.orig_coord = deepcopy(coord)
53
54
55 - def func(self, params):
56 """Target function for the optimisation of the motional pivot point.
57
58 @param params: The parameter vector from the optimisation algorithm.
59 @type params: list
60 @return: The target function value defined as the combined RMSD value.
61 @rtype: float
62 """
63
64
65 T, R, pivot = fit_to_mean(models=self.models, coord=self.coord, centroid=params, verbosity=0)
66
67
68 val = atomic_rmsd(self.coord)
69
70
71 self.coord = deepcopy(self.orig_coord)
72
73
74 return val
75
76
77
79 """Determine the RMSD for the given atomic coordinates.
80
81 This is the per atom RMSD to the mean structure.
82
83
84 @keyword coord: The array of molecular coordinates. The first dimension corresponds to the model, the second the atom, the third the coordinate.
85 @type coord: rank-3 numpy array
86 """
87
88
89 M = len(coord)
90 N = len(coord[0])
91 model_rmsd = zeros(M, float64)
92 mean = zeros((N, 3), float64)
93
94
95 calc_mean_structure(coord, mean)
96
97
98 for i in range(M):
99
100 for j in range(N):
101
102 vect = mean[j] - coord[i][j]
103
104
105 model_rmsd[i] += norm(vect)**2
106
107
108 model_rmsd[i] = sqrt(model_rmsd[i] / N)
109
110
111 return sum(model_rmsd) / M
112
113
115 """Average the coordinates.
116
117 @keyword coord: The list of coordinates of all models to superimpose. The first index is the models, the second is the atomic positions, and the third is the xyz coordinates.
118 @type coord: list of numpy rank-2, Nx3 arrays
119 @keyword mean: The data storage for the mean structure.
120 @type mean: numpy rank-2, Nx3 array
121 """
122
123
124 N = len(coord[0])
125 M = len(coord)
126
127
128 for i in range(N):
129 mean[i] = [0.0, 0.0, 0.0]
130
131
132 for i in range(N):
133
134 for j in range(M):
135 mean[i] += coord[j][i]
136
137
138 mean[i] = mean[i] / M
139
140
142 """Calculate the centroid of the structural coordinates.
143
144 @keyword coord: The atomic coordinates.
145 @type coord: numpy rank-2, Nx3 array
146 @return: The centroid.
147 @rtype: numpy rank-1, 3D array
148 """
149
150
151 centroid = coords.sum(0) / coords.shape[0]
152
153
154 return centroid
155
156
158 """Superimpose a set of structural models using the fit to first algorithm.
159
160 @keyword models: The list of models to superimpose.
161 @type models: list of int
162 @keyword coord: The list of coordinates of all models to superimpose. The first index is the models, the second is the atomic positions, and the third is the xyz coordinates.
163 @type coord: list of numpy rank-2, Nx3 arrays
164 @keyword centroid: An alternative position of the centroid to allow for different superpositions, for example of pivot point motions.
165 @type centroid: list of float or numpy rank-1, 3D array
166 @return: The lists of translation vectors, rotation matrices, and rotation pivots.
167 @rtype: list of numpy rank-1 3D arrays, list of numpy rank-2 3D arrays, list of numpy rank-1 3D arrays
168 """
169
170
171 print("\nSuperimposition of structural models %s using the 'fit to first' algorithm." % models)
172
173
174 T_list = [zeros(3, float64)]
175 R_list = [eye(3, dtype=float64)]
176 pivot_list = [zeros(3, float64)]
177
178
179 for i in range(1, len(models)):
180
181 trans_vect, trans_dist, R, axis, angle, pivot = kabsch(name_from='model %s'%models[0], name_to='model %s'%models[i], coord_from=coord[i], coord_to=coord[0], centroid=centroid)
182
183
184 T_list.append(trans_vect)
185 R_list.append(R)
186 pivot_list.append(pivot)
187
188
189 return T_list, R_list, pivot_list
190
191
192 -def fit_to_mean(models=None, coord=None, centroid=None, verbosity=1):
193 """Superimpose a set of structural models using the fit to first algorithm.
194
195 @keyword models: The list of models to superimpose.
196 @type models: list of int
197 @keyword coord: The list of coordinates of all models to superimpose. The first index is the models, the second is the atomic positions, and the third is the xyz coordinates.
198 @type coord: list of numpy rank-2, Nx3 arrays
199 @keyword centroid: An alternative position of the centroid to allow for different superpositions, for example of pivot point motions.
200 @type centroid: list of float or numpy rank-1, 3D array
201 @keyword verbosity: The amount of information to print out. If 0, nothing will be printed.
202 @type verbosity: int
203 @return: The lists of translation vectors, rotation matrices, and rotation pivots.
204 @rtype: list of numpy rank-1 3D arrays, list of numpy rank-2 3D arrays, list of numpy rank-1 3D arrays
205 """
206
207
208 if verbosity:
209 print("\nSuperimposition of structural models %s using the 'fit to mean' algorithm." % models)
210
211
212 orig_coord = deepcopy(coord)
213
214
215 T_list = []
216 R_list = []
217 pivot_list = []
218
219
220 N = len(coord[0])
221 mean = zeros((N, 3), float64)
222
223
224 converged = False
225 iter = 0
226 while not converged:
227
228 if verbosity:
229 print("\nIteration %i of the algorithm." % iter)
230 print("%-10s%-25s%-25s" % ("Model", "Translation (Angstrom)", "Rotation (deg)"))
231
232
233 calc_mean_structure(coord, mean)
234
235
236 converged = True
237 for i in range(len(models)):
238
239 trans_vect, trans_dist, R, axis, angle, pivot = kabsch(name_from='model %s'%models[0], name_to='mean', coord_from=coord[i], coord_to=mean, centroid=centroid, verbosity=0)
240
241
242 if verbosity:
243 print("%-10i%25.3g%25.3g" % (i, trans_dist, (angle / 2.0 / pi * 360.0)))
244
245
246 for j in range(N):
247
248 coord[i][j] += trans_vect
249
250
251 coord[i][j] -= pivot
252
253
254 coord[i][j] = dot(R, coord[i][j])
255
256
257 coord[i][j] += pivot
258
259
260 if trans_dist > 1e-10 or angle > 1e-10:
261 converged = False
262
263
264 iter += 1
265
266
267 for i in range(len(models)):
268
269 trans_vect, trans_dist, R, axis, angle, pivot = kabsch(name_from='model %s'%models[i], name_to='the mean structure', coord_from=orig_coord[i], coord_to=mean, centroid=centroid, verbosity=0)
270
271
272 T_list.append(trans_vect)
273 R_list.append(R)
274 pivot_list.append(pivot)
275
276
277 return T_list, R_list, pivot_list
278
279
280 -def kabsch(name_from=None, name_to=None, coord_from=None, coord_to=None, centroid=None, verbosity=1):
281 """Calculate the rotational and translational displacements between the two given coordinate sets.
282
283 This uses the Kabsch algorithm (http://en.wikipedia.org/wiki/Kabsch_algorithm).
284
285
286 @keyword name_from: The name of the starting structure, used for the printouts.
287 @type name_from: str
288 @keyword name_to: The name of the ending structure, used for the printouts.
289 @type name_to: str
290 @keyword coord_from: The list of atomic coordinates for the starting structure.
291 @type coord_from: numpy rank-2, Nx3 array
292 @keyword coord_to: The list of atomic coordinates for the ending structure.
293 @type coord_to: numpy rank-2, Nx3 array
294 @keyword centroid: An alternative position of the centroid, used for studying pivoted systems.
295 @type centroid: list of float or numpy rank-1, 3D array
296 @return: The translation vector T, translation distance d, rotation matrix R, rotation axis r, rotation angle theta, and the rotational pivot defined as the centroid of the ending structure.
297 @rtype: numpy rank-1 3D array, float, numpy rank-2 3D array, numpy rank-1 3D array, float, numpy rank-1 3D array
298 """
299
300
301 if centroid != None:
302 centroid_from = centroid
303 centroid_to = centroid
304 else:
305 centroid_from = find_centroid(coord_from)
306 centroid_to = find_centroid(coord_to)
307
308
309 trans_vect = centroid_to - centroid_from
310 trans_dist = norm(trans_vect)
311
312
313 R = kabsch_rotation(coord_from=coord_from, coord_to=coord_to, centroid_from=centroid_from, centroid_to=centroid_to)
314 axis, angle = R_to_axis_angle(R)
315
316
317 if verbosity >= 1:
318 print("\n\nCalculating the rotational and translational displacements from %s to %s using the Kabsch algorithm.\n" % (name_from, name_to))
319 print("Start centroid: [%20.15f, %20.15f, %20.15f]" % (centroid_from[0], centroid_from[1], centroid_from[2]))
320 print("End centroid: [%20.15f, %20.15f, %20.15f]" % (centroid_to[0], centroid_to[1], centroid_to[2]))
321 print("Translation vector: [%20.15f, %20.15f, %20.15f]" % (trans_vect[0], trans_vect[1], trans_vect[2]))
322 print("Translation distance: %.15f" % trans_dist)
323 print("Rotation matrix:")
324 print(" [[%20.15f, %20.15f, %20.15f]" % (R[0, 0], R[0, 1], R[0, 2]))
325 print(" [%20.15f, %20.15f, %20.15f]" % (R[1, 0], R[1, 1], R[1, 2]))
326 print(" [%20.15f, %20.15f, %20.15f]]" % (R[2, 0], R[2, 1], R[2, 2]))
327 print("Rotation axis: [%20.15f, %20.15f, %20.15f]" % (axis[0], axis[1], axis[2]))
328 print("Rotation angle (deg): %.15f" % (angle / 2.0 / pi * 360.0))
329
330
331 return trans_vect, trans_dist, R, axis, angle, centroid_to
332
333
334 -def kabsch_rotation(coord_from=None, coord_to=None, centroid_from=None, centroid_to=None):
335 """Calculate the rotation via SVD.
336
337 @keyword coord_from: The list of atomic coordinates for the starting structure.
338 @type coord_from: numpy rank-2, Nx3 array
339 @keyword coord_to: The list of atomic coordinates for the ending structure.
340 @type coord_to: numpy rank-2, Nx3 array
341 @keyword centroid_from: The starting centroid.
342 @type centroid_from: numpy rank-1, 3D array
343 @keyword centroid_to: The ending centroid.
344 @type centroid_to: numpy rank-1, 3D array
345 @return: The rotation matrix.
346 @rtype: numpy rank-2, 3D array
347 """
348
349
350 A = zeros((3, 3), float64)
351
352
353 for i in range(coord_from.shape[0]):
354
355 orig_from = coord_from[i] - centroid_from
356 orig_to = coord_to[i] - centroid_to
357
358
359 A += outer(orig_from, orig_to)
360
361
362 U, S, V = svd(A)
363
364
365 d = sign(det(A))
366 D = diag([1, 1, d])
367
368
369 R = dot(transpose(V), dot(D, transpose(U)))
370
371
372 return R
373