IMP logo
IMP Reference Guide  develop.63b38c487d,2024/12/22
The Integrative Modeling Platform
kappa2.py
1 """
2 
3 """
4 import numba as nb
5 import numpy as np
6 import typing
7 
8 
9 def kappasq_dwt(
10  sD2: float,
11  sA2: float,
12  fret_efficiency: float,
13  n_samples: int = 10000,
14  n_bins: int = 31,
15  k2_min: float = 0.0,
16  k2_max: float = 4.0
17 ):
18  """
19 
20  Parameters
21  ----------
22  sD2 : float
23  Second rank order parameter S2 of the donor dye. This can correspond
24  to the fraction of trapped donor dye.
25  sA2 : float
26  Second rank order parameter S2 of the acceptor dye. This can correspond
27  to the fraction of trapped acceptor dye.
28  fret_efficiency : float
29  FRET efficiency
30  n_bins : int
31  The number of bins in the kappa2 distribution that is
32  generated.
33  k2_max : float
34  Upper kappa2 bound in the generated histogram
35  k2_min : float
36  Lower kappa2 bound in the generate histogram
37  n_samples : int
38  The number random vector pairs that are drawn (default: 10000)
39 
40  Returns
41  -------
42  """
43 
44  # Binning of the kappa2 distribution
45  k2_step = (k2_max - k2_min) / (n_bins - 1)
46  k2_scale = np.arange(k2_min, k2_max + 1e-14, k2_step, dtype=np.float64)
47 
48  # Generate random orientations for the TDM vectors
49  donor_vec = np.random.randn(n_samples, 3)
50  acceptor_vec = np.random.randn(n_samples, 3)
51 
52  # x = (R_DA/R_0)^6; relative DA distance
53  x = 1 / fret_efficiency - 1
54 
55  k2s = np.zeros(n_samples, dtype=np.float64)
56  for i in range(n_samples):
57  donor = donor_vec[i]
58  acceptor = acceptor_vec[i]
59  # Assumption here: connecting vector R_DA is along the x-axis (R_DA=[1,0,0])
60  delta = np.arccos(np.dot(donor, acceptor) / (np.linalg.norm(donor) * np.linalg.norm(acceptor)))
61  beta1 = np.arccos(donor[0] / np.linalg.norm(donor))
62  beta2 = np.arccos(acceptor[0] / np.linalg.norm(acceptor))
63 
64  k2_trapped_free = kappasq(
65  delta=delta,
66  sD2=1,
67  sA2=0,
68  beta1=beta1,
69  beta2=beta2
70  )
71  k2_free_trapped = kappasq(
72  delta=delta,
73  sD2=0,
74  sA2=1,
75  beta1=beta1,
76  beta2=beta2
77  )
78  k2_trapped_trapped = kappasq(
79  delta=delta,
80  sD2=1,
81  sA2=1,
82  beta1=beta1,
83  beta2=beta2
84  )
85  ##
86  Ek2 = (1 - sD2) * (1 - sA2) / (1 + x) + sD2 * sA2 / (1 + 2 / 3. / k2_trapped_trapped * x) + sD2 * (1 - sA2) / (
87  1 + 2 / 3. / k2_trapped_free * x) + (1 - sD2) * sA2 / (1 + 2 / 3. / k2_free_trapped * x)
88  k2 = 2 / 3. * x / (1 / Ek2 - 1)
89  k2s[i] = k2
90 
91  # create the histogram
92  k2hist, bins = np.histogram(k2s, k2_scale)
93  return k2_scale, k2hist, k2s
94 
95 
96 def kappasq_all_delta_new(
97  delta: float,
98  sD2: float,
99  sA2: float,
100  step: float = 0.25,
101  n_bins: int = 31,
102  k2_min: float = 0.0,
103  k2_max: float = 4.0
104 ) -> typing.Tuple[np.ndarray, np.ndarray, np.ndarray]:
105  ks = list()
106  weights = list()
107  for beta1 in np.arange(0, np.pi/2, step=step / 180. * np.pi):
108  weight_beta1 = np.sin(beta1)
109  for beta2 in np.arange(
110  start=abs(delta - beta1),
111  stop=min(delta + beta1, np.pi / 2.),
112  step=step / 180. * np.pi
113  ):
114  weight_beta2 = np.sin(beta1)
115  weights.append(
116  weight_beta1 * weight_beta2
117  )
118  ks.append(
119  kappasq(
120  sD2=sD2,
121  sA2=sA2,
122  delta=delta,
123  beta1=beta1,
124  beta2=beta2
125  )
126  )
127  ks = np.array(ks)
128 
129  # histogram bin edges
130  k2_step = (k2_max - k2_min) / (n_bins - 1)
131  k2scale = np.arange(k2_min, k2_max + 1e-14, k2_step, dtype=np.float64)
132 
133  k2hist, x = np.histogram(ks, bins=k2scale, weights=weights)
134  return k2scale, k2hist, ks
135 
136 
137 
138 @nb.jit(nopython=True)
139 def kappasq_all_delta(
140  delta: float,
141  sD2: float,
142  sA2: float,
143  step: float = 0.25,
144  n_bins: int = 31,
145  k2_min: float = 0.0,
146  k2_max: float = 4.0
147 ) -> typing.Tuple[np.ndarray, np.ndarray, np.ndarray]:
148  """Computes a orientation factor distribution for a wobbling in a cone model
149  using parameters that can be estimated by experimental anisotropies.
150 
151  The function used second rank order parameter of the donor and acceptor
152  and the angle delta between the symmetry axes of the dyes as input. These
153  parameters can be estimated by the residual anisotropy the the dyes. The
154  second rank order parameter of the donor and acceptor are estimated by the
155  dye's residual anisotropies. The angle between the symmetry axes is estimated
156  by the residual anisotropy of the FRET sensitized emission (see:
157  `chisurf.fluorescence.anisotropy.kappa2.s2delta`).
158 
159  This function computes a orientation factor distribution, :math:`p(/kappa^2)`,
160  for a wobbling in a cone model (WIC) for second rank structure factors of
161  the donor and acceptor, and an angle :math:`delta`. The angle
162  :math:`delta` is the angle between the symmetry axes of the dyes and can be
163  estimated using experimental residual anisotropies [1]_.
164 
165  Parameters
166  ----------
167  delta : float
168  The angle delta (in rad) for which the WIC orientation factor
169  distribution is calculated.
170  sD2 : float
171  Second rank order parameter S2 of the donor dye. This can correspond
172  to the fraction of trapped donor dye.
173  sA2 : float
174  Second rank order parameter S2 of the acceptor dye. This can correspond
175  to the fraction of trapped acceptor dye.
176  step : float
177  The step size in degrees that is used to sample the
178  angles.
179  n_bins : int
180  The number of bins in the kappa2 distribution that is
181  generated.
182  k2_max : float
183  Upper kappa2 bound in the generated histogram
184  k2_min : float
185  Lower kappa2 bound in the generate histogram
186 
187  Returns
188  -------
189  k2scale : numpy-array
190  A linear scale in the range of [0, 4] with *n_bins* elements
191  k2hist : numpy-array
192  The histogram of kappa2 values
193  k2 : numpy-array
194  A numpy-array containing all computed kappa2 values. The histogram
195  corresponds to a histogram over all returned kappa2 values.
196 
197  Examples
198  --------
199  >>> from scikit_fluorescence.modeling.kappa2 import kappasq_all_delta
200  >>> k2s, k2h, k2v = kappasq_all_delta(
201  ... delta=0.2,
202  ... sD2=0.15,
203  ... sA2=0.25,
204  ... step=2.0,
205  ... n_bins=31
206  ... )
207  >>> np.allclose(k2h, np.array([ 0. , 0. , 0. , 0. ,
208  ... 3205.72877776, 1001.19048825, 611.44917432, 252.97166906,
209  ... 0. , 0. , 0. , 0. ,
210  ... 0. , 0. , 0. , 0. ,
211  ... 0. , 0. , 0. , 0. ,
212  ... 0. , 0. , 0. , 0. ,
213  ... 0. , 0. , 0. , 0. ,
214  ... 0. , 0. ]), rtol=0.3, atol=2.0)
215  True
216 
217  Notes
218  -----
219  The angle :math:`/beta_1` is varied in the range (0,pi/2)
220  The angle :math:`/phi` is varied in the range (0, 2 pi)
221 
222  References
223  ----------
224  .. [1] Simon Sindbert, Stanislav Kalinin, Hien Nguyen, Andrea Kienzler,
225  Lilia Clima, Willi Bannwarth, Bettina Appel, Sabine Mueller, Claus A. M.
226  Seidel, "Accurate Distance Determination of Nucleic Acids via Foerster
227  Resonance Energy Transfer: Implications of Dye Linker Length and Rigidity"
228  vol. 133, pp. 2463-2480, J. Am. Chem. Soc., 2011
229 
230  """
231  # beta angles
232  beta1 = np.arange(0.001, np.pi / 2.0, step * np.pi / 180.0, dtype=np.float64)
233  phi = np.arange(0.001, 2.0 * np.pi, step * np.pi / 180.0, dtype=np.float64)
234  n = beta1.shape[0]
235  m = phi.shape[0]
236  rda_vec = np.array([1, 0, 0], dtype=np.float64)
237 
238  # kappa-square values for allowed betas
239  k2 = np.zeros((n, m), dtype=np.float64)
240  k2hist = np.zeros(n_bins - 1, dtype=np.float64)
241 
242  # histogram bin edges
243  k2_step = (k2_max - k2_min) / (n_bins - 1)
244  k2scale = np.arange(k2_min, k2_max + 1e-14, k2_step, dtype=np.float64)
245  for i in range(n):
246  d1 = np.array([np.cos(beta1[i]), 0, np.sin(beta1[i])])
247  n1 = np.array([-np.sin(beta1[i]), 0, np.cos(beta1[i])])
248  n2 = np.array([0, 1, 0])
249  for j in range(m):
250  d2 = (n1*np.cos(phi[j])+n2*np.sin(phi[j]))*np.sin(delta)+d1*np.cos(delta)
251  beta2 = np.arccos(np.abs(d2.dot(rda_vec)))
252  k2[i, j] = kappasq(
253  delta=delta,
254  sD2=sD2,
255  sA2=sA2,
256  beta1=beta1[i],
257  beta2=beta2
258  )
259  y, x = np.histogram(k2[i, :], bins=k2scale)
260  k2hist += y*np.sin(beta1[i])
261  return k2scale, k2hist, k2
262 
263 
264 @nb.jit(nopython=True)
265 def kappasq_all(
266  sD2: float,
267  sA2: float,
268  n_bins: int = 81,
269  k2_min: float = 0.0,
270  k2_max: float = 4.0,
271  n_samples: int = 10000
272 ) -> typing.Tuple[np.array, np.array, np.array]:
273  """Computes a orientation factor distribution for a wobbling in a cone model
274  using specific second rank structure factors of the donor and acceptor.
275 
276  This function computes a orientation factor distribution, :math:`p(/kappa^2)`,
277  for a wobbling in a cone model (WIC) for second rank structure factors of
278  the donor and acceptor estimated using experimental residual anisotropies [1]_.
279 
280  Parameters
281  ----------
282  sD2 : float
283  Second rank order parameter S2 of the donor dye
284  sA2 : float
285  Second rank order parameter S2 of the acceptor dye
286  n_bins : int
287  The number of bins in the kappa2 histogram that is
288  generated.
289  k2_max : float
290  Upper kappa2 bound in the generated histogram
291  k2_min : float
292  Lower kappa2 bound in the generate histogram
293  n_samples : int
294  The number random vector pairs that are drawn (default: 10000)
295 
296  Returns
297  -------
298  k2scale : numpy-array
299  A linear scale in the range of [0, 4] with *n_bins* elements
300  k2hist : numpy-array
301  The histogram of kappa2 values
302  k2 : numpy-array
303  A numpy-array containing all computed kappa2 values. The histogram
304  corresponds to a histogram over all returned kappa2 values.
305 
306  Examples
307  --------
308  >>> from scikit_fluorescence.modeling.kappa2 import kappasq_all_delta
309  >>> k2_scale, k2_hist, k2 = kappasq_all(
310  ... sD2=0.3,
311  ... sA2=0.5,
312  ... n_bins=31,
313  ... n_samples=100000
314  ... )
315  >>> k2_scale
316  array([0. , 0.13333333, 0.26666667, 0.4 , 0.53333333,
317  0.66666667, 0.8 , 0.93333333, 1.06666667, 1.2 ,
318  1.33333333, 1.46666667, 1.6 , 1.73333333, 1.86666667,
319  2. , 2.13333333, 2.26666667, 2.4 , 2.53333333,
320  2.66666667, 2.8 , 2.93333333, 3.06666667, 3.2 ,
321  3.33333333, 3.46666667, 3.6 , 3.73333333, 3.86666667,
322  4. ])
323  >>> reference = np.array([0.0000e+00, 0.0000e+00, 0.0000e+00, 3.1920e+04, 4.3248e+04,
324  ... 1.4842e+04, 5.8930e+03, 2.5190e+03, 1.0840e+03, 3.9700e+02,
325  ... 9.4000e+01, 3.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00,
326  ... 0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00,
327  ... 0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00,
328  ... 0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00])
329  >>> np.allclose(reference, k2_hist, rtol=0.3, atol=2.0)
330  True
331 
332  References
333  ----------
334  .. [1] Simon Sindbert, Stanislav Kalinin, Hien Nguyen, Andrea Kienzler,
335  Lilia Clima, Willi Bannwarth, Bettina Appel, Sabine Mueller, Claus A. M.
336  Seidel, "Accurate Distance Determination of Nucleic Acids via Foerster
337  Resonance Energy Transfer: Implications of Dye Linker Length and Rigidity"
338  vol. 133, pp. 2463-2480, J. Am. Chem. Soc., 2011
339 
340  """
341  k2 = np.zeros(n_samples, dtype=np.float64)
342  step = (k2_max - k2_min) / (n_bins - 1)
343  k2scale = np.arange(k2_min, k2_max + 1e-14, step, dtype=np.float64)
344  k2hist = np.zeros(k2scale.shape[0] - 1, dtype=np.float64)
345  for i in range(n_samples):
346  d1 = np.random.random(3)
347  d2 = np.random.random(3)
348  n1 = np.linalg.norm(d1)
349  n2 = np.linalg.norm(d2)
350  # Assumption here: connecting vector R_DA is along the x-axis (R_DA=[1,0,0])
351  delta = np.arccos(np.dot(d1, d2) / (n1 * n2))
352  beta1 = np.arccos(d1[0] / n1)
353  beta2 = np.arccos(d2[0] / n2)
354  k2[i] = kappasq(
355  delta=delta,
356  sD2=sD2,
357  sA2=sA2,
358  beta1=beta1,
359  beta2=beta2
360  )
361  y, x = np.histogram(k2, bins=k2scale)
362  k2hist += y
363  return k2scale, k2hist, k2
364 
365 
366 @nb.jit(nopython=True)
367 def kappa_distance(
368  d1: np.array,
369  d2: np.array,
370  a1: np.array,
371  a2: np.array
372 ) -> typing.Tuple[float, float]:
373  r"""Calculates the distance between the center of two dipoles and the
374  orientation-factor kappa of the dipoles
375 
376  Calculates for the vectors d1 and d2 pointing to the donors and the vectors
377  a1 and a2 pointing to the ends of the acceptor dipole the orientation
378  factor kappa.
379 
380  Parameters
381  ----------
382  d1 : numpy-array
383  Vector pointing to the first point of the dipole D
384  d2 : numpy-array
385  Vector pointing to the second point of the dipole D
386  a1 : numpy-array
387  Vector pointing to the first point of the dipole A
388  a2 : numpy-array
389  Vector pointing to the second point of the dipole A
390 
391  Returns
392  -------
393  tuple
394  distance between the center of the dipoles and the orientation factor
395  for the two dipoles kappa
396 
397  Notes
398  -----
399  The four vectors defining the dipole of the donor :math:`\vec{r}_{D1}` and
400  :math:`\vec{r}_{D2}` specified by the parameters `d1` and `d2` and
401  :math:`\vec{r}_{A1}` and :math:`\vec{r}_{A2}` specified by the parameters
402  `a1` and `a1` are used to compute orientation factor :math:`kappa^2`
403  and the distance between the center of the two dipoles :math:`R_{DA}`.
404 
405  The distance :math:`R_{DA}` between the dipole centers and :math:`kappa`
406  is calculated as follows:
407 
408  ..math::
409 
410  R_{D,21}=|\vec{r}_{D2} - \vec{r}_{D1}| \
411  R_{A,21}=|\vec{r}_{A2} - \vec{r}_{A1}| \
412  \hat{\mu}_{D}=1/R_{D,21} \cdot (\vec{r}_{D2}-\vec{r}_{D1}) \
413  \hat{\mu}_{A}=1/R_{A,21} \cdot (\vec{r}_{A2}-\vec{r}_{A1}) \
414  \vec{m}_{D}=\vec{r}_{D1}+1/2 \cdot \hat{\mu}_{D} \
415  \vec{m}_{A}=\vec{r}_{A1}+1/2 \cdot \hat{\mu}_{A} \
416  \vec{r}_{DA}=\vec{m}_{D}-\vec{m}_{A} \
417  R_{DA}=|\vec{m}_{D}-\vec{m}_{A}| \
418  \hat{\mu}_{DA}=\vec{r}_{DA} / R_{DA} \
419  \kappa=\langle\mu_A,\mu_D\rangle-3\cdot\langle\mu_D,\mu_{DA}\rangle \cdot \langle\mu_A,\mu_{DA}\rangle
420 
421  Examples
422  --------
423  >>> import scikit_fluorescence.modeling.kappa2
424  >>> donor_dipole = np.array(
425  ... [
426  ... [0.0, 0.0, 0.0],
427  ... [1.0, 0.0, 0.0]
428  ... ], dtype=np.float64
429  ... )
430  >>> acceptor_dipole = np.array(
431  ... [
432  ... [0.0, 0.5, 0.0],
433  ... [0.0, 0.5, 1.0]
434  ... ], dtype=np.float64
435  ... )
436  >>> scikit_fluorescence.modeling.kappa2.kappa(
437  ... donor_dipole,
438  ... acceptor_dipole
439  ... )
440  (0.8660254037844386, 1.0000000000000002)
441 
442  """
443  # coordinates of the dipole
444  d11 = d1[0]
445  d12 = d1[1]
446  d13 = d1[2]
447 
448  d21 = d2[0]
449  d22 = d2[1]
450  d23 = d2[2]
451 
452  # distance between the two end points of the donor
453  dD21 = np.sqrt(
454  (d11 - d21) * (d11 - d21) +
455  (d12 - d22) * (d12 - d22) +
456  (d13 - d23) * (d13 - d23)
457  )
458 
459  # normal vector of the donor-dipole
460  muD1 = (d21 - d11) / dD21
461  muD2 = (d22 - d12) / dD21
462  muD3 = (d23 - d13) / dD21
463 
464  # vector to the middle of the donor-dipole
465  dM1 = d11 + dD21 * muD1 / 2.0
466  dM2 = d12 + dD21 * muD2 / 2.0
467  dM3 = d13 + dD21 * muD3 / 2.0
468 
469  ### Acceptor ###
470  # cartesian coordinates of the acceptor
471  a11 = a1[0]
472  a12 = a1[1]
473  a13 = a1[2]
474 
475  a21 = a2[0]
476  a22 = a2[1]
477  a23 = a2[2]
478 
479  # distance between the two end points of the acceptor
480  dA21 = np.sqrt(
481  (a11 - a21) * (a11 - a21) +
482  (a12 - a22) * (a12 - a22) +
483  (a13 - a23) * (a13 - a23)
484  )
485 
486  # normal vector of the acceptor-dipole
487  muA1 = (a21 - a11) / dA21
488  muA2 = (a22 - a12) / dA21
489  muA3 = (a23 - a13) / dA21
490 
491  # vector to the middle of the acceptor-dipole
492  aM1 = a11 + dA21 * muA1 / 2.0
493  aM2 = a12 + dA21 * muA2 / 2.0
494  aM3 = a13 + dA21 * muA3 / 2.0
495 
496  # vector connecting the middle of the dipoles
497  RDA1 = dM1 - aM1
498  RDA2 = dM2 - aM2
499  RDA3 = dM3 - aM3
500 
501  # Length of the dipole-dipole vector (distance)
502  dRDA = np.sqrt(RDA1 * RDA1 + RDA2 * RDA2 + RDA3 * RDA3)
503 
504  # Normalized dipole-diple vector
505  nRDA1 = RDA1 / dRDA
506  nRDA2 = RDA2 / dRDA
507  nRDA3 = RDA3 / dRDA
508 
509  # Orientation factor kappa2
510  kappa = muA1 * muD1 + \
511  muA2 * muD2 + \
512  muA3 * muD3 - \
513  3.0 * (muD1 * nRDA1 + muD2 * nRDA2 + muD3 * nRDA3) * \
514  (muA1 * nRDA1 + muA2 * nRDA2 + muA3 * nRDA3)
515  return dRDA, kappa
516 
517 
518 def kappa(
519  donor_dipole: np.ndarray,
520  acceptor_dipole: np.ndarray
521 ) -> typing.Tuple[float, float]:
522  """Calculates the orientation-factor kappa
523 
524  :param donor_dipole: 2x3 vector of the donor-dipole
525  :param acceptor_dipole: 2x3 vector of the acceptor-dipole
526  :return: distance, kappa
527 
528  Example
529  -------
530 
531  >>> import numpy as np
532  >>> donor_dipole = np.array([[0.0, 0.0, 0.0], [1.0, 0.0, 0.0]], dtype=np.float64)
533  >>> acceptor_dipole = np.array([[0.0, 0.5, 0.0], [0.0, 0.5, 1.0]], dtype=np.float64)
534  >>> kappa(donor_dipole, acceptor_dipole)
535  (0.8660254037844386, 1.0000000000000002)
536  """
537  return kappa_distance(
538  donor_dipole[0], donor_dipole[1],
539  acceptor_dipole[0], acceptor_dipole[1]
540  )
541 
542 
543 def s2delta(
544  s2_donor: float,
545  s2_acceptor: float,
546  r_inf_AD: float,
547  r_0: float = 0.38
548 ) -> typing.Tuple[float, float]:
549  """Calculate s2delta from the residual anisotropies of the donor and acceptor
550 
551  Parameters
552  ----------
553  r_0 : float
554  Fundamental anisotropy, the anisotropy of the dyes at time zero (
555  default value 0.4)
556  s2_donor : float
557  The second rank oder parameter of the donor dye. The second rank oder
558  parameter can be computed using the dye's residual anisotropy (see
559  Notes below)
560  s2_acceptor : float
561  The second rank oder parameter of the direct excited acceptor dye.
562  r_inf_AD : float
563  The residual anisotropy on the acceptor excited by the donor dye.
564 
565  Returns
566  -------
567  s2delta : float
568  A second rank order parameter of the angle [1]_ eq. 10
569  delta : float
570  The angle between the two symmetry axes of the dipols in units of rad.
571 
572  Examples
573  --------
574  >>> from scikit_fluorescence.modeling.kappa2 import s2delta
575  >>> r0 = 0.38
576  >>> s2donor = 0.2
577  >>> s2acceptor = 0.3
578  >>> r_inf_AD = 0.01
579  >>> s2delta(
580  ... r_0=r0,
581  ... s2_donor=s2donor,
582  ... s2_acceptor=s2acceptor,
583  ... r_inf_AD=r_inf_AD
584  ... )
585  (0.4385964912280701, 0.6583029208008411)
586 
587  Notes
588  -----
589  The parameters `s2_donor` and `s2_acceptor`, which correspond to :math:`S^{(2)}_D`
590  and :math:`S^{(2)}_A` are calculated using the dye's residual anisotropy [1]_
591 
592  ..math::
593 
594  S^{(2)}_D = - /sqrt{/frac{r_{D,inf}}{r_{0,D}}} \
595  S^{(2)}_D = /sqrt{/frac{r_{A,inf}}{r_{0,A}}} \
596 
597  References
598  ----------
599 
600  .. [1] Simon Sindbert, Stanislav Kalinin, Hien Nguyen, Andrea Kienzler,
601  Lilia Clima, Willi Bannwarth, Bettina Appel, Sabine Mueller, Claus A. M.
602  Seidel, "Accurate Distance Determination of Nucleic Acids via Foerster
603  Resonance Energy Transfer: Implications of Dye Linker Length and Rigidity"
604  vol. 133, pp. 2463-2480, J. Am. Chem. Soc., 2011
605 
606  """
607  s2_delta = r_inf_AD/(r_0 * s2_donor * s2_acceptor)
608  delta = np.arccos(np.sqrt((2.0 * s2_delta + 1.0) / 3.0))
609  return s2_delta, delta
610 
611 
612 def calculate_kappa_distance(
613  xyz: np.array,
614  aid1: int,
615  aid2: int,
616  aia1: int,
617  aia2: int
618 ) -> typing.Tuple[np.ndarray, np.ndarray]:
619  """Calculates the orientation factor kappa2 and the distance of a
620  trajectory given the atom-indices of the donor and the acceptor.
621 
622  :param xyz: numpy-array (frame, atom, xyz)
623  :param aid1: int, atom-index of d-dipole 1
624  :param aid2: int, atom-index of d-dipole 2
625  :param aia1: int, atom-index of a-dipole 1
626  :param aia2: int, atom-index of a-dipole 2
627 
628  :return: distances, kappa2
629  """
630  n_frames = xyz.shape[0]
631  ks = np.empty(n_frames, dtype=np.float32)
632  ds = np.empty(n_frames, dtype=np.float32)
633 
634  for i_frame in range(n_frames):
635  try:
636  d, k = kappa_distance(
637  xyz[i_frame, aid1], xyz[i_frame, aid2],
638  xyz[i_frame, aia1], xyz[i_frame, aia2]
639  )
640  ks[i_frame] = k
641  ds[i_frame] = d
642  except:
643  print("Frame ", i_frame, "skipped, calculation error")
644 
645  return ds, ks
646 
647 
648 @nb.jit(nopython=True)
649 def kappasq(
650  delta: float,
651  sD2: float,
652  sA2: float,
653  beta1: float,
654  beta2: float
655 ) -> float:
656  """Calculates kappa2 given a set of oder parameters and angles
657 
658  Parameters
659  ----------
660  delta : float
661  The angle between the symmetry axis of rotation of the dyes in units
662  of rad.
663  sD2 : float
664  The second rank oder parameter of the donor
665  sA2 : float
666  The second rank oder parameter of the acceptor
667  beta1 : float
668  The angle between the symmetry axes of the rotation of the dye and
669  the distance vector RDA between the two dipoles
670  beta2
671  The angle between the symmetry axes of the rotation of the dye and
672  the distance vector RDA between the two dipoles
673 
674  Returns
675  -------
676  kappa2 : float
677  The orientation factor that corresponds to the provided angles.
678 
679  Notes
680  -----
681 
682  This function corresponds to eq. 9 in [1]_.
683 
684  References
685  ----------
686  .. [1] Simon Sindbert, Stanislav Kalinin, Hien Nguyen, Andrea Kienzler,
687  Lilia Clima, Willi Bannwarth, Bettina Appel, Sabine Mueller, Claus A. M.
688  Seidel, "Accurate Distance Determination of Nucleic Acids via Foerster
689  Resonance Energy Transfer: Implications of Dye Linker Length and Rigidity"
690  vol. 133, pp. 2463-2480, J. Am. Chem. Soc., 2011
691 
692  """
693  s2delta = (3.0 * np.cos(delta) * np.cos(delta) - 1.0) / 2.0
694  s2beta1 = (3.0 * np.cos(beta1) * np.cos(beta1) - 1.0) / 2.0
695  s2beta2 = (3.0 * np.cos(beta2) * np.cos(beta2) - 1.0) / 2.0
696  k2 = 2.0 / 3.0 * (
697  1.0 +
698  sD2 * s2beta1 +
699  sA2 * s2beta2 +
700  sD2 * sA2 * (
701  s2delta +
702  6 * s2beta1 * s2beta2 +
703  1 +
704  2 * s2beta1 +
705  2 * s2beta2 -
706  9 * np.cos(beta1) * np.cos(beta2) * np.cos(delta)
707  )
708  )
709  return k2
710 
711 
712 def p_isotropic_orientation_factor(
713  k2: np.ndarray,
714  normalize: bool = True
715 ) -> np.ndarray:
716  """Calculates an the probability of a given kappa2 according to
717  an isotropic orientation factor distribution
718 
719  Parameters
720  ----------
721  k2 : numpy-array
722  An array containing kappa squared values.
723  normalize : bool
724  If this parameter is set to True (default) the returned distribution is
725  normalized to unity.
726 
727  Returns
728  -------
729  p_k2 : numpy-array
730  The probability distribution of kappa2 for isotropic oriented dipoles
731 
732 
733  Example
734  -------
735  >>> import scikit_fluorescence.modeling.kappa2
736  >>> k2 = np.linspace(0.1, 4, 32)
737  >>> p_k2 = scikit_fluorescence.modeling.kappa2.p_isotropic_orientation_factor(k2=k2)
738  >>> p_k2
739  array([0.17922824, 0.11927194, 0.09558154, 0.08202693, 0.07297372,
740  0.06637936, 0.06130055, 0.05723353, 0.04075886, 0.03302977,
741  0.0276794 , 0.02359627, 0.02032998, 0.01763876, 0.01537433,
742  0.01343829, 0.01176177, 0.01029467, 0.00899941, 0.00784718,
743  0.00681541, 0.00588615, 0.00504489, 0.0042798 , 0.0035811 ,
744  0.00294063, 0.00235153, 0.001808 , 0.00130506, 0.00083845,
745  0.0004045 , 0. ])
746 
747  Notes
748  -----
749  http://www.fretresearch.org/kappasquaredchapter.pdf
750 
751  """
752  ks = np.sqrt(k2)
753  s3 = np.sqrt(3.)
754  r = np.zeros_like(k2)
755  for i, k in enumerate(ks):
756  if 0 <= k <= 1:
757  r[i] = 0.5 / (s3 * k) * np.log(2 + s3)
758  elif 1 <= k <= 2:
759  r[i] = 0.5 / (s3 * k) * np.log((2 + s3) / (k + np.sqrt(k**2 - 1.0)))
760  if normalize:
761  r /= max(1.0, r.sum())
762  return r