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 the double rotor frame order model."""
24
25
26 from math import cos, pi, sin
27 from numpy import add, divide, dot, eye, float64, multiply, sinc, swapaxes, tensordot
28 try:
29 from scipy.integrate import dblquad
30 except ImportError:
31 pass
32
33
34 from lib.compat import norm
35 from lib.frame_order.matrix_ops import rotate_daeg
36
37
39 """Generate the 1st degree Frame Order matrix for the double rotor model.
40
41 @param matrix: The Frame Order matrix, 1st degree to be populated.
42 @type matrix: numpy 3D, rank-2 array
43 @param R_eigen: The eigenframe rotation matrix.
44 @type R_eigen: numpy 3D, rank-2 array
45 @param smax1: The maximum torsion angle for the first rotor.
46 @type smax1: float
47 @param smax2: The maximum torsion angle for the second rotor.
48 @type smax2: float
49 """
50
51
52 sinc_smax1 = sinc(smax1/pi)
53 sinc_smax2 = sinc(smax2/pi)
54
55
56 matrix[0, 0] = sinc_smax1
57 matrix[1, 1] = sinc_smax2
58 matrix[2, 2] = sinc_smax1 * sinc_smax2
59
60
61 return rotate_daeg(matrix, R_eigen)
62
63
65 """Generate the rotated 2nd degree Frame Order matrix for the double rotor model.
66
67 The cone axis is assumed to be parallel to the z-axis in the eigenframe.
68
69
70 @param matrix: The Frame Order matrix, 2nd degree to be populated.
71 @type matrix: numpy 9D, rank-2 array
72 @param Rx2_eigen: The Kronecker product of the eigenframe rotation matrix with itself.
73 @type Rx2_eigen: numpy 9D, rank-2 array
74 @param smax1: The maximum torsion angle for the first rotor.
75 @type smax1: float
76 @param smax2: The maximum torsion angle for the second rotor.
77 @type smax2: float
78 """
79
80
81 matrix[:] = 0.0
82
83
84 sinc_smax1 = sinc(smax1/pi)
85 sinc_2smax1 = sinc(2.0*smax1/pi)
86 sinc_2smax1p1 = sinc_2smax1 + 1.0
87 sinc_2smax1n1 = sinc_2smax1 - 1.0
88 sinc_smax2 = sinc(smax2/pi)
89 sinc_2smax2 = sinc(2.0*smax2/pi)
90 sinc_2smax2p1 = sinc_2smax2 + 1.0
91 sinc_2smax2n1 = sinc_2smax2 - 1.0
92
93
94 matrix[0, 0] = sinc_2smax1 + 1.0
95 matrix[1, 1] = 2.0 * sinc_smax1 * sinc_smax2
96 matrix[2, 2] = sinc_smax2 * sinc_2smax1p1
97 matrix[3, 3] = matrix[1, 1]
98 matrix[4, 4] = sinc_2smax2p1
99 matrix[5, 5] = sinc_smax1 * sinc_2smax2p1
100 matrix[6, 6] = matrix[2, 2]
101 matrix[7, 7] = matrix[5, 5]
102 matrix[8, 8] = 0.5 * sinc_2smax1p1 * sinc_2smax2p1
103
104
105 matrix[4, 0] = 0.5 * sinc_2smax1n1 * sinc_2smax2n1
106 matrix[0, 8] = -sinc_2smax1n1
107 matrix[8, 0] = -0.5 * sinc_2smax1n1 * sinc_2smax2p1
108 matrix[4, 8] = -0.5 * sinc_2smax1p1 * sinc_2smax2n1
109 matrix[8, 4] = -sinc_2smax2n1
110
111
112 matrix[2, 6] = matrix[6, 2] = sinc_smax2 * sinc_2smax1n1
113 matrix[5, 7] = matrix[7, 5] = sinc_smax1 * sinc_2smax2n1
114
115
116 multiply(0.5, matrix, matrix)
117
118
119 return rotate_daeg(matrix, Rx2_eigen)
120
121
122 -def pcs_numeric_qr_int_double_rotor(points=None, max_points=None, sigma_max=None, sigma_max_2=None, c=None, full_in_ref_frame=None, r_pivot_atom=None, r_pivot_atom_rev=None, r_ln_pivot=None, r_inter_pivot=None, A=None, R_eigen=None, RT_eigen=None, Ri_prime=None, Ri2_prime=None, pcs_theta=None, pcs_theta_err=None, missing_pcs=None):
123 """The averaged PCS value via numerical integration for the double rotor frame order model.
124
125 @keyword points: The Sobol points in the torsion-tilt angle space.
126 @type points: numpy rank-2, 3D array
127 @keyword max_points: The maximum number of Sobol' points to use. Once this number is reached, the loop over the Sobol' torsion-tilt angles is terminated.
128 @type max_points: int
129 @keyword sigma_max: The maximum opening angle for the first rotor.
130 @type sigma_max: float
131 @keyword sigma_max_2: The maximum opening angle for the second rotor.
132 @type sigma_max_2: float
133 @keyword c: The PCS constant (without the interatomic distance and in Angstrom units).
134 @type c: numpy rank-1 array
135 @keyword full_in_ref_frame: An array of flags specifying if the tensor in the reference frame is the full or reduced tensor.
136 @type full_in_ref_frame: numpy rank-1 array
137 @keyword r_pivot_atom: The pivot point to atom vector.
138 @type r_pivot_atom: numpy rank-2, 3D array
139 @keyword r_pivot_atom_rev: The reversed pivot point to atom vector.
140 @type r_pivot_atom_rev: numpy rank-2, 3D array
141 @keyword r_ln_pivot: The lanthanide position to pivot point vector.
142 @type r_ln_pivot: numpy rank-2, 3D array
143 @keyword r_inter_pivot: The vector between the two pivots.
144 @type r_inter_pivot: numpy rank-1, 3D array
145 @keyword A: The full alignment tensor of the non-moving domain.
146 @type A: numpy rank-2, 3D array
147 @keyword R_eigen: The eigenframe rotation matrix.
148 @type R_eigen: numpy rank-2, 3D array
149 @keyword RT_eigen: The transpose of the eigenframe rotation matrix (for faster calculations).
150 @type RT_eigen: numpy rank-2, 3D array
151 @keyword Ri_prime: The array of pre-calculated rotation matrices for the in-frame double rotor motion for the 1st mode of motion, used to calculate the PCS for each state i in the numerical integration.
152 @type Ri_prime: numpy rank-3, array of 3D arrays
153 @keyword Ri2_prime: The array of pre-calculated rotation matrices for the in-frame double rotor motion for the 2nd mode of motion, used to calculate the PCS for each state i in the numerical integration.
154 @type Ri2_prime: numpy rank-3, array of 3D arrays
155 @keyword pcs_theta: The storage structure for the back-calculated PCS values.
156 @type pcs_theta: numpy rank-2 array
157 @keyword pcs_theta_err: The storage structure for the back-calculated PCS errors.
158 @type pcs_theta_err: numpy rank-2 array
159 @keyword missing_pcs: A structure used to indicate which PCS values are missing.
160 @type missing_pcs: numpy rank-2 array
161 """
162
163
164 pcs_theta[:] = 0.0
165 pcs_theta_err[:] = 0.0
166
167
168 Ri = dot(R_eigen, tensordot(Ri_prime, RT_eigen, axes=1))
169 Ri = swapaxes(Ri, 0, 1)
170 Ri2 = dot(R_eigen, tensordot(Ri2_prime, RT_eigen, axes=1))
171 Ri2 = swapaxes(Ri2, 0, 1)
172
173
174 sigma, sigma2 = points
175
176
177 num = 0
178 for i in range(len(points[0])):
179
180 if num == max_points:
181 break
182
183
184 if abs(sigma[i]) > sigma_max:
185 continue
186 if abs(sigma2[i]) > sigma_max_2:
187 continue
188
189
190 pcs_pivot_motion_double_rotor_qr_int(full_in_ref_frame=full_in_ref_frame, r_pivot_atom=r_pivot_atom, r_pivot_atom_rev=r_pivot_atom_rev, r_ln_pivot=r_ln_pivot, r_inter_pivot=r_inter_pivot, A=A, Ri=Ri[i], Ri2=Ri2[i], pcs_theta=pcs_theta, pcs_theta_err=pcs_theta_err, missing_pcs=missing_pcs)
191
192
193 num += 1
194
195
196 if num == 0:
197
198 Ri_prime = eye(3, dtype=float64)
199 Ri = dot(R_eigen, tensordot(Ri_prime, RT_eigen, axes=1))
200 Ri = swapaxes(Ri, 0, 1)
201
202
203 pcs_pivot_motion_double_rotor_qr_int(full_in_ref_frame=full_in_ref_frame, r_pivot_atom=r_pivot_atom, r_pivot_atom_rev=r_pivot_atom_rev, r_ln_pivot=r_ln_pivot, r_inter_pivot=r_inter_pivot, A=A, Ri=Ri, Ri2=Ri, pcs_theta=pcs_theta, pcs_theta_err=pcs_theta_err, missing_pcs=missing_pcs)
204
205
206 multiply(c, pcs_theta, pcs_theta)
207
208
209 else:
210 multiply(c, pcs_theta, pcs_theta)
211 divide(pcs_theta, float(num), pcs_theta)
212
213
214 -def pcs_numeric_quad_int_double_rotor(sigma_max=None, sigma_max_2=None, c=None, r_pivot_atom=None, r_ln_pivot=None, r_inter_pivot=None, A=None, R_eigen=None, RT_eigen=None, Ri_prime=None, Ri2_prime=None):
215 """Determine the averaged PCS value via SciPy quadratic numerical integration.
216
217 @keyword sigma_max: The maximum opening angle for the first rotor.
218 @type sigma_max: float
219 @keyword sigma_max_2: The maximum opening angle for the second rotor.
220 @type sigma_max_2: float
221 @keyword c: The PCS constant (without the interatomic distance and in Angstrom units).
222 @type c: numpy rank-1 array
223 @keyword r_pivot_atom: The pivot point to atom vector.
224 @type r_pivot_atom: numpy rank-1, 3D array
225 @keyword r_ln_pivot: The lanthanide position to pivot point vector.
226 @type r_ln_pivot: numpy rank-1, 3D array
227 @keyword r_inter_pivot: The vector between the two pivots.
228 @type r_inter_pivot: numpy rank-1, 3D array
229 @keyword A: The full alignment tensor of the non-moving domain.
230 @type A: numpy rank-2, 3D array
231 @keyword R_eigen: The eigenframe rotation matrix.
232 @type R_eigen: numpy rank-2, 3D array
233 @keyword RT_eigen: The transpose of the eigenframe rotation matrix (for faster calculations).
234 @type RT_eigen: numpy rank-2, 3D array
235 @keyword Ri_prime: The array of pre-calculated rotation matrices for the in-frame double rotor motion for the 1st mode of motion, used to calculate the PCS for each state i in the numerical integration.
236 @type Ri_prime: numpy rank-2, 3D array
237 @keyword Ri2_prime: The array of pre-calculated rotation matrices for the in-frame double rotor motion for the 2nd mode of motion, used to calculate the PCS for each state i in the numerical integration.
238 @type Ri2_prime: numpy rank-2, 3D array
239 """
240
241
242 Ri_prime[0, 1] = 0.0
243 Ri_prime[1, 0] = 0.0
244 Ri_prime[1, 1] = 1.0
245 Ri_prime[1, 2] = 0.0
246 Ri_prime[2, 1] = 0.0
247
248
249 Ri2_prime[0, 0] = 1.0
250 Ri2_prime[0, 1] = 0.0
251 Ri2_prime[0, 2] = 0.0
252 Ri2_prime[1, 0] = 0.0
253 Ri2_prime[2, 0] = 0.0
254
255
256 result = dblquad(pcs_pivot_motion_double_rotor_quad_int, -sigma_max, sigma_max, lambda sigma2: -sigma_max_2, lambda sigma2: sigma_max_2, args=(r_pivot_atom, r_ln_pivot, r_inter_pivot, A, R_eigen, RT_eigen, Ri_prime, Ri2_prime))
257
258
259 SA = 4.0 * sigma_max * sigma_max_2
260
261
262 return c * result[0] / SA
263
264
265 -def pcs_pivot_motion_double_rotor_qr_int(full_in_ref_frame=None, r_pivot_atom=None, r_pivot_atom_rev=None, r_ln_pivot=None, r_inter_pivot=None, A=None, Ri=None, Ri2=None, pcs_theta=None, pcs_theta_err=None, missing_pcs=None):
266 """Calculate the PCS value after a pivoted motion for the double rotor model.
267
268 @keyword full_in_ref_frame: An array of flags specifying if the tensor in the reference frame is the full or reduced tensor.
269 @type full_in_ref_frame: numpy rank-1 array
270 @keyword r_pivot_atom: The pivot point to atom vector.
271 @type r_pivot_atom: numpy rank-2, 3D array
272 @keyword r_pivot_atom_rev: The reversed pivot point to atom vector.
273 @type r_pivot_atom_rev: numpy rank-2, 3D array
274 @keyword r_ln_pivot: The lanthanide position to pivot point vector.
275 @type r_ln_pivot: numpy rank-2, 3D array
276 @keyword r_inter_pivot: The vector between the two pivots.
277 @type r_inter_pivot: numpy rank-1, 3D array
278 @keyword A: The full alignment tensor of the non-moving domain.
279 @type A: numpy rank-2, 3D array
280 @keyword Ri: The frame-shifted, pre-calculated rotation matrix for state i for the 1st mode of motion.
281 @type Ri: numpy rank-2, 3D array
282 @keyword Ri2: The frame-shifted, pre-calculated rotation matrix for state i for the 2nd mode of motion.
283 @type Ri2: numpy rank-2, 3D array
284 @keyword pcs_theta: The storage structure for the back-calculated PCS values.
285 @type pcs_theta: numpy rank-2 array
286 @keyword pcs_theta_err: The storage structure for the back-calculated PCS errors.
287 @type pcs_theta_err: numpy rank-2 array
288 @keyword missing_pcs: A structure used to indicate which PCS values are missing.
289 @type missing_pcs: numpy rank-2 array
290 """
291
292
293 rot_vect = dot(r_pivot_atom, Ri)
294
295
296 add(r_inter_pivot, rot_vect, rot_vect)
297
298
299 rot_vect = dot(rot_vect, Ri2)
300
301
302 add(rot_vect, r_ln_pivot, rot_vect)
303
304
305 length = 1.0 / norm(rot_vect, axis=1)**5
306
307
308 if min(full_in_ref_frame) == 0:
309 rot_vect_rev = dot(r_pivot_atom_rev, Ri)
310 add(r_inter_pivot, rot_vect_rev, rot_vect_rev)
311 rot_vect_rev = dot(rot_vect_rev, Ri2)
312 add(rot_vect_rev, r_ln_pivot, rot_vect_rev)
313 length_rev = 1.0 / norm(rot_vect_rev, axis=1)**5
314
315
316 for j in range(len(r_pivot_atom[:, 0])):
317
318 for i in range(len(pcs_theta)):
319
320 if missing_pcs[i, j]:
321 continue
322
323
324 if full_in_ref_frame[i]:
325 proj = dot(rot_vect[j], dot(A[i], rot_vect[j]))
326 length_i = length[j]
327 else:
328 proj = dot(rot_vect_rev[j], dot(A[i], rot_vect_rev[j]))
329 length_i = length_rev[j]
330
331
332 pcs_theta[i, j] += proj * length_i
333
334
336 """Calculate the PCS value after a pivoted motion for the double rotor model.
337
338 @param sigma_i: The 1st torsion angle for state i.
339 @type sigma_i: float
340 @param sigma2_i: The 1st torsion angle for state i.
341 @type sigma2_i: float
342 @param r_pivot_atom: The pivot point to atom vector.
343 @type r_pivot_atom: numpy rank-2, 3D array
344 @param r_ln_pivot: The lanthanide position to pivot point vector.
345 @type r_ln_pivot: numpy rank-2, 3D array
346 @param r_inter_pivot: The vector between the two pivots.
347 @type r_inter_pivot: numpy rank-1, 3D array
348 @param A: The full alignment tensor of the non-moving domain.
349 @type A: numpy rank-2, 3D array
350 @param R_eigen: The eigenframe rotation matrix.
351 @type R_eigen: numpy rank-2, 3D array
352 @param RT_eigen: The transpose of the eigenframe rotation matrix (for faster calculations).
353 @type RT_eigen: numpy rank-2, 3D array
354 @param Ri_prime: The empty rotation matrix for state i.
355 @type Ri_prime: numpy rank-2, 3D array
356 @param Ri2_prime: The 2nd empty rotation matrix for state i.
357 @type Ri2_prime: numpy rank-2, 3D array
358 @return: The PCS value for the changed position.
359 @rtype: float
360 """
361
362
363 c_sigma = cos(sigma_i)
364 s_sigma = sin(sigma_i)
365 Ri_prime[0, 0] = c_sigma
366 Ri_prime[0, 2] = s_sigma
367 Ri_prime[2, 0] = -s_sigma
368 Ri_prime[2, 2] = c_sigma
369
370
371 c_sigma = cos(sigma2_i)
372 s_sigma = sin(sigma2_i)
373 Ri2_prime[1, 1] = c_sigma
374 Ri2_prime[1, 2] = -s_sigma
375 Ri2_prime[2, 1] = s_sigma
376 Ri2_prime[2, 2] = c_sigma
377
378
379 Ri = dot(R_eigen, dot(Ri_prime, RT_eigen))
380 Ri2 = dot(R_eigen, dot(Ri2_prime, RT_eigen))
381
382
383 rot_vect = dot(r_pivot_atom, Ri)
384
385
386 add(r_inter_pivot, rot_vect, rot_vect)
387
388
389 rot_vect = dot(rot_vect, Ri2)
390
391
392 add(rot_vect, r_ln_pivot, rot_vect)
393
394
395 length = norm(rot_vect)
396
397
398 proj = dot(rot_vect, dot(A, rot_vect))
399
400
401 pcs = proj / length**5
402
403
404 return pcs
405