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