ayousanz commited on
Commit
b0c4556
·
verified ·
1 Parent(s): ad4c03e

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +2 -0
  2. .venv/Lib/site-packages/scipy/signal/_max_len_seq_inner.cp39-win_amd64.pyd +3 -0
  3. .venv/Lib/site-packages/scipy/sparse/_sparsetools.cp39-win_amd64.pyd +3 -0
  4. .venv/Lib/site-packages/scipy/spatial/__pycache__/__init__.cpython-39.pyc +0 -0
  5. .venv/Lib/site-packages/scipy/spatial/__pycache__/_geometric_slerp.cpython-39.pyc +0 -0
  6. .venv/Lib/site-packages/scipy/spatial/__pycache__/_kdtree.cpython-39.pyc +0 -0
  7. .venv/Lib/site-packages/scipy/spatial/__pycache__/_plotutils.cpython-39.pyc +0 -0
  8. .venv/Lib/site-packages/scipy/spatial/__pycache__/_procrustes.cpython-39.pyc +0 -0
  9. .venv/Lib/site-packages/scipy/spatial/__pycache__/_spherical_voronoi.cpython-39.pyc +0 -0
  10. .venv/Lib/site-packages/scipy/spatial/__pycache__/ckdtree.cpython-39.pyc +0 -0
  11. .venv/Lib/site-packages/scipy/spatial/__pycache__/distance.cpython-39.pyc +0 -0
  12. .venv/Lib/site-packages/scipy/spatial/__pycache__/kdtree.cpython-39.pyc +0 -0
  13. .venv/Lib/site-packages/scipy/spatial/__pycache__/qhull.cpython-39.pyc +0 -0
  14. .venv/Lib/site-packages/scipy/spatial/transform/__init__.py +29 -0
  15. .venv/Lib/site-packages/scipy/spatial/transform/__pycache__/__init__.cpython-39.pyc +0 -0
  16. .venv/Lib/site-packages/scipy/spatial/transform/__pycache__/_rotation_groups.cpython-39.pyc +0 -0
  17. .venv/Lib/site-packages/scipy/spatial/transform/__pycache__/_rotation_spline.cpython-39.pyc +0 -0
  18. .venv/Lib/site-packages/scipy/spatial/transform/__pycache__/rotation.cpython-39.pyc +0 -0
  19. .venv/Lib/site-packages/scipy/spatial/transform/_rotation_groups.py +140 -0
  20. .venv/Lib/site-packages/scipy/spatial/transform/_rotation_spline.py +460 -0
  21. .venv/Lib/site-packages/scipy/spatial/transform/tests/__init__.py +0 -0
  22. .venv/Lib/site-packages/scipy/spatial/transform/tests/test_rotation.py +1906 -0
  23. .venv/Lib/site-packages/scipy/spatial/transform/tests/test_rotation_groups.py +169 -0
  24. .venv/Lib/site-packages/scipy/spatial/transform/tests/test_rotation_spline.py +162 -0
  25. .venv/Lib/site-packages/scipy/special/_add_newdocs.py +0 -0
  26. .venv/Lib/site-packages/scipy/special/_basic.py +0 -0
  27. .venv/Lib/site-packages/scipy/special/_cdflib.cp39-win_amd64.dll.a +0 -0
  28. .venv/Lib/site-packages/scipy/special/_cdflib.cp39-win_amd64.pyd +0 -0
  29. .venv/Lib/site-packages/scipy/special/_comb.cp39-win_amd64.dll.a +0 -0
  30. .venv/Lib/site-packages/scipy/special/_comb.cp39-win_amd64.pyd +0 -0
  31. .venv/Lib/site-packages/scipy/special/_ellip_harm.py +214 -0
  32. .venv/Lib/site-packages/scipy/special/_ellip_harm_2.cp39-win_amd64.dll.a +0 -0
  33. .venv/Lib/site-packages/scipy/special/_ellip_harm_2.cp39-win_amd64.pyd +0 -0
  34. .venv/Lib/site-packages/scipy/special/_lambertw.py +149 -0
  35. .venv/Lib/site-packages/scipy/special/_logsumexp.py +307 -0
  36. .venv/Lib/site-packages/scipy/special/_mptestutils.py +453 -0
  37. .venv/Lib/site-packages/scipy/special/_orthogonal.py +2605 -0
  38. .venv/Lib/site-packages/scipy/special/_orthogonal.pyi +331 -0
  39. .venv/Lib/site-packages/scipy/special/_sf_error.py +15 -0
  40. .venv/Lib/site-packages/scipy/special/_specfun.cp39-win_amd64.dll.a +0 -0
  41. .venv/Lib/site-packages/scipy/special/_specfun.cp39-win_amd64.pyd +0 -0
  42. .venv/Lib/site-packages/scipy/special/_spfun_stats.py +106 -0
  43. .venv/Lib/site-packages/scipy/special/_spherical_bessel.py +354 -0
  44. .venv/Lib/site-packages/scipy/special/_support_alternative_backends.py +75 -0
  45. .venv/Lib/site-packages/scipy/special/_test_internal.cp39-win_amd64.dll.a +0 -0
  46. .venv/Lib/site-packages/scipy/special/_test_internal.cp39-win_amd64.pyd +0 -0
  47. .venv/Lib/site-packages/scipy/special/_test_internal.pyi +9 -0
  48. .venv/Lib/site-packages/scipy/special/_testutils.py +321 -0
  49. .venv/Lib/site-packages/scipy/special/_ufuncs.cp39-win_amd64.dll.a +0 -0
  50. .venv/Lib/site-packages/scipy/special/_ufuncs.pyi +526 -0
.gitattributes CHANGED
@@ -62,3 +62,5 @@ reference_sample_wavs/syuukovoice_200918_3_01.wav filter=lfs diff=lfs merge=lfs
62
  .venv/Lib/site-packages/scipy/misc/face.dat filter=lfs diff=lfs merge=lfs -text
63
  .venv/Lib/site-packages/scipy/optimize/_highs/_highs_wrapper.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
64
  .venv/Lib/site-packages/scipy/optimize/_group_columns.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
 
 
 
62
  .venv/Lib/site-packages/scipy/misc/face.dat filter=lfs diff=lfs merge=lfs -text
63
  .venv/Lib/site-packages/scipy/optimize/_highs/_highs_wrapper.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
64
  .venv/Lib/site-packages/scipy/optimize/_group_columns.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
65
+ .venv/Lib/site-packages/scipy/signal/_max_len_seq_inner.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
66
+ .venv/Lib/site-packages/scipy/sparse/_sparsetools.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
.venv/Lib/site-packages/scipy/signal/_max_len_seq_inner.cp39-win_amd64.pyd ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ef397a9043a91f39ac1b28b5c2f4df6b98536ed8cc1ede3ba8310c38a7ff2d21
3
+ size 1005568
.venv/Lib/site-packages/scipy/sparse/_sparsetools.cp39-win_amd64.pyd ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2e30dc5bb7ebbd9fbdb71cfdf45c50f878e5cdfa29a489fa34f0b3fbb9667e05
3
+ size 4189184
.venv/Lib/site-packages/scipy/spatial/__pycache__/__init__.cpython-39.pyc ADDED
Binary file (3.98 kB). View file
 
.venv/Lib/site-packages/scipy/spatial/__pycache__/_geometric_slerp.cpython-39.pyc ADDED
Binary file (7.19 kB). View file
 
.venv/Lib/site-packages/scipy/spatial/__pycache__/_kdtree.cpython-39.pyc ADDED
Binary file (34.5 kB). View file
 
.venv/Lib/site-packages/scipy/spatial/__pycache__/_plotutils.cpython-39.pyc ADDED
Binary file (6.7 kB). View file
 
.venv/Lib/site-packages/scipy/spatial/__pycache__/_procrustes.cpython-39.pyc ADDED
Binary file (4.27 kB). View file
 
.venv/Lib/site-packages/scipy/spatial/__pycache__/_spherical_voronoi.cpython-39.pyc ADDED
Binary file (11.9 kB). View file
 
.venv/Lib/site-packages/scipy/spatial/__pycache__/ckdtree.cpython-39.pyc ADDED
Binary file (683 Bytes). View file
 
.venv/Lib/site-packages/scipy/spatial/__pycache__/distance.cpython-39.pyc ADDED
Binary file (80.7 kB). View file
 
.venv/Lib/site-packages/scipy/spatial/__pycache__/kdtree.cpython-39.pyc ADDED
Binary file (698 Bytes). View file
 
.venv/Lib/site-packages/scipy/spatial/__pycache__/qhull.cpython-39.pyc ADDED
Binary file (670 Bytes). View file
 
.venv/Lib/site-packages/scipy/spatial/transform/__init__.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Spatial Transformations (:mod:`scipy.spatial.transform`)
3
+ ========================================================
4
+
5
+ .. currentmodule:: scipy.spatial.transform
6
+
7
+ This package implements various spatial transformations. For now,
8
+ only rotations are supported.
9
+
10
+ Rotations in 3 dimensions
11
+ -------------------------
12
+ .. autosummary::
13
+ :toctree: generated/
14
+
15
+ Rotation
16
+ Slerp
17
+ RotationSpline
18
+ """
19
+ from ._rotation import Rotation, Slerp
20
+ from ._rotation_spline import RotationSpline
21
+
22
+ # Deprecated namespaces, to be removed in v2.0.0
23
+ from . import rotation
24
+
25
+ __all__ = ['Rotation', 'Slerp', 'RotationSpline']
26
+
27
+ from scipy._lib._testutils import PytestTester
28
+ test = PytestTester(__name__)
29
+ del PytestTester
.venv/Lib/site-packages/scipy/spatial/transform/__pycache__/__init__.cpython-39.pyc ADDED
Binary file (880 Bytes). View file
 
.venv/Lib/site-packages/scipy/spatial/transform/__pycache__/_rotation_groups.cpython-39.pyc ADDED
Binary file (3.6 kB). View file
 
.venv/Lib/site-packages/scipy/spatial/transform/__pycache__/_rotation_spline.cpython-39.pyc ADDED
Binary file (12.7 kB). View file
 
.venv/Lib/site-packages/scipy/spatial/transform/__pycache__/rotation.cpython-39.pyc ADDED
Binary file (679 Bytes). View file
 
.venv/Lib/site-packages/scipy/spatial/transform/_rotation_groups.py ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ from scipy.constants import golden as phi
3
+
4
+
5
+ def icosahedral(cls):
6
+ g1 = tetrahedral(cls).as_quat()
7
+ a = 0.5
8
+ b = 0.5 / phi
9
+ c = phi / 2
10
+ g2 = np.array([[+a, +b, +c, 0],
11
+ [+a, +b, -c, 0],
12
+ [+a, +c, 0, +b],
13
+ [+a, +c, 0, -b],
14
+ [+a, -b, +c, 0],
15
+ [+a, -b, -c, 0],
16
+ [+a, -c, 0, +b],
17
+ [+a, -c, 0, -b],
18
+ [+a, 0, +b, +c],
19
+ [+a, 0, +b, -c],
20
+ [+a, 0, -b, +c],
21
+ [+a, 0, -b, -c],
22
+ [+b, +a, 0, +c],
23
+ [+b, +a, 0, -c],
24
+ [+b, +c, +a, 0],
25
+ [+b, +c, -a, 0],
26
+ [+b, -a, 0, +c],
27
+ [+b, -a, 0, -c],
28
+ [+b, -c, +a, 0],
29
+ [+b, -c, -a, 0],
30
+ [+b, 0, +c, +a],
31
+ [+b, 0, +c, -a],
32
+ [+b, 0, -c, +a],
33
+ [+b, 0, -c, -a],
34
+ [+c, +a, +b, 0],
35
+ [+c, +a, -b, 0],
36
+ [+c, +b, 0, +a],
37
+ [+c, +b, 0, -a],
38
+ [+c, -a, +b, 0],
39
+ [+c, -a, -b, 0],
40
+ [+c, -b, 0, +a],
41
+ [+c, -b, 0, -a],
42
+ [+c, 0, +a, +b],
43
+ [+c, 0, +a, -b],
44
+ [+c, 0, -a, +b],
45
+ [+c, 0, -a, -b],
46
+ [0, +a, +c, +b],
47
+ [0, +a, +c, -b],
48
+ [0, +a, -c, +b],
49
+ [0, +a, -c, -b],
50
+ [0, +b, +a, +c],
51
+ [0, +b, +a, -c],
52
+ [0, +b, -a, +c],
53
+ [0, +b, -a, -c],
54
+ [0, +c, +b, +a],
55
+ [0, +c, +b, -a],
56
+ [0, +c, -b, +a],
57
+ [0, +c, -b, -a]])
58
+ return cls.from_quat(np.concatenate((g1, g2)))
59
+
60
+
61
+ def octahedral(cls):
62
+ g1 = tetrahedral(cls).as_quat()
63
+ c = np.sqrt(2) / 2
64
+ g2 = np.array([[+c, 0, 0, +c],
65
+ [0, +c, 0, +c],
66
+ [0, 0, +c, +c],
67
+ [0, 0, -c, +c],
68
+ [0, -c, 0, +c],
69
+ [-c, 0, 0, +c],
70
+ [0, +c, +c, 0],
71
+ [0, -c, +c, 0],
72
+ [+c, 0, +c, 0],
73
+ [-c, 0, +c, 0],
74
+ [+c, +c, 0, 0],
75
+ [-c, +c, 0, 0]])
76
+ return cls.from_quat(np.concatenate((g1, g2)))
77
+
78
+
79
+ def tetrahedral(cls):
80
+ g1 = np.eye(4)
81
+ c = 0.5
82
+ g2 = np.array([[c, -c, -c, +c],
83
+ [c, -c, +c, +c],
84
+ [c, +c, -c, +c],
85
+ [c, +c, +c, +c],
86
+ [c, -c, -c, -c],
87
+ [c, -c, +c, -c],
88
+ [c, +c, -c, -c],
89
+ [c, +c, +c, -c]])
90
+ return cls.from_quat(np.concatenate((g1, g2)))
91
+
92
+
93
+ def dicyclic(cls, n, axis=2):
94
+ g1 = cyclic(cls, n, axis).as_rotvec()
95
+
96
+ thetas = np.linspace(0, np.pi, n, endpoint=False)
97
+ rv = np.pi * np.vstack([np.zeros(n), np.cos(thetas), np.sin(thetas)]).T
98
+ g2 = np.roll(rv, axis, axis=1)
99
+ return cls.from_rotvec(np.concatenate((g1, g2)))
100
+
101
+
102
+ def cyclic(cls, n, axis=2):
103
+ thetas = np.linspace(0, 2 * np.pi, n, endpoint=False)
104
+ rv = np.vstack([thetas, np.zeros(n), np.zeros(n)]).T
105
+ return cls.from_rotvec(np.roll(rv, axis, axis=1))
106
+
107
+
108
+ def create_group(cls, group, axis='Z'):
109
+ if not isinstance(group, str):
110
+ raise ValueError("`group` argument must be a string")
111
+
112
+ permitted_axes = ['x', 'y', 'z', 'X', 'Y', 'Z']
113
+ if axis not in permitted_axes:
114
+ raise ValueError("`axis` must be one of " + ", ".join(permitted_axes))
115
+
116
+ if group in ['I', 'O', 'T']:
117
+ symbol = group
118
+ order = 1
119
+ elif group[:1] in ['C', 'D'] and group[1:].isdigit():
120
+ symbol = group[:1]
121
+ order = int(group[1:])
122
+ else:
123
+ raise ValueError("`group` must be one of 'I', 'O', 'T', 'Dn', 'Cn'")
124
+
125
+ if order < 1:
126
+ raise ValueError("Group order must be positive")
127
+
128
+ axis = 'xyz'.index(axis.lower())
129
+ if symbol == 'I':
130
+ return icosahedral(cls)
131
+ elif symbol == 'O':
132
+ return octahedral(cls)
133
+ elif symbol == 'T':
134
+ return tetrahedral(cls)
135
+ elif symbol == 'D':
136
+ return dicyclic(cls, order, axis=axis)
137
+ elif symbol == 'C':
138
+ return cyclic(cls, order, axis=axis)
139
+ else:
140
+ assert False
.venv/Lib/site-packages/scipy/spatial/transform/_rotation_spline.py ADDED
@@ -0,0 +1,460 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ from scipy.linalg import solve_banded
3
+ from ._rotation import Rotation
4
+
5
+
6
+ def _create_skew_matrix(x):
7
+ """Create skew-symmetric matrices corresponding to vectors.
8
+
9
+ Parameters
10
+ ----------
11
+ x : ndarray, shape (n, 3)
12
+ Set of vectors.
13
+
14
+ Returns
15
+ -------
16
+ ndarray, shape (n, 3, 3)
17
+ """
18
+ result = np.zeros((len(x), 3, 3))
19
+ result[:, 0, 1] = -x[:, 2]
20
+ result[:, 0, 2] = x[:, 1]
21
+ result[:, 1, 0] = x[:, 2]
22
+ result[:, 1, 2] = -x[:, 0]
23
+ result[:, 2, 0] = -x[:, 1]
24
+ result[:, 2, 1] = x[:, 0]
25
+ return result
26
+
27
+
28
+ def _matrix_vector_product_of_stacks(A, b):
29
+ """Compute the product of stack of matrices and vectors."""
30
+ return np.einsum("ijk,ik->ij", A, b)
31
+
32
+
33
+ def _angular_rate_to_rotvec_dot_matrix(rotvecs):
34
+ """Compute matrices to transform angular rates to rot. vector derivatives.
35
+
36
+ The matrices depend on the current attitude represented as a rotation
37
+ vector.
38
+
39
+ Parameters
40
+ ----------
41
+ rotvecs : ndarray, shape (n, 3)
42
+ Set of rotation vectors.
43
+
44
+ Returns
45
+ -------
46
+ ndarray, shape (n, 3, 3)
47
+ """
48
+ norm = np.linalg.norm(rotvecs, axis=1)
49
+ k = np.empty_like(norm)
50
+
51
+ mask = norm > 1e-4
52
+ nm = norm[mask]
53
+ k[mask] = (1 - 0.5 * nm / np.tan(0.5 * nm)) / nm**2
54
+ mask = ~mask
55
+ nm = norm[mask]
56
+ k[mask] = 1/12 + 1/720 * nm**2
57
+
58
+ skew = _create_skew_matrix(rotvecs)
59
+
60
+ result = np.empty((len(rotvecs), 3, 3))
61
+ result[:] = np.identity(3)
62
+ result[:] += 0.5 * skew
63
+ result[:] += k[:, None, None] * np.matmul(skew, skew)
64
+
65
+ return result
66
+
67
+
68
+ def _rotvec_dot_to_angular_rate_matrix(rotvecs):
69
+ """Compute matrices to transform rot. vector derivatives to angular rates.
70
+
71
+ The matrices depend on the current attitude represented as a rotation
72
+ vector.
73
+
74
+ Parameters
75
+ ----------
76
+ rotvecs : ndarray, shape (n, 3)
77
+ Set of rotation vectors.
78
+
79
+ Returns
80
+ -------
81
+ ndarray, shape (n, 3, 3)
82
+ """
83
+ norm = np.linalg.norm(rotvecs, axis=1)
84
+ k1 = np.empty_like(norm)
85
+ k2 = np.empty_like(norm)
86
+
87
+ mask = norm > 1e-4
88
+ nm = norm[mask]
89
+ k1[mask] = (1 - np.cos(nm)) / nm ** 2
90
+ k2[mask] = (nm - np.sin(nm)) / nm ** 3
91
+
92
+ mask = ~mask
93
+ nm = norm[mask]
94
+ k1[mask] = 0.5 - nm ** 2 / 24
95
+ k2[mask] = 1 / 6 - nm ** 2 / 120
96
+
97
+ skew = _create_skew_matrix(rotvecs)
98
+
99
+ result = np.empty((len(rotvecs), 3, 3))
100
+ result[:] = np.identity(3)
101
+ result[:] -= k1[:, None, None] * skew
102
+ result[:] += k2[:, None, None] * np.matmul(skew, skew)
103
+
104
+ return result
105
+
106
+
107
+ def _angular_acceleration_nonlinear_term(rotvecs, rotvecs_dot):
108
+ """Compute the non-linear term in angular acceleration.
109
+
110
+ The angular acceleration contains a quadratic term with respect to
111
+ the derivative of the rotation vector. This function computes that.
112
+
113
+ Parameters
114
+ ----------
115
+ rotvecs : ndarray, shape (n, 3)
116
+ Set of rotation vectors.
117
+ rotvecs_dot : ndarray, shape (n, 3)
118
+ Set of rotation vector derivatives.
119
+
120
+ Returns
121
+ -------
122
+ ndarray, shape (n, 3)
123
+ """
124
+ norm = np.linalg.norm(rotvecs, axis=1)
125
+ dp = np.sum(rotvecs * rotvecs_dot, axis=1)
126
+ cp = np.cross(rotvecs, rotvecs_dot)
127
+ ccp = np.cross(rotvecs, cp)
128
+ dccp = np.cross(rotvecs_dot, cp)
129
+
130
+ k1 = np.empty_like(norm)
131
+ k2 = np.empty_like(norm)
132
+ k3 = np.empty_like(norm)
133
+
134
+ mask = norm > 1e-4
135
+ nm = norm[mask]
136
+ k1[mask] = (-nm * np.sin(nm) - 2 * (np.cos(nm) - 1)) / nm ** 4
137
+ k2[mask] = (-2 * nm + 3 * np.sin(nm) - nm * np.cos(nm)) / nm ** 5
138
+ k3[mask] = (nm - np.sin(nm)) / nm ** 3
139
+
140
+ mask = ~mask
141
+ nm = norm[mask]
142
+ k1[mask] = 1/12 - nm ** 2 / 180
143
+ k2[mask] = -1/60 + nm ** 2 / 12604
144
+ k3[mask] = 1/6 - nm ** 2 / 120
145
+
146
+ dp = dp[:, None]
147
+ k1 = k1[:, None]
148
+ k2 = k2[:, None]
149
+ k3 = k3[:, None]
150
+
151
+ return dp * (k1 * cp + k2 * ccp) + k3 * dccp
152
+
153
+
154
+ def _compute_angular_rate(rotvecs, rotvecs_dot):
155
+ """Compute angular rates given rotation vectors and its derivatives.
156
+
157
+ Parameters
158
+ ----------
159
+ rotvecs : ndarray, shape (n, 3)
160
+ Set of rotation vectors.
161
+ rotvecs_dot : ndarray, shape (n, 3)
162
+ Set of rotation vector derivatives.
163
+
164
+ Returns
165
+ -------
166
+ ndarray, shape (n, 3)
167
+ """
168
+ return _matrix_vector_product_of_stacks(
169
+ _rotvec_dot_to_angular_rate_matrix(rotvecs), rotvecs_dot)
170
+
171
+
172
+ def _compute_angular_acceleration(rotvecs, rotvecs_dot, rotvecs_dot_dot):
173
+ """Compute angular acceleration given rotation vector and its derivatives.
174
+
175
+ Parameters
176
+ ----------
177
+ rotvecs : ndarray, shape (n, 3)
178
+ Set of rotation vectors.
179
+ rotvecs_dot : ndarray, shape (n, 3)
180
+ Set of rotation vector derivatives.
181
+ rotvecs_dot_dot : ndarray, shape (n, 3)
182
+ Set of rotation vector second derivatives.
183
+
184
+ Returns
185
+ -------
186
+ ndarray, shape (n, 3)
187
+ """
188
+ return (_compute_angular_rate(rotvecs, rotvecs_dot_dot) +
189
+ _angular_acceleration_nonlinear_term(rotvecs, rotvecs_dot))
190
+
191
+
192
+ def _create_block_3_diagonal_matrix(A, B, d):
193
+ """Create a 3-diagonal block matrix as banded.
194
+
195
+ The matrix has the following structure:
196
+
197
+ DB...
198
+ ADB..
199
+ .ADB.
200
+ ..ADB
201
+ ...AD
202
+
203
+ The blocks A, B and D are 3-by-3 matrices. The D matrices has the form
204
+ d * I.
205
+
206
+ Parameters
207
+ ----------
208
+ A : ndarray, shape (n, 3, 3)
209
+ Stack of A blocks.
210
+ B : ndarray, shape (n, 3, 3)
211
+ Stack of B blocks.
212
+ d : ndarray, shape (n + 1,)
213
+ Values for diagonal blocks.
214
+
215
+ Returns
216
+ -------
217
+ ndarray, shape (11, 3 * (n + 1))
218
+ Matrix in the banded form as used by `scipy.linalg.solve_banded`.
219
+ """
220
+ ind = np.arange(3)
221
+ ind_blocks = np.arange(len(A))
222
+
223
+ A_i = np.empty_like(A, dtype=int)
224
+ A_i[:] = ind[:, None]
225
+ A_i += 3 * (1 + ind_blocks[:, None, None])
226
+
227
+ A_j = np.empty_like(A, dtype=int)
228
+ A_j[:] = ind
229
+ A_j += 3 * ind_blocks[:, None, None]
230
+
231
+ B_i = np.empty_like(B, dtype=int)
232
+ B_i[:] = ind[:, None]
233
+ B_i += 3 * ind_blocks[:, None, None]
234
+
235
+ B_j = np.empty_like(B, dtype=int)
236
+ B_j[:] = ind
237
+ B_j += 3 * (1 + ind_blocks[:, None, None])
238
+
239
+ diag_i = diag_j = np.arange(3 * len(d))
240
+ i = np.hstack((A_i.ravel(), B_i.ravel(), diag_i))
241
+ j = np.hstack((A_j.ravel(), B_j.ravel(), diag_j))
242
+ values = np.hstack((A.ravel(), B.ravel(), np.repeat(d, 3)))
243
+
244
+ u = 5
245
+ l = 5
246
+ result = np.zeros((u + l + 1, 3 * len(d)))
247
+ result[u + i - j, j] = values
248
+ return result
249
+
250
+
251
+ class RotationSpline:
252
+ """Interpolate rotations with continuous angular rate and acceleration.
253
+
254
+ The rotation vectors between each consecutive orientation are cubic
255
+ functions of time and it is guaranteed that angular rate and acceleration
256
+ are continuous. Such interpolation are analogous to cubic spline
257
+ interpolation.
258
+
259
+ Refer to [1]_ for math and implementation details.
260
+
261
+ Parameters
262
+ ----------
263
+ times : array_like, shape (N,)
264
+ Times of the known rotations. At least 2 times must be specified.
265
+ rotations : `Rotation` instance
266
+ Rotations to perform the interpolation between. Must contain N
267
+ rotations.
268
+
269
+ Methods
270
+ -------
271
+ __call__
272
+
273
+ References
274
+ ----------
275
+ .. [1] `Smooth Attitude Interpolation
276
+ <https://github.com/scipy/scipy/files/2932755/attitude_interpolation.pdf>`_
277
+
278
+ Examples
279
+ --------
280
+ >>> from scipy.spatial.transform import Rotation, RotationSpline
281
+ >>> import numpy as np
282
+
283
+ Define the sequence of times and rotations from the Euler angles:
284
+
285
+ >>> times = [0, 10, 20, 40]
286
+ >>> angles = [[-10, 20, 30], [0, 15, 40], [-30, 45, 30], [20, 45, 90]]
287
+ >>> rotations = Rotation.from_euler('XYZ', angles, degrees=True)
288
+
289
+ Create the interpolator object:
290
+
291
+ >>> spline = RotationSpline(times, rotations)
292
+
293
+ Interpolate the Euler angles, angular rate and acceleration:
294
+
295
+ >>> angular_rate = np.rad2deg(spline(times, 1))
296
+ >>> angular_acceleration = np.rad2deg(spline(times, 2))
297
+ >>> times_plot = np.linspace(times[0], times[-1], 100)
298
+ >>> angles_plot = spline(times_plot).as_euler('XYZ', degrees=True)
299
+ >>> angular_rate_plot = np.rad2deg(spline(times_plot, 1))
300
+ >>> angular_acceleration_plot = np.rad2deg(spline(times_plot, 2))
301
+
302
+ On this plot you see that Euler angles are continuous and smooth:
303
+
304
+ >>> import matplotlib.pyplot as plt
305
+ >>> plt.plot(times_plot, angles_plot)
306
+ >>> plt.plot(times, angles, 'x')
307
+ >>> plt.title("Euler angles")
308
+ >>> plt.show()
309
+
310
+ The angular rate is also smooth:
311
+
312
+ >>> plt.plot(times_plot, angular_rate_plot)
313
+ >>> plt.plot(times, angular_rate, 'x')
314
+ >>> plt.title("Angular rate")
315
+ >>> plt.show()
316
+
317
+ The angular acceleration is continuous, but not smooth. Also note that
318
+ the angular acceleration is not a piecewise-linear function, because
319
+ it is different from the second derivative of the rotation vector (which
320
+ is a piecewise-linear function as in the cubic spline).
321
+
322
+ >>> plt.plot(times_plot, angular_acceleration_plot)
323
+ >>> plt.plot(times, angular_acceleration, 'x')
324
+ >>> plt.title("Angular acceleration")
325
+ >>> plt.show()
326
+ """
327
+ # Parameters for the solver for angular rate.
328
+ MAX_ITER = 10
329
+ TOL = 1e-9
330
+
331
+ def _solve_for_angular_rates(self, dt, angular_rates, rotvecs):
332
+ angular_rate_first = angular_rates[0].copy()
333
+
334
+ A = _angular_rate_to_rotvec_dot_matrix(rotvecs)
335
+ A_inv = _rotvec_dot_to_angular_rate_matrix(rotvecs)
336
+ M = _create_block_3_diagonal_matrix(
337
+ 2 * A_inv[1:-1] / dt[1:-1, None, None],
338
+ 2 * A[1:-1] / dt[1:-1, None, None],
339
+ 4 * (1 / dt[:-1] + 1 / dt[1:]))
340
+
341
+ b0 = 6 * (rotvecs[:-1] * dt[:-1, None] ** -2 +
342
+ rotvecs[1:] * dt[1:, None] ** -2)
343
+ b0[0] -= 2 / dt[0] * A_inv[0].dot(angular_rate_first)
344
+ b0[-1] -= 2 / dt[-1] * A[-1].dot(angular_rates[-1])
345
+
346
+ for iteration in range(self.MAX_ITER):
347
+ rotvecs_dot = _matrix_vector_product_of_stacks(A, angular_rates)
348
+ delta_beta = _angular_acceleration_nonlinear_term(
349
+ rotvecs[:-1], rotvecs_dot[:-1])
350
+ b = b0 - delta_beta
351
+ angular_rates_new = solve_banded((5, 5), M, b.ravel())
352
+ angular_rates_new = angular_rates_new.reshape((-1, 3))
353
+
354
+ delta = np.abs(angular_rates_new - angular_rates[:-1])
355
+ angular_rates[:-1] = angular_rates_new
356
+ if np.all(delta < self.TOL * (1 + np.abs(angular_rates_new))):
357
+ break
358
+
359
+ rotvecs_dot = _matrix_vector_product_of_stacks(A, angular_rates)
360
+ angular_rates = np.vstack((angular_rate_first, angular_rates[:-1]))
361
+
362
+ return angular_rates, rotvecs_dot
363
+
364
+ def __init__(self, times, rotations):
365
+ from scipy.interpolate import PPoly
366
+
367
+ if rotations.single:
368
+ raise ValueError("`rotations` must be a sequence of rotations.")
369
+
370
+ if len(rotations) == 1:
371
+ raise ValueError("`rotations` must contain at least 2 rotations.")
372
+
373
+ times = np.asarray(times, dtype=float)
374
+ if times.ndim != 1:
375
+ raise ValueError("`times` must be 1-dimensional.")
376
+
377
+ if len(times) != len(rotations):
378
+ raise ValueError("Expected number of rotations to be equal to "
379
+ "number of timestamps given, got {} rotations "
380
+ "and {} timestamps."
381
+ .format(len(rotations), len(times)))
382
+
383
+ dt = np.diff(times)
384
+ if np.any(dt <= 0):
385
+ raise ValueError("Values in `times` must be in a strictly "
386
+ "increasing order.")
387
+
388
+ rotvecs = (rotations[:-1].inv() * rotations[1:]).as_rotvec()
389
+ angular_rates = rotvecs / dt[:, None]
390
+
391
+ if len(rotations) == 2:
392
+ rotvecs_dot = angular_rates
393
+ else:
394
+ angular_rates, rotvecs_dot = self._solve_for_angular_rates(
395
+ dt, angular_rates, rotvecs)
396
+
397
+ dt = dt[:, None]
398
+ coeff = np.empty((4, len(times) - 1, 3))
399
+ coeff[0] = (-2 * rotvecs + dt * angular_rates
400
+ + dt * rotvecs_dot) / dt ** 3
401
+ coeff[1] = (3 * rotvecs - 2 * dt * angular_rates
402
+ - dt * rotvecs_dot) / dt ** 2
403
+ coeff[2] = angular_rates
404
+ coeff[3] = 0
405
+
406
+ self.times = times
407
+ self.rotations = rotations
408
+ self.interpolator = PPoly(coeff, times)
409
+
410
+ def __call__(self, times, order=0):
411
+ """Compute interpolated values.
412
+
413
+ Parameters
414
+ ----------
415
+ times : float or array_like
416
+ Times of interest.
417
+ order : {0, 1, 2}, optional
418
+ Order of differentiation:
419
+
420
+ * 0 (default) : return Rotation
421
+ * 1 : return the angular rate in rad/sec
422
+ * 2 : return the angular acceleration in rad/sec/sec
423
+
424
+ Returns
425
+ -------
426
+ Interpolated Rotation, angular rate or acceleration.
427
+ """
428
+ if order not in [0, 1, 2]:
429
+ raise ValueError("`order` must be 0, 1 or 2.")
430
+
431
+ times = np.asarray(times, dtype=float)
432
+ if times.ndim > 1:
433
+ raise ValueError("`times` must be at most 1-dimensional.")
434
+
435
+ singe_time = times.ndim == 0
436
+ times = np.atleast_1d(times)
437
+
438
+ rotvecs = self.interpolator(times)
439
+ if order == 0:
440
+ index = np.searchsorted(self.times, times, side='right')
441
+ index -= 1
442
+ index[index < 0] = 0
443
+ n_segments = len(self.times) - 1
444
+ index[index > n_segments - 1] = n_segments - 1
445
+ result = self.rotations[index] * Rotation.from_rotvec(rotvecs)
446
+ elif order == 1:
447
+ rotvecs_dot = self.interpolator(times, 1)
448
+ result = _compute_angular_rate(rotvecs, rotvecs_dot)
449
+ elif order == 2:
450
+ rotvecs_dot = self.interpolator(times, 1)
451
+ rotvecs_dot_dot = self.interpolator(times, 2)
452
+ result = _compute_angular_acceleration(rotvecs, rotvecs_dot,
453
+ rotvecs_dot_dot)
454
+ else:
455
+ assert False
456
+
457
+ if singe_time:
458
+ result = result[0]
459
+
460
+ return result
.venv/Lib/site-packages/scipy/spatial/transform/tests/__init__.py ADDED
File without changes
.venv/Lib/site-packages/scipy/spatial/transform/tests/test_rotation.py ADDED
@@ -0,0 +1,1906 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pytest
2
+
3
+ import numpy as np
4
+ from numpy.testing import assert_equal, assert_array_almost_equal
5
+ from numpy.testing import assert_allclose
6
+ from scipy.spatial.transform import Rotation, Slerp
7
+ from scipy.stats import special_ortho_group
8
+ from itertools import permutations
9
+
10
+ import pickle
11
+ import copy
12
+
13
+ def basis_vec(axis):
14
+ if axis == 'x':
15
+ return [1, 0, 0]
16
+ elif axis == 'y':
17
+ return [0, 1, 0]
18
+ elif axis == 'z':
19
+ return [0, 0, 1]
20
+
21
+ def test_generic_quat_matrix():
22
+ x = np.array([[3, 4, 0, 0], [5, 12, 0, 0]])
23
+ r = Rotation.from_quat(x)
24
+ expected_quat = x / np.array([[5], [13]])
25
+ assert_array_almost_equal(r.as_quat(), expected_quat)
26
+
27
+
28
+ def test_from_single_1d_quaternion():
29
+ x = np.array([3, 4, 0, 0])
30
+ r = Rotation.from_quat(x)
31
+ expected_quat = x / 5
32
+ assert_array_almost_equal(r.as_quat(), expected_quat)
33
+
34
+
35
+ def test_from_single_2d_quaternion():
36
+ x = np.array([[3, 4, 0, 0]])
37
+ r = Rotation.from_quat(x)
38
+ expected_quat = x / 5
39
+ assert_array_almost_equal(r.as_quat(), expected_quat)
40
+
41
+
42
+ def test_from_square_quat_matrix():
43
+ # Ensure proper norm array broadcasting
44
+ x = np.array([
45
+ [3, 0, 0, 4],
46
+ [5, 0, 12, 0],
47
+ [0, 0, 0, 1],
48
+ [-1, -1, -1, 1],
49
+ [0, 0, 0, -1], # Check double cover
50
+ [-1, -1, -1, -1] # Check double cover
51
+ ])
52
+ r = Rotation.from_quat(x)
53
+ expected_quat = x / np.array([[5], [13], [1], [2], [1], [2]])
54
+ assert_array_almost_equal(r.as_quat(), expected_quat)
55
+
56
+
57
+ def test_quat_double_to_canonical_single_cover():
58
+ x = np.array([
59
+ [-1, 0, 0, 0],
60
+ [0, -1, 0, 0],
61
+ [0, 0, -1, 0],
62
+ [0, 0, 0, -1],
63
+ [-1, -1, -1, -1]
64
+ ])
65
+ r = Rotation.from_quat(x)
66
+ expected_quat = np.abs(x) / np.linalg.norm(x, axis=1)[:, None]
67
+ assert_allclose(r.as_quat(canonical=True), expected_quat)
68
+
69
+
70
+ def test_quat_double_cover():
71
+ # See the Rotation.from_quat() docstring for scope of the quaternion
72
+ # double cover property.
73
+ # Check from_quat and as_quat(canonical=False)
74
+ q = np.array([0, 0, 0, -1])
75
+ r = Rotation.from_quat(q)
76
+ assert_equal(q, r.as_quat(canonical=False))
77
+
78
+ # Check composition and inverse
79
+ q = np.array([1, 0, 0, 1])/np.sqrt(2) # 90 deg rotation about x
80
+ r = Rotation.from_quat(q)
81
+ r3 = r*r*r
82
+ assert_allclose(r.as_quat(canonical=False)*np.sqrt(2),
83
+ [1, 0, 0, 1])
84
+ assert_allclose(r.inv().as_quat(canonical=False)*np.sqrt(2),
85
+ [-1, 0, 0, 1])
86
+ assert_allclose(r3.as_quat(canonical=False)*np.sqrt(2),
87
+ [1, 0, 0, -1])
88
+ assert_allclose(r3.inv().as_quat(canonical=False)*np.sqrt(2),
89
+ [-1, 0, 0, -1])
90
+
91
+ # More sanity checks
92
+ assert_allclose((r*r.inv()).as_quat(canonical=False),
93
+ [0, 0, 0, 1], atol=2e-16)
94
+ assert_allclose((r3*r3.inv()).as_quat(canonical=False),
95
+ [0, 0, 0, 1], atol=2e-16)
96
+ assert_allclose((r*r3).as_quat(canonical=False),
97
+ [0, 0, 0, -1], atol=2e-16)
98
+ assert_allclose((r.inv()*r3.inv()).as_quat(canonical=False),
99
+ [0, 0, 0, -1], atol=2e-16)
100
+
101
+
102
+ def test_malformed_1d_from_quat():
103
+ with pytest.raises(ValueError):
104
+ Rotation.from_quat(np.array([1, 2, 3]))
105
+
106
+
107
+ def test_malformed_2d_from_quat():
108
+ with pytest.raises(ValueError):
109
+ Rotation.from_quat(np.array([
110
+ [1, 2, 3, 4, 5],
111
+ [4, 5, 6, 7, 8]
112
+ ]))
113
+
114
+
115
+ def test_zero_norms_from_quat():
116
+ x = np.array([
117
+ [3, 4, 0, 0],
118
+ [0, 0, 0, 0],
119
+ [5, 0, 12, 0]
120
+ ])
121
+ with pytest.raises(ValueError):
122
+ Rotation.from_quat(x)
123
+
124
+
125
+ def test_as_matrix_single_1d_quaternion():
126
+ quat = [0, 0, 0, 1]
127
+ mat = Rotation.from_quat(quat).as_matrix()
128
+ # mat.shape == (3,3) due to 1d input
129
+ assert_array_almost_equal(mat, np.eye(3))
130
+
131
+
132
+ def test_as_matrix_single_2d_quaternion():
133
+ quat = [[0, 0, 1, 1]]
134
+ mat = Rotation.from_quat(quat).as_matrix()
135
+ assert_equal(mat.shape, (1, 3, 3))
136
+ expected_mat = np.array([
137
+ [0, -1, 0],
138
+ [1, 0, 0],
139
+ [0, 0, 1]
140
+ ])
141
+ assert_array_almost_equal(mat[0], expected_mat)
142
+
143
+
144
+ def test_as_matrix_from_square_input():
145
+ quats = [
146
+ [0, 0, 1, 1],
147
+ [0, 1, 0, 1],
148
+ [0, 0, 0, 1],
149
+ [0, 0, 0, -1]
150
+ ]
151
+ mat = Rotation.from_quat(quats).as_matrix()
152
+ assert_equal(mat.shape, (4, 3, 3))
153
+
154
+ expected0 = np.array([
155
+ [0, -1, 0],
156
+ [1, 0, 0],
157
+ [0, 0, 1]
158
+ ])
159
+ assert_array_almost_equal(mat[0], expected0)
160
+
161
+ expected1 = np.array([
162
+ [0, 0, 1],
163
+ [0, 1, 0],
164
+ [-1, 0, 0]
165
+ ])
166
+ assert_array_almost_equal(mat[1], expected1)
167
+
168
+ assert_array_almost_equal(mat[2], np.eye(3))
169
+ assert_array_almost_equal(mat[3], np.eye(3))
170
+
171
+
172
+ def test_as_matrix_from_generic_input():
173
+ quats = [
174
+ [0, 0, 1, 1],
175
+ [0, 1, 0, 1],
176
+ [1, 2, 3, 4]
177
+ ]
178
+ mat = Rotation.from_quat(quats).as_matrix()
179
+ assert_equal(mat.shape, (3, 3, 3))
180
+
181
+ expected0 = np.array([
182
+ [0, -1, 0],
183
+ [1, 0, 0],
184
+ [0, 0, 1]
185
+ ])
186
+ assert_array_almost_equal(mat[0], expected0)
187
+
188
+ expected1 = np.array([
189
+ [0, 0, 1],
190
+ [0, 1, 0],
191
+ [-1, 0, 0]
192
+ ])
193
+ assert_array_almost_equal(mat[1], expected1)
194
+
195
+ expected2 = np.array([
196
+ [0.4, -2, 2.2],
197
+ [2.8, 1, 0.4],
198
+ [-1, 2, 2]
199
+ ]) / 3
200
+ assert_array_almost_equal(mat[2], expected2)
201
+
202
+
203
+ def test_from_single_2d_matrix():
204
+ mat = [
205
+ [0, 0, 1],
206
+ [1, 0, 0],
207
+ [0, 1, 0]
208
+ ]
209
+ expected_quat = [0.5, 0.5, 0.5, 0.5]
210
+ assert_array_almost_equal(
211
+ Rotation.from_matrix(mat).as_quat(),
212
+ expected_quat)
213
+
214
+
215
+ def test_from_single_3d_matrix():
216
+ mat = np.array([
217
+ [0, 0, 1],
218
+ [1, 0, 0],
219
+ [0, 1, 0]
220
+ ]).reshape((1, 3, 3))
221
+ expected_quat = np.array([0.5, 0.5, 0.5, 0.5]).reshape((1, 4))
222
+ assert_array_almost_equal(
223
+ Rotation.from_matrix(mat).as_quat(),
224
+ expected_quat)
225
+
226
+
227
+ def test_from_matrix_calculation():
228
+ expected_quat = np.array([1, 1, 6, 1]) / np.sqrt(39)
229
+ mat = np.array([
230
+ [-0.8974359, -0.2564103, 0.3589744],
231
+ [0.3589744, -0.8974359, 0.2564103],
232
+ [0.2564103, 0.3589744, 0.8974359]
233
+ ])
234
+ assert_array_almost_equal(
235
+ Rotation.from_matrix(mat).as_quat(),
236
+ expected_quat)
237
+ assert_array_almost_equal(
238
+ Rotation.from_matrix(mat.reshape((1, 3, 3))).as_quat(),
239
+ expected_quat.reshape((1, 4)))
240
+
241
+
242
+ def test_matrix_calculation_pipeline():
243
+ mat = special_ortho_group.rvs(3, size=10, random_state=0)
244
+ assert_array_almost_equal(Rotation.from_matrix(mat).as_matrix(), mat)
245
+
246
+
247
+ def test_from_matrix_ortho_output():
248
+ rnd = np.random.RandomState(0)
249
+ mat = rnd.random_sample((100, 3, 3))
250
+ ortho_mat = Rotation.from_matrix(mat).as_matrix()
251
+
252
+ mult_result = np.einsum('...ij,...jk->...ik', ortho_mat,
253
+ ortho_mat.transpose((0, 2, 1)))
254
+
255
+ eye3d = np.zeros((100, 3, 3))
256
+ for i in range(3):
257
+ eye3d[:, i, i] = 1.0
258
+
259
+ assert_array_almost_equal(mult_result, eye3d)
260
+
261
+
262
+ def test_from_1d_single_rotvec():
263
+ rotvec = [1, 0, 0]
264
+ expected_quat = np.array([0.4794255, 0, 0, 0.8775826])
265
+ result = Rotation.from_rotvec(rotvec)
266
+ assert_array_almost_equal(result.as_quat(), expected_quat)
267
+
268
+
269
+ def test_from_2d_single_rotvec():
270
+ rotvec = [[1, 0, 0]]
271
+ expected_quat = np.array([[0.4794255, 0, 0, 0.8775826]])
272
+ result = Rotation.from_rotvec(rotvec)
273
+ assert_array_almost_equal(result.as_quat(), expected_quat)
274
+
275
+
276
+ def test_from_generic_rotvec():
277
+ rotvec = [
278
+ [1, 2, 2],
279
+ [1, -1, 0.5],
280
+ [0, 0, 0]
281
+ ]
282
+ expected_quat = np.array([
283
+ [0.3324983, 0.6649967, 0.6649967, 0.0707372],
284
+ [0.4544258, -0.4544258, 0.2272129, 0.7316889],
285
+ [0, 0, 0, 1]
286
+ ])
287
+ assert_array_almost_equal(
288
+ Rotation.from_rotvec(rotvec).as_quat(),
289
+ expected_quat)
290
+
291
+
292
+ def test_from_rotvec_small_angle():
293
+ rotvec = np.array([
294
+ [5e-4 / np.sqrt(3), -5e-4 / np.sqrt(3), 5e-4 / np.sqrt(3)],
295
+ [0.2, 0.3, 0.4],
296
+ [0, 0, 0]
297
+ ])
298
+
299
+ quat = Rotation.from_rotvec(rotvec).as_quat()
300
+ # cos(theta/2) ~~ 1 for small theta
301
+ assert_allclose(quat[0, 3], 1)
302
+ # sin(theta/2) / theta ~~ 0.5 for small theta
303
+ assert_allclose(quat[0, :3], rotvec[0] * 0.5)
304
+
305
+ assert_allclose(quat[1, 3], 0.9639685)
306
+ assert_allclose(
307
+ quat[1, :3],
308
+ np.array([
309
+ 0.09879603932153465,
310
+ 0.14819405898230198,
311
+ 0.19759207864306931
312
+ ]))
313
+
314
+ assert_equal(quat[2], np.array([0, 0, 0, 1]))
315
+
316
+
317
+ def test_degrees_from_rotvec():
318
+ rotvec1 = [1.0 / np.cbrt(3), 1.0 / np.cbrt(3), 1.0 / np.cbrt(3)]
319
+ rot1 = Rotation.from_rotvec(rotvec1, degrees=True)
320
+ quat1 = rot1.as_quat()
321
+
322
+ rotvec2 = np.deg2rad(rotvec1)
323
+ rot2 = Rotation.from_rotvec(rotvec2)
324
+ quat2 = rot2.as_quat()
325
+
326
+ assert_allclose(quat1, quat2)
327
+
328
+
329
+ def test_malformed_1d_from_rotvec():
330
+ with pytest.raises(ValueError, match='Expected `rot_vec` to have shape'):
331
+ Rotation.from_rotvec([1, 2])
332
+
333
+
334
+ def test_malformed_2d_from_rotvec():
335
+ with pytest.raises(ValueError, match='Expected `rot_vec` to have shape'):
336
+ Rotation.from_rotvec([
337
+ [1, 2, 3, 4],
338
+ [5, 6, 7, 8]
339
+ ])
340
+
341
+
342
+ def test_as_generic_rotvec():
343
+ quat = np.array([
344
+ [1, 2, -1, 0.5],
345
+ [1, -1, 1, 0.0003],
346
+ [0, 0, 0, 1]
347
+ ])
348
+ quat /= np.linalg.norm(quat, axis=1)[:, None]
349
+
350
+ rotvec = Rotation.from_quat(quat).as_rotvec()
351
+ angle = np.linalg.norm(rotvec, axis=1)
352
+
353
+ assert_allclose(quat[:, 3], np.cos(angle/2))
354
+ assert_allclose(np.cross(rotvec, quat[:, :3]), np.zeros((3, 3)))
355
+
356
+
357
+ def test_as_rotvec_single_1d_input():
358
+ quat = np.array([1, 2, -3, 2])
359
+ expected_rotvec = np.array([0.5772381, 1.1544763, -1.7317144])
360
+
361
+ actual_rotvec = Rotation.from_quat(quat).as_rotvec()
362
+
363
+ assert_equal(actual_rotvec.shape, (3,))
364
+ assert_allclose(actual_rotvec, expected_rotvec)
365
+
366
+
367
+ def test_as_rotvec_single_2d_input():
368
+ quat = np.array([[1, 2, -3, 2]])
369
+ expected_rotvec = np.array([[0.5772381, 1.1544763, -1.7317144]])
370
+
371
+ actual_rotvec = Rotation.from_quat(quat).as_rotvec()
372
+
373
+ assert_equal(actual_rotvec.shape, (1, 3))
374
+ assert_allclose(actual_rotvec, expected_rotvec)
375
+
376
+
377
+ def test_as_rotvec_degrees():
378
+ # x->y, y->z, z->x
379
+ mat = [[0, 0, 1], [1, 0, 0], [0, 1, 0]]
380
+ rot = Rotation.from_matrix(mat)
381
+ rotvec = rot.as_rotvec(degrees=True)
382
+ angle = np.linalg.norm(rotvec)
383
+ assert_allclose(angle, 120.0)
384
+ assert_allclose(rotvec[0], rotvec[1])
385
+ assert_allclose(rotvec[1], rotvec[2])
386
+
387
+
388
+ def test_rotvec_calc_pipeline():
389
+ # Include small angles
390
+ rotvec = np.array([
391
+ [0, 0, 0],
392
+ [1, -1, 2],
393
+ [-3e-4, 3.5e-4, 7.5e-5]
394
+ ])
395
+ assert_allclose(Rotation.from_rotvec(rotvec).as_rotvec(), rotvec)
396
+ assert_allclose(Rotation.from_rotvec(rotvec, degrees=True).as_rotvec(degrees=True),
397
+ rotvec)
398
+
399
+
400
+ def test_from_1d_single_mrp():
401
+ mrp = [0, 0, 1.0]
402
+ expected_quat = np.array([0, 0, 1, 0])
403
+ result = Rotation.from_mrp(mrp)
404
+ assert_array_almost_equal(result.as_quat(), expected_quat)
405
+
406
+
407
+ def test_from_2d_single_mrp():
408
+ mrp = [[0, 0, 1.0]]
409
+ expected_quat = np.array([[0, 0, 1, 0]])
410
+ result = Rotation.from_mrp(mrp)
411
+ assert_array_almost_equal(result.as_quat(), expected_quat)
412
+
413
+
414
+ def test_from_generic_mrp():
415
+ mrp = np.array([
416
+ [1, 2, 2],
417
+ [1, -1, 0.5],
418
+ [0, 0, 0]])
419
+ expected_quat = np.array([
420
+ [0.2, 0.4, 0.4, -0.8],
421
+ [0.61538462, -0.61538462, 0.30769231, -0.38461538],
422
+ [0, 0, 0, 1]])
423
+ assert_array_almost_equal(Rotation.from_mrp(mrp).as_quat(), expected_quat)
424
+
425
+
426
+ def test_malformed_1d_from_mrp():
427
+ with pytest.raises(ValueError, match='Expected `mrp` to have shape'):
428
+ Rotation.from_mrp([1, 2])
429
+
430
+
431
+ def test_malformed_2d_from_mrp():
432
+ with pytest.raises(ValueError, match='Expected `mrp` to have shape'):
433
+ Rotation.from_mrp([
434
+ [1, 2, 3, 4],
435
+ [5, 6, 7, 8]
436
+ ])
437
+
438
+
439
+ def test_as_generic_mrp():
440
+ quat = np.array([
441
+ [1, 2, -1, 0.5],
442
+ [1, -1, 1, 0.0003],
443
+ [0, 0, 0, 1]])
444
+ quat /= np.linalg.norm(quat, axis=1)[:, None]
445
+
446
+ expected_mrp = np.array([
447
+ [0.33333333, 0.66666667, -0.33333333],
448
+ [0.57725028, -0.57725028, 0.57725028],
449
+ [0, 0, 0]])
450
+ assert_array_almost_equal(Rotation.from_quat(quat).as_mrp(), expected_mrp)
451
+
452
+ def test_past_180_degree_rotation():
453
+ # ensure that a > 180 degree rotation is returned as a <180 rotation in MRPs
454
+ # in this case 270 should be returned as -90
455
+ expected_mrp = np.array([-np.tan(np.pi/2/4), 0.0, 0])
456
+ assert_array_almost_equal(
457
+ Rotation.from_euler('xyz', [270, 0, 0], degrees=True).as_mrp(),
458
+ expected_mrp
459
+ )
460
+
461
+
462
+ def test_as_mrp_single_1d_input():
463
+ quat = np.array([1, 2, -3, 2])
464
+ expected_mrp = np.array([0.16018862, 0.32037724, -0.48056586])
465
+
466
+ actual_mrp = Rotation.from_quat(quat).as_mrp()
467
+
468
+ assert_equal(actual_mrp.shape, (3,))
469
+ assert_allclose(actual_mrp, expected_mrp)
470
+
471
+
472
+ def test_as_mrp_single_2d_input():
473
+ quat = np.array([[1, 2, -3, 2]])
474
+ expected_mrp = np.array([[0.16018862, 0.32037724, -0.48056586]])
475
+
476
+ actual_mrp = Rotation.from_quat(quat).as_mrp()
477
+
478
+ assert_equal(actual_mrp.shape, (1, 3))
479
+ assert_allclose(actual_mrp, expected_mrp)
480
+
481
+
482
+ def test_mrp_calc_pipeline():
483
+ actual_mrp = np.array([
484
+ [0, 0, 0],
485
+ [1, -1, 2],
486
+ [0.41421356, 0, 0],
487
+ [0.1, 0.2, 0.1]])
488
+ expected_mrp = np.array([
489
+ [0, 0, 0],
490
+ [-0.16666667, 0.16666667, -0.33333333],
491
+ [0.41421356, 0, 0],
492
+ [0.1, 0.2, 0.1]])
493
+ assert_allclose(Rotation.from_mrp(actual_mrp).as_mrp(), expected_mrp)
494
+
495
+
496
+ def test_from_euler_single_rotation():
497
+ quat = Rotation.from_euler('z', 90, degrees=True).as_quat()
498
+ expected_quat = np.array([0, 0, 1, 1]) / np.sqrt(2)
499
+ assert_allclose(quat, expected_quat)
500
+
501
+
502
+ def test_single_intrinsic_extrinsic_rotation():
503
+ extrinsic = Rotation.from_euler('z', 90, degrees=True).as_matrix()
504
+ intrinsic = Rotation.from_euler('Z', 90, degrees=True).as_matrix()
505
+ assert_allclose(extrinsic, intrinsic)
506
+
507
+
508
+ def test_from_euler_rotation_order():
509
+ # Intrinsic rotation is same as extrinsic with order reversed
510
+ rnd = np.random.RandomState(0)
511
+ a = rnd.randint(low=0, high=180, size=(6, 3))
512
+ b = a[:, ::-1]
513
+ x = Rotation.from_euler('xyz', a, degrees=True).as_quat()
514
+ y = Rotation.from_euler('ZYX', b, degrees=True).as_quat()
515
+ assert_allclose(x, y)
516
+
517
+
518
+ def test_from_euler_elementary_extrinsic_rotation():
519
+ # Simple test to check if extrinsic rotations are implemented correctly
520
+ mat = Rotation.from_euler('zx', [90, 90], degrees=True).as_matrix()
521
+ expected_mat = np.array([
522
+ [0, -1, 0],
523
+ [0, 0, -1],
524
+ [1, 0, 0]
525
+ ])
526
+ assert_array_almost_equal(mat, expected_mat)
527
+
528
+
529
+ def test_from_euler_intrinsic_rotation_312():
530
+ angles = [
531
+ [30, 60, 45],
532
+ [30, 60, 30],
533
+ [45, 30, 60]
534
+ ]
535
+ mat = Rotation.from_euler('ZXY', angles, degrees=True).as_matrix()
536
+
537
+ assert_array_almost_equal(mat[0], np.array([
538
+ [0.3061862, -0.2500000, 0.9185587],
539
+ [0.8838835, 0.4330127, -0.1767767],
540
+ [-0.3535534, 0.8660254, 0.3535534]
541
+ ]))
542
+
543
+ assert_array_almost_equal(mat[1], np.array([
544
+ [0.5334936, -0.2500000, 0.8080127],
545
+ [0.8080127, 0.4330127, -0.3995191],
546
+ [-0.2500000, 0.8660254, 0.4330127]
547
+ ]))
548
+
549
+ assert_array_almost_equal(mat[2], np.array([
550
+ [0.0473672, -0.6123725, 0.7891491],
551
+ [0.6597396, 0.6123725, 0.4355958],
552
+ [-0.7500000, 0.5000000, 0.4330127]
553
+ ]))
554
+
555
+
556
+ def test_from_euler_intrinsic_rotation_313():
557
+ angles = [
558
+ [30, 60, 45],
559
+ [30, 60, 30],
560
+ [45, 30, 60]
561
+ ]
562
+ mat = Rotation.from_euler('ZXZ', angles, degrees=True).as_matrix()
563
+
564
+ assert_array_almost_equal(mat[0], np.array([
565
+ [0.43559574, -0.78914913, 0.4330127],
566
+ [0.65973961, -0.04736717, -0.750000],
567
+ [0.61237244, 0.61237244, 0.500000]
568
+ ]))
569
+
570
+ assert_array_almost_equal(mat[1], np.array([
571
+ [0.6250000, -0.64951905, 0.4330127],
572
+ [0.64951905, 0.1250000, -0.750000],
573
+ [0.4330127, 0.750000, 0.500000]
574
+ ]))
575
+
576
+ assert_array_almost_equal(mat[2], np.array([
577
+ [-0.1767767, -0.91855865, 0.35355339],
578
+ [0.88388348, -0.30618622, -0.35355339],
579
+ [0.4330127, 0.25000000, 0.8660254]
580
+ ]))
581
+
582
+
583
+ def test_from_euler_extrinsic_rotation_312():
584
+ angles = [
585
+ [30, 60, 45],
586
+ [30, 60, 30],
587
+ [45, 30, 60]
588
+ ]
589
+ mat = Rotation.from_euler('zxy', angles, degrees=True).as_matrix()
590
+
591
+ assert_array_almost_equal(mat[0], np.array([
592
+ [0.91855865, 0.1767767, 0.35355339],
593
+ [0.25000000, 0.4330127, -0.8660254],
594
+ [-0.30618622, 0.88388348, 0.35355339]
595
+ ]))
596
+
597
+ assert_array_almost_equal(mat[1], np.array([
598
+ [0.96650635, -0.0580127, 0.2500000],
599
+ [0.25000000, 0.4330127, -0.8660254],
600
+ [-0.0580127, 0.89951905, 0.4330127]
601
+ ]))
602
+
603
+ assert_array_almost_equal(mat[2], np.array([
604
+ [0.65973961, -0.04736717, 0.7500000],
605
+ [0.61237244, 0.61237244, -0.5000000],
606
+ [-0.43559574, 0.78914913, 0.4330127]
607
+ ]))
608
+
609
+
610
+ def test_from_euler_extrinsic_rotation_313():
611
+ angles = [
612
+ [30, 60, 45],
613
+ [30, 60, 30],
614
+ [45, 30, 60]
615
+ ]
616
+ mat = Rotation.from_euler('zxz', angles, degrees=True).as_matrix()
617
+
618
+ assert_array_almost_equal(mat[0], np.array([
619
+ [0.43559574, -0.65973961, 0.61237244],
620
+ [0.78914913, -0.04736717, -0.61237244],
621
+ [0.4330127, 0.75000000, 0.500000]
622
+ ]))
623
+
624
+ assert_array_almost_equal(mat[1], np.array([
625
+ [0.62500000, -0.64951905, 0.4330127],
626
+ [0.64951905, 0.12500000, -0.750000],
627
+ [0.4330127, 0.75000000, 0.500000]
628
+ ]))
629
+
630
+ assert_array_almost_equal(mat[2], np.array([
631
+ [-0.1767767, -0.88388348, 0.4330127],
632
+ [0.91855865, -0.30618622, -0.250000],
633
+ [0.35355339, 0.35355339, 0.8660254]
634
+ ]))
635
+
636
+
637
+ @pytest.mark.parametrize("seq_tuple", permutations("xyz"))
638
+ @pytest.mark.parametrize("intrinsic", (False, True))
639
+ def test_as_euler_asymmetric_axes(seq_tuple, intrinsic):
640
+ # helper function for mean error tests
641
+ def test_stats(error, mean_max, rms_max):
642
+ mean = np.mean(error, axis=0)
643
+ std = np.std(error, axis=0)
644
+ rms = np.hypot(mean, std)
645
+ assert np.all(np.abs(mean) < mean_max)
646
+ assert np.all(rms < rms_max)
647
+
648
+ rnd = np.random.RandomState(0)
649
+ n = 1000
650
+ angles = np.empty((n, 3))
651
+ angles[:, 0] = rnd.uniform(low=-np.pi, high=np.pi, size=(n,))
652
+ angles[:, 1] = rnd.uniform(low=-np.pi / 2, high=np.pi / 2, size=(n,))
653
+ angles[:, 2] = rnd.uniform(low=-np.pi, high=np.pi, size=(n,))
654
+
655
+ seq = "".join(seq_tuple)
656
+ if intrinsic:
657
+ # Extrinsic rotation (wrt to global world) at lower case
658
+ # intrinsinc (WRT the object itself) lower case.
659
+ seq = seq.upper()
660
+ rotation = Rotation.from_euler(seq, angles)
661
+ angles_quat = rotation.as_euler(seq)
662
+ angles_mat = rotation._as_euler_from_matrix(seq)
663
+ assert_allclose(angles, angles_quat, atol=0, rtol=1e-12)
664
+ assert_allclose(angles, angles_mat, atol=0, rtol=1e-12)
665
+ test_stats(angles_quat - angles, 1e-15, 1e-14)
666
+ test_stats(angles_mat - angles, 1e-15, 1e-14)
667
+
668
+
669
+
670
+ @pytest.mark.parametrize("seq_tuple", permutations("xyz"))
671
+ @pytest.mark.parametrize("intrinsic", (False, True))
672
+ def test_as_euler_symmetric_axes(seq_tuple, intrinsic):
673
+ # helper function for mean error tests
674
+ def test_stats(error, mean_max, rms_max):
675
+ mean = np.mean(error, axis=0)
676
+ std = np.std(error, axis=0)
677
+ rms = np.hypot(mean, std)
678
+ assert np.all(np.abs(mean) < mean_max)
679
+ assert np.all(rms < rms_max)
680
+
681
+ rnd = np.random.RandomState(0)
682
+ n = 1000
683
+ angles = np.empty((n, 3))
684
+ angles[:, 0] = rnd.uniform(low=-np.pi, high=np.pi, size=(n,))
685
+ angles[:, 1] = rnd.uniform(low=0, high=np.pi, size=(n,))
686
+ angles[:, 2] = rnd.uniform(low=-np.pi, high=np.pi, size=(n,))
687
+
688
+ # Rotation of the form A/B/A are rotation around symmetric axes
689
+ seq = "".join([seq_tuple[0], seq_tuple[1], seq_tuple[0]])
690
+ if intrinsic:
691
+ seq = seq.upper()
692
+ rotation = Rotation.from_euler(seq, angles)
693
+ angles_quat = rotation.as_euler(seq)
694
+ angles_mat = rotation._as_euler_from_matrix(seq)
695
+ assert_allclose(angles, angles_quat, atol=0, rtol=1e-13)
696
+ assert_allclose(angles, angles_mat, atol=0, rtol=1e-9)
697
+ test_stats(angles_quat - angles, 1e-16, 1e-14)
698
+ test_stats(angles_mat - angles, 1e-15, 1e-13)
699
+
700
+
701
+ @pytest.mark.parametrize("seq_tuple", permutations("xyz"))
702
+ @pytest.mark.parametrize("intrinsic", (False, True))
703
+ def test_as_euler_degenerate_asymmetric_axes(seq_tuple, intrinsic):
704
+ # Since we cannot check for angle equality, we check for rotation matrix
705
+ # equality
706
+ angles = np.array([
707
+ [45, 90, 35],
708
+ [35, -90, 20],
709
+ [35, 90, 25],
710
+ [25, -90, 15]])
711
+
712
+ seq = "".join(seq_tuple)
713
+ if intrinsic:
714
+ # Extrinsic rotation (wrt to global world) at lower case
715
+ # Intrinsic (WRT the object itself) upper case.
716
+ seq = seq.upper()
717
+ rotation = Rotation.from_euler(seq, angles, degrees=True)
718
+ mat_expected = rotation.as_matrix()
719
+
720
+ with pytest.warns(UserWarning, match="Gimbal lock"):
721
+ angle_estimates = rotation.as_euler(seq, degrees=True)
722
+ mat_estimated = Rotation.from_euler(seq, angle_estimates, degrees=True).as_matrix()
723
+
724
+ assert_array_almost_equal(mat_expected, mat_estimated)
725
+
726
+
727
+ @pytest.mark.parametrize("seq_tuple", permutations("xyz"))
728
+ @pytest.mark.parametrize("intrinsic", (False, True))
729
+ def test_as_euler_degenerate_symmetric_axes(seq_tuple, intrinsic):
730
+ # Since we cannot check for angle equality, we check for rotation matrix
731
+ # equality
732
+ angles = np.array([
733
+ [15, 0, 60],
734
+ [35, 0, 75],
735
+ [60, 180, 35],
736
+ [15, -180, 25]])
737
+
738
+ # Rotation of the form A/B/A are rotation around symmetric axes
739
+ seq = "".join([seq_tuple[0], seq_tuple[1], seq_tuple[0]])
740
+ if intrinsic:
741
+ # Extrinsic rotation (wrt to global world) at lower case
742
+ # Intrinsic (WRT the object itself) upper case.
743
+ seq = seq.upper()
744
+ rotation = Rotation.from_euler(seq, angles, degrees=True)
745
+ mat_expected = rotation.as_matrix()
746
+
747
+ with pytest.warns(UserWarning, match="Gimbal lock"):
748
+ angle_estimates = rotation.as_euler(seq, degrees=True)
749
+ mat_estimated = Rotation.from_euler(seq, angle_estimates, degrees=True).as_matrix()
750
+
751
+ assert_array_almost_equal(mat_expected, mat_estimated)
752
+
753
+
754
+ @pytest.mark.parametrize("seq_tuple", permutations("xyz"))
755
+ @pytest.mark.parametrize("intrinsic", (False, True))
756
+ def test_as_euler_degenerate_compare_algorithms(seq_tuple, intrinsic):
757
+ # this test makes sure that both algorithms are doing the same choices
758
+ # in degenerate cases
759
+
760
+ # asymmetric axes
761
+ angles = np.array([
762
+ [45, 90, 35],
763
+ [35, -90, 20],
764
+ [35, 90, 25],
765
+ [25, -90, 15]])
766
+
767
+ seq = "".join(seq_tuple)
768
+ if intrinsic:
769
+ # Extrinsic rotation (wrt to global world at lower case
770
+ # Intrinsic (WRT the object itself) upper case.
771
+ seq = seq.upper()
772
+
773
+ rot = Rotation.from_euler(seq, angles, degrees=True)
774
+ with pytest.warns(UserWarning, match="Gimbal lock"):
775
+ estimates_matrix = rot._as_euler_from_matrix(seq, degrees=True)
776
+ with pytest.warns(UserWarning, match="Gimbal lock"):
777
+ estimates_quat = rot.as_euler(seq, degrees=True)
778
+ assert_allclose(
779
+ estimates_matrix[:, [0, 2]], estimates_quat[:, [0, 2]], atol=0, rtol=1e-12
780
+ )
781
+ assert_allclose(estimates_matrix[:, 1], estimates_quat[:, 1], atol=0, rtol=1e-7)
782
+
783
+ # symmetric axes
784
+ # Absolute error tolerance must be looser to directly compare the results
785
+ # from both algorithms, because of numerical loss of precision for the
786
+ # method _as_euler_from_matrix near a zero angle value
787
+
788
+ angles = np.array([
789
+ [15, 0, 60],
790
+ [35, 0, 75],
791
+ [60, 180, 35],
792
+ [15, -180, 25]])
793
+
794
+ idx = angles[:, 1] == 0 # find problematic angles indices
795
+
796
+ # Rotation of the form A/B/A are rotation around symmetric axes
797
+ seq = "".join([seq_tuple[0], seq_tuple[1], seq_tuple[0]])
798
+ if intrinsic:
799
+ # Extrinsinc rotation (wrt to global world) at lower case
800
+ # Intrinsic (WRT the object itself) upper case.
801
+ seq = seq.upper()
802
+
803
+ rot = Rotation.from_euler(seq, angles, degrees=True)
804
+ with pytest.warns(UserWarning, match="Gimbal lock"):
805
+ estimates_matrix = rot._as_euler_from_matrix(seq, degrees=True)
806
+ with pytest.warns(UserWarning, match="Gimbal lock"):
807
+ estimates_quat = rot.as_euler(seq, degrees=True)
808
+ assert_allclose(
809
+ estimates_matrix[:, [0, 2]], estimates_quat[:, [0, 2]], atol=0, rtol=1e-12
810
+ )
811
+
812
+ assert_allclose(
813
+ estimates_matrix[~idx, 1], estimates_quat[~idx, 1], atol=0, rtol=1e-7
814
+ )
815
+
816
+ assert_allclose(
817
+ estimates_matrix[idx, 1], estimates_quat[idx, 1], atol=1e-6
818
+ ) # problematic, angles[1] = 0
819
+
820
+
821
+ def test_inv():
822
+ rnd = np.random.RandomState(0)
823
+ n = 10
824
+ p = Rotation.random(num=n, random_state=rnd)
825
+ q = p.inv()
826
+
827
+ p_mat = p.as_matrix()
828
+ q_mat = q.as_matrix()
829
+ result1 = np.einsum('...ij,...jk->...ik', p_mat, q_mat)
830
+ result2 = np.einsum('...ij,...jk->...ik', q_mat, p_mat)
831
+
832
+ eye3d = np.empty((n, 3, 3))
833
+ eye3d[:] = np.eye(3)
834
+
835
+ assert_array_almost_equal(result1, eye3d)
836
+ assert_array_almost_equal(result2, eye3d)
837
+
838
+
839
+ def test_inv_single_rotation():
840
+ rnd = np.random.RandomState(0)
841
+ p = Rotation.random(random_state=rnd)
842
+ q = p.inv()
843
+
844
+ p_mat = p.as_matrix()
845
+ q_mat = q.as_matrix()
846
+ res1 = np.dot(p_mat, q_mat)
847
+ res2 = np.dot(q_mat, p_mat)
848
+
849
+ eye = np.eye(3)
850
+
851
+ assert_array_almost_equal(res1, eye)
852
+ assert_array_almost_equal(res2, eye)
853
+
854
+ x = Rotation.random(num=1, random_state=rnd)
855
+ y = x.inv()
856
+
857
+ x_matrix = x.as_matrix()
858
+ y_matrix = y.as_matrix()
859
+ result1 = np.einsum('...ij,...jk->...ik', x_matrix, y_matrix)
860
+ result2 = np.einsum('...ij,...jk->...ik', y_matrix, x_matrix)
861
+
862
+ eye3d = np.empty((1, 3, 3))
863
+ eye3d[:] = np.eye(3)
864
+
865
+ assert_array_almost_equal(result1, eye3d)
866
+ assert_array_almost_equal(result2, eye3d)
867
+
868
+
869
+ def test_identity_magnitude():
870
+ n = 10
871
+ assert_allclose(Rotation.identity(n).magnitude(), 0)
872
+ assert_allclose(Rotation.identity(n).inv().magnitude(), 0)
873
+
874
+
875
+ def test_single_identity_magnitude():
876
+ assert Rotation.identity().magnitude() == 0
877
+ assert Rotation.identity().inv().magnitude() == 0
878
+
879
+
880
+ def test_identity_invariance():
881
+ n = 10
882
+ p = Rotation.random(n, random_state=0)
883
+
884
+ result = p * Rotation.identity(n)
885
+ assert_array_almost_equal(p.as_quat(), result.as_quat())
886
+
887
+ result = result * p.inv()
888
+ assert_array_almost_equal(result.magnitude(), np.zeros(n))
889
+
890
+
891
+ def test_single_identity_invariance():
892
+ n = 10
893
+ p = Rotation.random(n, random_state=0)
894
+
895
+ result = p * Rotation.identity()
896
+ assert_array_almost_equal(p.as_quat(), result.as_quat())
897
+
898
+ result = result * p.inv()
899
+ assert_array_almost_equal(result.magnitude(), np.zeros(n))
900
+
901
+
902
+ def test_magnitude():
903
+ r = Rotation.from_quat(np.eye(4))
904
+ result = r.magnitude()
905
+ assert_array_almost_equal(result, [np.pi, np.pi, np.pi, 0])
906
+
907
+ r = Rotation.from_quat(-np.eye(4))
908
+ result = r.magnitude()
909
+ assert_array_almost_equal(result, [np.pi, np.pi, np.pi, 0])
910
+
911
+
912
+ def test_magnitude_single_rotation():
913
+ r = Rotation.from_quat(np.eye(4))
914
+ result1 = r[0].magnitude()
915
+ assert_allclose(result1, np.pi)
916
+
917
+ result2 = r[3].magnitude()
918
+ assert_allclose(result2, 0)
919
+
920
+
921
+ def test_approx_equal():
922
+ rng = np.random.RandomState(0)
923
+ p = Rotation.random(10, random_state=rng)
924
+ q = Rotation.random(10, random_state=rng)
925
+ r = p * q.inv()
926
+ r_mag = r.magnitude()
927
+ atol = np.median(r_mag) # ensure we get mix of Trues and Falses
928
+ assert_equal(p.approx_equal(q, atol), (r_mag < atol))
929
+
930
+
931
+ def test_approx_equal_single_rotation():
932
+ # also tests passing single argument to approx_equal
933
+ p = Rotation.from_rotvec([0, 0, 1e-9]) # less than default atol of 1e-8
934
+ q = Rotation.from_quat(np.eye(4))
935
+ assert p.approx_equal(q[3])
936
+ assert not p.approx_equal(q[0])
937
+
938
+ # test passing atol and using degrees
939
+ assert not p.approx_equal(q[3], atol=1e-10)
940
+ assert not p.approx_equal(q[3], atol=1e-8, degrees=True)
941
+ with pytest.warns(UserWarning, match="atol must be set"):
942
+ assert p.approx_equal(q[3], degrees=True)
943
+
944
+
945
+ def test_mean():
946
+ axes = np.concatenate((-np.eye(3), np.eye(3)))
947
+ thetas = np.linspace(0, np.pi / 2, 100)
948
+ for t in thetas:
949
+ r = Rotation.from_rotvec(t * axes)
950
+ assert_allclose(r.mean().magnitude(), 0, atol=1E-10)
951
+
952
+
953
+ def test_weighted_mean():
954
+ # test that doubling a weight is equivalent to including a rotation twice.
955
+ axes = np.array([[0, 0, 0], [1, 0, 0], [1, 0, 0]])
956
+ thetas = np.linspace(0, np.pi / 2, 100)
957
+ for t in thetas:
958
+ rw = Rotation.from_rotvec(t * axes[:2])
959
+ mw = rw.mean(weights=[1, 2])
960
+
961
+ r = Rotation.from_rotvec(t * axes)
962
+ m = r.mean()
963
+ assert_allclose((m * mw.inv()).magnitude(), 0, atol=1E-10)
964
+
965
+
966
+ def test_mean_invalid_weights():
967
+ with pytest.raises(ValueError, match="non-negative"):
968
+ r = Rotation.from_quat(np.eye(4))
969
+ r.mean(weights=-np.ones(4))
970
+
971
+
972
+ def test_reduction_no_indices():
973
+ result = Rotation.identity().reduce(return_indices=False)
974
+ assert isinstance(result, Rotation)
975
+
976
+
977
+ def test_reduction_none_indices():
978
+ result = Rotation.identity().reduce(return_indices=True)
979
+ assert type(result) == tuple
980
+ assert len(result) == 3
981
+
982
+ reduced, left_best, right_best = result
983
+ assert left_best is None
984
+ assert right_best is None
985
+
986
+
987
+ def test_reduction_scalar_calculation():
988
+ rng = np.random.RandomState(0)
989
+ l = Rotation.random(5, random_state=rng)
990
+ r = Rotation.random(10, random_state=rng)
991
+ p = Rotation.random(7, random_state=rng)
992
+ reduced, left_best, right_best = p.reduce(l, r, return_indices=True)
993
+
994
+ # Loop implementation of the vectorized calculation in Rotation.reduce
995
+ scalars = np.zeros((len(l), len(p), len(r)))
996
+ for i, li in enumerate(l):
997
+ for j, pj in enumerate(p):
998
+ for k, rk in enumerate(r):
999
+ scalars[i, j, k] = np.abs((li * pj * rk).as_quat()[3])
1000
+ scalars = np.reshape(np.moveaxis(scalars, 1, 0), (scalars.shape[1], -1))
1001
+
1002
+ max_ind = np.argmax(np.reshape(scalars, (len(p), -1)), axis=1)
1003
+ left_best_check = max_ind // len(r)
1004
+ right_best_check = max_ind % len(r)
1005
+ assert (left_best == left_best_check).all()
1006
+ assert (right_best == right_best_check).all()
1007
+
1008
+ reduced_check = l[left_best_check] * p * r[right_best_check]
1009
+ mag = (reduced.inv() * reduced_check).magnitude()
1010
+ assert_array_almost_equal(mag, np.zeros(len(p)))
1011
+
1012
+
1013
+ def test_apply_single_rotation_single_point():
1014
+ mat = np.array([
1015
+ [0, -1, 0],
1016
+ [1, 0, 0],
1017
+ [0, 0, 1]
1018
+ ])
1019
+ r_1d = Rotation.from_matrix(mat)
1020
+ r_2d = Rotation.from_matrix(np.expand_dims(mat, axis=0))
1021
+
1022
+ v_1d = np.array([1, 2, 3])
1023
+ v_2d = np.expand_dims(v_1d, axis=0)
1024
+ v1d_rotated = np.array([-2, 1, 3])
1025
+ v2d_rotated = np.expand_dims(v1d_rotated, axis=0)
1026
+
1027
+ assert_allclose(r_1d.apply(v_1d), v1d_rotated)
1028
+ assert_allclose(r_1d.apply(v_2d), v2d_rotated)
1029
+ assert_allclose(r_2d.apply(v_1d), v2d_rotated)
1030
+ assert_allclose(r_2d.apply(v_2d), v2d_rotated)
1031
+
1032
+ v1d_inverse = np.array([2, -1, 3])
1033
+ v2d_inverse = np.expand_dims(v1d_inverse, axis=0)
1034
+
1035
+ assert_allclose(r_1d.apply(v_1d, inverse=True), v1d_inverse)
1036
+ assert_allclose(r_1d.apply(v_2d, inverse=True), v2d_inverse)
1037
+ assert_allclose(r_2d.apply(v_1d, inverse=True), v2d_inverse)
1038
+ assert_allclose(r_2d.apply(v_2d, inverse=True), v2d_inverse)
1039
+
1040
+
1041
+ def test_apply_single_rotation_multiple_points():
1042
+ mat = np.array([
1043
+ [0, -1, 0],
1044
+ [1, 0, 0],
1045
+ [0, 0, 1]
1046
+ ])
1047
+ r1 = Rotation.from_matrix(mat)
1048
+ r2 = Rotation.from_matrix(np.expand_dims(mat, axis=0))
1049
+
1050
+ v = np.array([[1, 2, 3], [4, 5, 6]])
1051
+ v_rotated = np.array([[-2, 1, 3], [-5, 4, 6]])
1052
+
1053
+ assert_allclose(r1.apply(v), v_rotated)
1054
+ assert_allclose(r2.apply(v), v_rotated)
1055
+
1056
+ v_inverse = np.array([[2, -1, 3], [5, -4, 6]])
1057
+
1058
+ assert_allclose(r1.apply(v, inverse=True), v_inverse)
1059
+ assert_allclose(r2.apply(v, inverse=True), v_inverse)
1060
+
1061
+
1062
+ def test_apply_multiple_rotations_single_point():
1063
+ mat = np.empty((2, 3, 3))
1064
+ mat[0] = np.array([
1065
+ [0, -1, 0],
1066
+ [1, 0, 0],
1067
+ [0, 0, 1]
1068
+ ])
1069
+ mat[1] = np.array([
1070
+ [1, 0, 0],
1071
+ [0, 0, -1],
1072
+ [0, 1, 0]
1073
+ ])
1074
+ r = Rotation.from_matrix(mat)
1075
+
1076
+ v1 = np.array([1, 2, 3])
1077
+ v2 = np.expand_dims(v1, axis=0)
1078
+
1079
+ v_rotated = np.array([[-2, 1, 3], [1, -3, 2]])
1080
+
1081
+ assert_allclose(r.apply(v1), v_rotated)
1082
+ assert_allclose(r.apply(v2), v_rotated)
1083
+
1084
+ v_inverse = np.array([[2, -1, 3], [1, 3, -2]])
1085
+
1086
+ assert_allclose(r.apply(v1, inverse=True), v_inverse)
1087
+ assert_allclose(r.apply(v2, inverse=True), v_inverse)
1088
+
1089
+
1090
+ def test_apply_multiple_rotations_multiple_points():
1091
+ mat = np.empty((2, 3, 3))
1092
+ mat[0] = np.array([
1093
+ [0, -1, 0],
1094
+ [1, 0, 0],
1095
+ [0, 0, 1]
1096
+ ])
1097
+ mat[1] = np.array([
1098
+ [1, 0, 0],
1099
+ [0, 0, -1],
1100
+ [0, 1, 0]
1101
+ ])
1102
+ r = Rotation.from_matrix(mat)
1103
+
1104
+ v = np.array([[1, 2, 3], [4, 5, 6]])
1105
+ v_rotated = np.array([[-2, 1, 3], [4, -6, 5]])
1106
+ assert_allclose(r.apply(v), v_rotated)
1107
+
1108
+ v_inverse = np.array([[2, -1, 3], [4, 6, -5]])
1109
+ assert_allclose(r.apply(v, inverse=True), v_inverse)
1110
+
1111
+
1112
+ def test_getitem():
1113
+ mat = np.empty((2, 3, 3))
1114
+ mat[0] = np.array([
1115
+ [0, -1, 0],
1116
+ [1, 0, 0],
1117
+ [0, 0, 1]
1118
+ ])
1119
+ mat[1] = np.array([
1120
+ [1, 0, 0],
1121
+ [0, 0, -1],
1122
+ [0, 1, 0]
1123
+ ])
1124
+ r = Rotation.from_matrix(mat)
1125
+
1126
+ assert_allclose(r[0].as_matrix(), mat[0], atol=1e-15)
1127
+ assert_allclose(r[1].as_matrix(), mat[1], atol=1e-15)
1128
+ assert_allclose(r[:-1].as_matrix(), np.expand_dims(mat[0], axis=0), atol=1e-15)
1129
+
1130
+
1131
+ def test_getitem_single():
1132
+ with pytest.raises(TypeError, match='not subscriptable'):
1133
+ Rotation.identity()[0]
1134
+
1135
+
1136
+ def test_setitem_single():
1137
+ r = Rotation.identity()
1138
+ with pytest.raises(TypeError, match='not subscriptable'):
1139
+ r[0] = Rotation.identity()
1140
+
1141
+
1142
+ def test_setitem_slice():
1143
+ rng = np.random.RandomState(seed=0)
1144
+ r1 = Rotation.random(10, random_state=rng)
1145
+ r2 = Rotation.random(5, random_state=rng)
1146
+ r1[1:6] = r2
1147
+ assert_equal(r1[1:6].as_quat(), r2.as_quat())
1148
+
1149
+
1150
+ def test_setitem_integer():
1151
+ rng = np.random.RandomState(seed=0)
1152
+ r1 = Rotation.random(10, random_state=rng)
1153
+ r2 = Rotation.random(random_state=rng)
1154
+ r1[1] = r2
1155
+ assert_equal(r1[1].as_quat(), r2.as_quat())
1156
+
1157
+
1158
+ def test_setitem_wrong_type():
1159
+ r = Rotation.random(10, random_state=0)
1160
+ with pytest.raises(TypeError, match='Rotation object'):
1161
+ r[0] = 1
1162
+
1163
+
1164
+ def test_n_rotations():
1165
+ mat = np.empty((2, 3, 3))
1166
+ mat[0] = np.array([
1167
+ [0, -1, 0],
1168
+ [1, 0, 0],
1169
+ [0, 0, 1]
1170
+ ])
1171
+ mat[1] = np.array([
1172
+ [1, 0, 0],
1173
+ [0, 0, -1],
1174
+ [0, 1, 0]
1175
+ ])
1176
+ r = Rotation.from_matrix(mat)
1177
+
1178
+ assert_equal(len(r), 2)
1179
+ assert_equal(len(r[:-1]), 1)
1180
+
1181
+
1182
+ def test_random_rotation_shape():
1183
+ rnd = np.random.RandomState(0)
1184
+ assert_equal(Rotation.random(random_state=rnd).as_quat().shape, (4,))
1185
+ assert_equal(Rotation.random(None, random_state=rnd).as_quat().shape, (4,))
1186
+
1187
+ assert_equal(Rotation.random(1, random_state=rnd).as_quat().shape, (1, 4))
1188
+ assert_equal(Rotation.random(5, random_state=rnd).as_quat().shape, (5, 4))
1189
+
1190
+
1191
+ def test_align_vectors_no_rotation():
1192
+ x = np.array([[1, 2, 3], [4, 5, 6]])
1193
+ y = x.copy()
1194
+
1195
+ r, rssd = Rotation.align_vectors(x, y)
1196
+ assert_array_almost_equal(r.as_matrix(), np.eye(3))
1197
+ assert_allclose(rssd, 0, atol=1e-6)
1198
+
1199
+
1200
+ def test_align_vectors_no_noise():
1201
+ rnd = np.random.RandomState(0)
1202
+ c = Rotation.random(random_state=rnd)
1203
+ b = rnd.normal(size=(5, 3))
1204
+ a = c.apply(b)
1205
+
1206
+ est, rssd = Rotation.align_vectors(a, b)
1207
+ assert_allclose(c.as_quat(), est.as_quat())
1208
+ assert_allclose(rssd, 0, atol=1e-7)
1209
+
1210
+
1211
+ def test_align_vectors_improper_rotation():
1212
+ # Tests correct logic for issue #10444
1213
+ x = np.array([[0.89299824, -0.44372674, 0.0752378],
1214
+ [0.60221789, -0.47564102, -0.6411702]])
1215
+ y = np.array([[0.02386536, -0.82176463, 0.5693271],
1216
+ [-0.27654929, -0.95191427, -0.1318321]])
1217
+
1218
+ est, rssd = Rotation.align_vectors(x, y)
1219
+ assert_allclose(x, est.apply(y), atol=1e-6)
1220
+ assert_allclose(rssd, 0, atol=1e-7)
1221
+
1222
+
1223
+ def test_align_vectors_rssd_sensitivity():
1224
+ rssd_expected = 0.141421356237308
1225
+ sens_expected = np.array([[0.2, 0. , 0.],
1226
+ [0. , 1.5, 1.],
1227
+ [0. , 1. , 1.]])
1228
+ atol = 1e-6
1229
+ a = [[0, 1, 0], [0, 1, 1], [0, 1, 1]]
1230
+ b = [[1, 0, 0], [1, 1.1, 0], [1, 0.9, 0]]
1231
+ rot, rssd, sens = Rotation.align_vectors(a, b, return_sensitivity=True)
1232
+ assert np.isclose(rssd, rssd_expected, atol=atol)
1233
+ assert np.allclose(sens, sens_expected, atol=atol)
1234
+
1235
+
1236
+ def test_align_vectors_scaled_weights():
1237
+ n = 10
1238
+ a = Rotation.random(n, random_state=0).apply([1, 0, 0])
1239
+ b = Rotation.random(n, random_state=1).apply([1, 0, 0])
1240
+ scale = 2
1241
+
1242
+ est1, rssd1, cov1 = Rotation.align_vectors(a, b, np.ones(n), True)
1243
+ est2, rssd2, cov2 = Rotation.align_vectors(a, b, scale * np.ones(n), True)
1244
+
1245
+ assert_allclose(est1.as_matrix(), est2.as_matrix())
1246
+ assert_allclose(np.sqrt(scale) * rssd1, rssd2, atol=1e-6)
1247
+ assert_allclose(cov1, cov2)
1248
+
1249
+
1250
+ def test_align_vectors_noise():
1251
+ rnd = np.random.RandomState(0)
1252
+ n_vectors = 100
1253
+ rot = Rotation.random(random_state=rnd)
1254
+ vectors = rnd.normal(size=(n_vectors, 3))
1255
+ result = rot.apply(vectors)
1256
+
1257
+ # The paper adds noise as independently distributed angular errors
1258
+ sigma = np.deg2rad(1)
1259
+ tolerance = 1.5 * sigma
1260
+ noise = Rotation.from_rotvec(
1261
+ rnd.normal(
1262
+ size=(n_vectors, 3),
1263
+ scale=sigma
1264
+ )
1265
+ )
1266
+
1267
+ # Attitude errors must preserve norm. Hence apply individual random
1268
+ # rotations to each vector.
1269
+ noisy_result = noise.apply(result)
1270
+
1271
+ est, rssd, cov = Rotation.align_vectors(noisy_result, vectors,
1272
+ return_sensitivity=True)
1273
+
1274
+ # Use rotation compositions to find out closeness
1275
+ error_vector = (rot * est.inv()).as_rotvec()
1276
+ assert_allclose(error_vector[0], 0, atol=tolerance)
1277
+ assert_allclose(error_vector[1], 0, atol=tolerance)
1278
+ assert_allclose(error_vector[2], 0, atol=tolerance)
1279
+
1280
+ # Check error bounds using covariance matrix
1281
+ cov *= sigma
1282
+ assert_allclose(cov[0, 0], 0, atol=tolerance)
1283
+ assert_allclose(cov[1, 1], 0, atol=tolerance)
1284
+ assert_allclose(cov[2, 2], 0, atol=tolerance)
1285
+
1286
+ assert_allclose(rssd, np.sum((noisy_result - est.apply(vectors))**2)**0.5)
1287
+
1288
+
1289
+ def test_align_vectors_invalid_input():
1290
+ with pytest.raises(ValueError, match="Expected input `a` to have shape"):
1291
+ Rotation.align_vectors([1, 2, 3, 4], [1, 2, 3])
1292
+
1293
+ with pytest.raises(ValueError, match="Expected input `b` to have shape"):
1294
+ Rotation.align_vectors([1, 2, 3], [1, 2, 3, 4])
1295
+
1296
+ with pytest.raises(ValueError, match="Expected inputs `a` and `b` "
1297
+ "to have same shapes"):
1298
+ Rotation.align_vectors([[1, 2, 3],[4, 5, 6]], [[1, 2, 3]])
1299
+
1300
+ with pytest.raises(ValueError,
1301
+ match="Expected `weights` to be 1 dimensional"):
1302
+ Rotation.align_vectors([[1, 2, 3]], [[1, 2, 3]], weights=[[1]])
1303
+
1304
+ with pytest.raises(ValueError,
1305
+ match="Expected `weights` to have number of values"):
1306
+ Rotation.align_vectors([[1, 2, 3], [4, 5, 6]], [[1, 2, 3], [4, 5, 6]],
1307
+ weights=[1, 2, 3])
1308
+
1309
+ with pytest.raises(ValueError,
1310
+ match="`weights` may not contain negative values"):
1311
+ Rotation.align_vectors([[1, 2, 3]], [[1, 2, 3]], weights=[-1])
1312
+
1313
+ with pytest.raises(ValueError,
1314
+ match="Only one infinite weight is allowed"):
1315
+ Rotation.align_vectors([[1, 2, 3], [4, 5, 6]], [[1, 2, 3], [4, 5, 6]],
1316
+ weights=[np.inf, np.inf])
1317
+
1318
+ with pytest.raises(ValueError,
1319
+ match="Cannot align zero length primary vectors"):
1320
+ Rotation.align_vectors([[0, 0, 0]], [[1, 2, 3]])
1321
+
1322
+ with pytest.raises(ValueError,
1323
+ match="Cannot return sensitivity matrix"):
1324
+ Rotation.align_vectors([[1, 2, 3], [4, 5, 6]], [[1, 2, 3], [4, 5, 6]],
1325
+ return_sensitivity=True, weights=[np.inf, 1])
1326
+
1327
+ with pytest.raises(ValueError,
1328
+ match="Cannot return sensitivity matrix"):
1329
+ Rotation.align_vectors([[1, 2, 3]], [[1, 2, 3]],
1330
+ return_sensitivity=True)
1331
+
1332
+
1333
+ def test_align_vectors_align_constrain():
1334
+ # Align the primary +X B axis with the primary +Y A axis, and rotate about
1335
+ # it such that the +Y B axis (residual of the [1, 1, 0] secondary b vector)
1336
+ # is aligned with the +Z A axis (residual of the [0, 1, 1] secondary a
1337
+ # vector)
1338
+ atol = 1e-12
1339
+ b = [[1, 0, 0], [1, 1, 0]]
1340
+ a = [[0, 1, 0], [0, 1, 1]]
1341
+ m_expected = np.array([[0, 0, 1],
1342
+ [1, 0, 0],
1343
+ [0, 1, 0]])
1344
+ R, rssd = Rotation.align_vectors(a, b, weights=[np.inf, 1])
1345
+ assert_allclose(R.as_matrix(), m_expected, atol=atol)
1346
+ assert_allclose(R.apply(b), a, atol=atol) # Pri and sec align exactly
1347
+ assert np.isclose(rssd, 0, atol=atol)
1348
+
1349
+ # Do the same but with an inexact secondary rotation
1350
+ b = [[1, 0, 0], [1, 2, 0]]
1351
+ rssd_expected = 1.0
1352
+ R, rssd = Rotation.align_vectors(a, b, weights=[np.inf, 1])
1353
+ assert_allclose(R.as_matrix(), m_expected, atol=atol)
1354
+ assert_allclose(R.apply(b)[0], a[0], atol=atol) # Only pri aligns exactly
1355
+ assert np.isclose(rssd, rssd_expected, atol=atol)
1356
+ a_expected = [[0, 1, 0], [0, 1, 2]]
1357
+ assert_allclose(R.apply(b), a_expected, atol=atol)
1358
+
1359
+ # Check random vectors
1360
+ b = [[1, 2, 3], [-2, 3, -1]]
1361
+ a = [[-1, 3, 2], [1, -1, 2]]
1362
+ rssd_expected = 1.3101595297515016
1363
+ R, rssd = Rotation.align_vectors(a, b, weights=[np.inf, 1])
1364
+ assert_allclose(R.apply(b)[0], a[0], atol=atol) # Only pri aligns exactly
1365
+ assert np.isclose(rssd, rssd_expected, atol=atol)
1366
+
1367
+
1368
+ def test_align_vectors_near_inf():
1369
+ # align_vectors should return near the same result for high weights as for
1370
+ # infinite weights. rssd will be different with floating point error on the
1371
+ # exactly aligned vector being multiplied by a large non-infinite weight
1372
+ n = 100
1373
+ mats = []
1374
+ for i in range(6):
1375
+ mats.append(Rotation.random(n, random_state=10 + i).as_matrix())
1376
+
1377
+ for i in range(n):
1378
+ # Get random pairs of 3-element vectors
1379
+ a = [1*mats[0][i][0], 2*mats[1][i][0]]
1380
+ b = [3*mats[2][i][0], 4*mats[3][i][0]]
1381
+
1382
+ R, _ = Rotation.align_vectors(a, b, weights=[1e10, 1])
1383
+ R2, _ = Rotation.align_vectors(a, b, weights=[np.inf, 1])
1384
+ assert_allclose(R.as_matrix(), R2.as_matrix(), atol=1e-4)
1385
+
1386
+ for i in range(n):
1387
+ # Get random triplets of 3-element vectors
1388
+ a = [1*mats[0][i][0], 2*mats[1][i][0], 3*mats[2][i][0]]
1389
+ b = [4*mats[3][i][0], 5*mats[4][i][0], 6*mats[5][i][0]]
1390
+
1391
+ R, _ = Rotation.align_vectors(a, b, weights=[1e10, 2, 1])
1392
+ R2, _ = Rotation.align_vectors(a, b, weights=[np.inf, 2, 1])
1393
+ assert_allclose(R.as_matrix(), R2.as_matrix(), atol=1e-4)
1394
+
1395
+
1396
+ def test_align_vectors_parallel():
1397
+ atol = 1e-12
1398
+ a = [[1, 0, 0], [0, 1, 0]]
1399
+ b = [[0, 1, 0], [0, 1, 0]]
1400
+ m_expected = np.array([[0, 1, 0],
1401
+ [-1, 0, 0],
1402
+ [0, 0, 1]])
1403
+ R, _ = Rotation.align_vectors(a, b, weights=[np.inf, 1])
1404
+ assert_allclose(R.as_matrix(), m_expected, atol=atol)
1405
+ R, _ = Rotation.align_vectors(a[0], b[0])
1406
+ assert_allclose(R.as_matrix(), m_expected, atol=atol)
1407
+ assert_allclose(R.apply(b[0]), a[0], atol=atol)
1408
+
1409
+ b = [[1, 0, 0], [1, 0, 0]]
1410
+ m_expected = np.array([[1, 0, 0],
1411
+ [0, 1, 0],
1412
+ [0, 0, 1]])
1413
+ R, _ = Rotation.align_vectors(a, b, weights=[np.inf, 1])
1414
+ assert_allclose(R.as_matrix(), m_expected, atol=atol)
1415
+ R, _ = Rotation.align_vectors(a[0], b[0])
1416
+ assert_allclose(R.as_matrix(), m_expected, atol=atol)
1417
+ assert_allclose(R.apply(b[0]), a[0], atol=atol)
1418
+
1419
+
1420
+ def test_align_vectors_antiparallel():
1421
+ # Test exact 180 deg rotation
1422
+ atol = 1e-12
1423
+ as_to_test = np.array([[[1, 0, 0], [0, 1, 0]],
1424
+ [[0, 1, 0], [1, 0, 0]],
1425
+ [[0, 0, 1], [0, 1, 0]]])
1426
+ bs_to_test = [[-a[0], a[1]] for a in as_to_test]
1427
+ for a, b in zip(as_to_test, bs_to_test):
1428
+ R, _ = Rotation.align_vectors(a, b, weights=[np.inf, 1])
1429
+ assert_allclose(R.magnitude(), np.pi, atol=atol)
1430
+ assert_allclose(R.apply(b[0]), a[0], atol=atol)
1431
+
1432
+ # Test exact rotations near 180 deg
1433
+ Rs = Rotation.random(100, random_state=0)
1434
+ dRs = Rotation.from_rotvec(Rs.as_rotvec()*1e-4) # scale down to small angle
1435
+ a = [[ 1, 0, 0], [0, 1, 0]]
1436
+ b = [[-1, 0, 0], [0, 1, 0]]
1437
+ as_to_test = []
1438
+ for dR in dRs:
1439
+ as_to_test.append([dR.apply(a[0]), a[1]])
1440
+ for a in as_to_test:
1441
+ R, _ = Rotation.align_vectors(a, b, weights=[np.inf, 1])
1442
+ R2, _ = Rotation.align_vectors(a, b, weights=[1e10, 1])
1443
+ assert_allclose(R.as_matrix(), R2.as_matrix(), atol=atol)
1444
+
1445
+
1446
+ def test_align_vectors_primary_only():
1447
+ atol = 1e-12
1448
+ mats_a = Rotation.random(100, random_state=0).as_matrix()
1449
+ mats_b = Rotation.random(100, random_state=1).as_matrix()
1450
+ for mat_a, mat_b in zip(mats_a, mats_b):
1451
+ # Get random 3-element unit vectors
1452
+ a = mat_a[0]
1453
+ b = mat_b[0]
1454
+
1455
+ # Compare to align_vectors with primary only
1456
+ R, rssd = Rotation.align_vectors(a, b)
1457
+ assert_allclose(R.apply(b), a, atol=atol)
1458
+ assert np.isclose(rssd, 0, atol=atol)
1459
+
1460
+
1461
+ def test_slerp():
1462
+ rnd = np.random.RandomState(0)
1463
+
1464
+ key_rots = Rotation.from_quat(rnd.uniform(size=(5, 4)))
1465
+ key_quats = key_rots.as_quat()
1466
+
1467
+ key_times = [0, 1, 2, 3, 4]
1468
+ interpolator = Slerp(key_times, key_rots)
1469
+
1470
+ times = [0, 0.5, 0.25, 1, 1.5, 2, 2.75, 3, 3.25, 3.60, 4]
1471
+ interp_rots = interpolator(times)
1472
+ interp_quats = interp_rots.as_quat()
1473
+
1474
+ # Dot products are affected by sign of quaternions
1475
+ interp_quats[interp_quats[:, -1] < 0] *= -1
1476
+ # Checking for quaternion equality, perform same operation
1477
+ key_quats[key_quats[:, -1] < 0] *= -1
1478
+
1479
+ # Equality at keyframes, including both endpoints
1480
+ assert_allclose(interp_quats[0], key_quats[0])
1481
+ assert_allclose(interp_quats[3], key_quats[1])
1482
+ assert_allclose(interp_quats[5], key_quats[2])
1483
+ assert_allclose(interp_quats[7], key_quats[3])
1484
+ assert_allclose(interp_quats[10], key_quats[4])
1485
+
1486
+ # Constant angular velocity between keyframes. Check by equating
1487
+ # cos(theta) between quaternion pairs with equal time difference.
1488
+ cos_theta1 = np.sum(interp_quats[0] * interp_quats[2])
1489
+ cos_theta2 = np.sum(interp_quats[2] * interp_quats[1])
1490
+ assert_allclose(cos_theta1, cos_theta2)
1491
+
1492
+ cos_theta4 = np.sum(interp_quats[3] * interp_quats[4])
1493
+ cos_theta5 = np.sum(interp_quats[4] * interp_quats[5])
1494
+ assert_allclose(cos_theta4, cos_theta5)
1495
+
1496
+ # theta1: 0 -> 0.25, theta3 : 0.5 -> 1
1497
+ # Use double angle formula for double the time difference
1498
+ cos_theta3 = np.sum(interp_quats[1] * interp_quats[3])
1499
+ assert_allclose(cos_theta3, 2 * (cos_theta1**2) - 1)
1500
+
1501
+ # Miscellaneous checks
1502
+ assert_equal(len(interp_rots), len(times))
1503
+
1504
+
1505
+ def test_slerp_rot_is_rotation():
1506
+ with pytest.raises(TypeError, match="must be a `Rotation` instance"):
1507
+ r = np.array([[1,2,3,4],
1508
+ [0,0,0,1]])
1509
+ t = np.array([0, 1])
1510
+ Slerp(t, r)
1511
+
1512
+
1513
+ def test_slerp_single_rot():
1514
+ msg = "must be a sequence of at least 2 rotations"
1515
+ with pytest.raises(ValueError, match=msg):
1516
+ r = Rotation.from_quat([1, 2, 3, 4])
1517
+ Slerp([1], r)
1518
+
1519
+
1520
+ def test_slerp_rot_len1():
1521
+ msg = "must be a sequence of at least 2 rotations"
1522
+ with pytest.raises(ValueError, match=msg):
1523
+ r = Rotation.from_quat([[1, 2, 3, 4]])
1524
+ Slerp([1], r)
1525
+
1526
+
1527
+ def test_slerp_time_dim_mismatch():
1528
+ with pytest.raises(ValueError,
1529
+ match="times to be specified in a 1 dimensional array"):
1530
+ rnd = np.random.RandomState(0)
1531
+ r = Rotation.from_quat(rnd.uniform(size=(2, 4)))
1532
+ t = np.array([[1],
1533
+ [2]])
1534
+ Slerp(t, r)
1535
+
1536
+
1537
+ def test_slerp_num_rotations_mismatch():
1538
+ with pytest.raises(ValueError, match="number of rotations to be equal to "
1539
+ "number of timestamps"):
1540
+ rnd = np.random.RandomState(0)
1541
+ r = Rotation.from_quat(rnd.uniform(size=(5, 4)))
1542
+ t = np.arange(7)
1543
+ Slerp(t, r)
1544
+
1545
+
1546
+ def test_slerp_equal_times():
1547
+ with pytest.raises(ValueError, match="strictly increasing order"):
1548
+ rnd = np.random.RandomState(0)
1549
+ r = Rotation.from_quat(rnd.uniform(size=(5, 4)))
1550
+ t = [0, 1, 2, 2, 4]
1551
+ Slerp(t, r)
1552
+
1553
+
1554
+ def test_slerp_decreasing_times():
1555
+ with pytest.raises(ValueError, match="strictly increasing order"):
1556
+ rnd = np.random.RandomState(0)
1557
+ r = Rotation.from_quat(rnd.uniform(size=(5, 4)))
1558
+ t = [0, 1, 3, 2, 4]
1559
+ Slerp(t, r)
1560
+
1561
+
1562
+ def test_slerp_call_time_dim_mismatch():
1563
+ rnd = np.random.RandomState(0)
1564
+ r = Rotation.from_quat(rnd.uniform(size=(5, 4)))
1565
+ t = np.arange(5)
1566
+ s = Slerp(t, r)
1567
+
1568
+ with pytest.raises(ValueError,
1569
+ match="`times` must be at most 1-dimensional."):
1570
+ interp_times = np.array([[3.5],
1571
+ [4.2]])
1572
+ s(interp_times)
1573
+
1574
+
1575
+ def test_slerp_call_time_out_of_range():
1576
+ rnd = np.random.RandomState(0)
1577
+ r = Rotation.from_quat(rnd.uniform(size=(5, 4)))
1578
+ t = np.arange(5) + 1
1579
+ s = Slerp(t, r)
1580
+
1581
+ with pytest.raises(ValueError, match="times must be within the range"):
1582
+ s([0, 1, 2])
1583
+ with pytest.raises(ValueError, match="times must be within the range"):
1584
+ s([1, 2, 6])
1585
+
1586
+
1587
+ def test_slerp_call_scalar_time():
1588
+ r = Rotation.from_euler('X', [0, 80], degrees=True)
1589
+ s = Slerp([0, 1], r)
1590
+
1591
+ r_interpolated = s(0.25)
1592
+ r_interpolated_expected = Rotation.from_euler('X', 20, degrees=True)
1593
+
1594
+ delta = r_interpolated * r_interpolated_expected.inv()
1595
+
1596
+ assert_allclose(delta.magnitude(), 0, atol=1e-16)
1597
+
1598
+
1599
+ def test_multiplication_stability():
1600
+ qs = Rotation.random(50, random_state=0)
1601
+ rs = Rotation.random(1000, random_state=1)
1602
+ for q in qs:
1603
+ rs *= q * rs
1604
+ assert_allclose(np.linalg.norm(rs.as_quat(), axis=1), 1)
1605
+
1606
+
1607
+ def test_pow():
1608
+ atol = 1e-14
1609
+ p = Rotation.random(10, random_state=0)
1610
+ p_inv = p.inv()
1611
+ # Test the short-cuts and other integers
1612
+ for n in [-5, -2, -1, 0, 1, 2, 5]:
1613
+ # Test accuracy
1614
+ q = p ** n
1615
+ r = Rotation.identity(10)
1616
+ for _ in range(abs(n)):
1617
+ if n > 0:
1618
+ r = r * p
1619
+ else:
1620
+ r = r * p_inv
1621
+ ang = (q * r.inv()).magnitude()
1622
+ assert np.all(ang < atol)
1623
+
1624
+ # Test shape preservation
1625
+ r = Rotation.from_quat([0, 0, 0, 1])
1626
+ assert (r**n).as_quat().shape == (4,)
1627
+ r = Rotation.from_quat([[0, 0, 0, 1]])
1628
+ assert (r**n).as_quat().shape == (1, 4)
1629
+
1630
+ # Large angle fractional
1631
+ for n in [-1.5, -0.5, -0.0, 0.0, 0.5, 1.5]:
1632
+ q = p ** n
1633
+ r = Rotation.from_rotvec(n * p.as_rotvec())
1634
+ assert_allclose(q.as_quat(), r.as_quat(), atol=atol)
1635
+
1636
+ # Small angle
1637
+ p = Rotation.from_rotvec([1e-12, 0, 0])
1638
+ n = 3
1639
+ q = p ** n
1640
+ r = Rotation.from_rotvec(n * p.as_rotvec())
1641
+ assert_allclose(q.as_quat(), r.as_quat(), atol=atol)
1642
+
1643
+
1644
+ def test_pow_errors():
1645
+ p = Rotation.random(random_state=0)
1646
+ with pytest.raises(NotImplementedError, match='modulus not supported'):
1647
+ pow(p, 1, 1)
1648
+
1649
+
1650
+ def test_rotation_within_numpy_array():
1651
+ single = Rotation.random(random_state=0)
1652
+ multiple = Rotation.random(2, random_state=1)
1653
+
1654
+ array = np.array(single)
1655
+ assert_equal(array.shape, ())
1656
+
1657
+ array = np.array(multiple)
1658
+ assert_equal(array.shape, (2,))
1659
+ assert_allclose(array[0].as_matrix(), multiple[0].as_matrix())
1660
+ assert_allclose(array[1].as_matrix(), multiple[1].as_matrix())
1661
+
1662
+ array = np.array([single])
1663
+ assert_equal(array.shape, (1,))
1664
+ assert_equal(array[0], single)
1665
+
1666
+ array = np.array([multiple])
1667
+ assert_equal(array.shape, (1, 2))
1668
+ assert_allclose(array[0, 0].as_matrix(), multiple[0].as_matrix())
1669
+ assert_allclose(array[0, 1].as_matrix(), multiple[1].as_matrix())
1670
+
1671
+ array = np.array([single, multiple], dtype=object)
1672
+ assert_equal(array.shape, (2,))
1673
+ assert_equal(array[0], single)
1674
+ assert_equal(array[1], multiple)
1675
+
1676
+ array = np.array([multiple, multiple, multiple])
1677
+ assert_equal(array.shape, (3, 2))
1678
+
1679
+
1680
+ def test_pickling():
1681
+ r = Rotation.from_quat([0, 0, np.sin(np.pi/4), np.cos(np.pi/4)])
1682
+ pkl = pickle.dumps(r)
1683
+ unpickled = pickle.loads(pkl)
1684
+ assert_allclose(r.as_matrix(), unpickled.as_matrix(), atol=1e-15)
1685
+
1686
+
1687
+ def test_deepcopy():
1688
+ r = Rotation.from_quat([0, 0, np.sin(np.pi/4), np.cos(np.pi/4)])
1689
+ r1 = copy.deepcopy(r)
1690
+ assert_allclose(r.as_matrix(), r1.as_matrix(), atol=1e-15)
1691
+
1692
+
1693
+ def test_as_euler_contiguous():
1694
+ r = Rotation.from_quat([0, 0, 0, 1])
1695
+ e1 = r.as_euler('xyz') # extrinsic euler rotation
1696
+ e2 = r.as_euler('XYZ') # intrinsic
1697
+ assert e1.flags['C_CONTIGUOUS'] is True
1698
+ assert e2.flags['C_CONTIGUOUS'] is True
1699
+ assert all(i >= 0 for i in e1.strides)
1700
+ assert all(i >= 0 for i in e2.strides)
1701
+
1702
+
1703
+ def test_concatenate():
1704
+ rotation = Rotation.random(10, random_state=0)
1705
+ sizes = [1, 2, 3, 1, 3]
1706
+ starts = [0] + list(np.cumsum(sizes))
1707
+ split = [rotation[i:i + n] for i, n in zip(starts, sizes)]
1708
+ result = Rotation.concatenate(split)
1709
+ assert_equal(rotation.as_quat(), result.as_quat())
1710
+
1711
+
1712
+ def test_concatenate_wrong_type():
1713
+ with pytest.raises(TypeError, match='Rotation objects only'):
1714
+ Rotation.concatenate([Rotation.identity(), 1, None])
1715
+
1716
+
1717
+ # Regression test for gh-16663
1718
+ def test_len_and_bool():
1719
+ rotation_multi_empty = Rotation(np.empty((0, 4)))
1720
+ rotation_multi_one = Rotation([[0, 0, 0, 1]])
1721
+ rotation_multi = Rotation([[0, 0, 0, 1], [0, 0, 0, 1]])
1722
+ rotation_single = Rotation([0, 0, 0, 1])
1723
+
1724
+ assert len(rotation_multi_empty) == 0
1725
+ assert len(rotation_multi_one) == 1
1726
+ assert len(rotation_multi) == 2
1727
+ with pytest.raises(TypeError, match="Single rotation has no len()."):
1728
+ len(rotation_single)
1729
+
1730
+ # Rotation should always be truthy. See gh-16663
1731
+ assert rotation_multi_empty
1732
+ assert rotation_multi_one
1733
+ assert rotation_multi
1734
+ assert rotation_single
1735
+
1736
+
1737
+ def test_from_davenport_single_rotation():
1738
+ axis = [0, 0, 1]
1739
+ quat = Rotation.from_davenport(axis, 'extrinsic', 90,
1740
+ degrees=True).as_quat()
1741
+ expected_quat = np.array([0, 0, 1, 1]) / np.sqrt(2)
1742
+ assert_allclose(quat, expected_quat)
1743
+
1744
+
1745
+ def test_from_davenport_one_or_two_axes():
1746
+ ez = [0, 0, 1]
1747
+ ey = [0, 1, 0]
1748
+
1749
+ # Single rotation, single axis, axes.shape == (3, )
1750
+ rot = Rotation.from_rotvec(np.array(ez) * np.pi/4)
1751
+ rot_dav = Rotation.from_davenport(ez, 'e', np.pi/4)
1752
+ assert_allclose(rot.as_quat(canonical=True),
1753
+ rot_dav.as_quat(canonical=True))
1754
+
1755
+ # Single rotation, single axis, axes.shape == (1, 3)
1756
+ rot = Rotation.from_rotvec([np.array(ez) * np.pi/4])
1757
+ rot_dav = Rotation.from_davenport([ez], 'e', [np.pi/4])
1758
+ assert_allclose(rot.as_quat(canonical=True),
1759
+ rot_dav.as_quat(canonical=True))
1760
+
1761
+ # Single rotation, two axes, axes.shape == (2, 3)
1762
+ rot = Rotation.from_rotvec([np.array(ez) * np.pi/4,
1763
+ np.array(ey) * np.pi/6])
1764
+ rot = rot[0] * rot[1]
1765
+ rot_dav = Rotation.from_davenport([ey, ez], 'e', [np.pi/6, np.pi/4])
1766
+ assert_allclose(rot.as_quat(canonical=True),
1767
+ rot_dav.as_quat(canonical=True))
1768
+
1769
+ # Two rotations, single axis, axes.shape == (3, )
1770
+ rot = Rotation.from_rotvec([np.array(ez) * np.pi/6,
1771
+ np.array(ez) * np.pi/4])
1772
+ rot_dav = Rotation.from_davenport([ez], 'e', [np.pi/6, np.pi/4])
1773
+ assert_allclose(rot.as_quat(canonical=True),
1774
+ rot_dav.as_quat(canonical=True))
1775
+
1776
+
1777
+ def test_from_davenport_invalid_input():
1778
+ ez = [0, 0, 1]
1779
+ ey = [0, 1, 0]
1780
+ ezy = [0, 1, 1]
1781
+ with pytest.raises(ValueError, match="must be orthogonal"):
1782
+ Rotation.from_davenport([ez, ezy], 'e', [0, 0])
1783
+ with pytest.raises(ValueError, match="must be orthogonal"):
1784
+ Rotation.from_davenport([ez, ey, ezy], 'e', [0, 0, 0])
1785
+ with pytest.raises(ValueError, match="order should be"):
1786
+ Rotation.from_davenport([ez], 'xyz', [0])
1787
+ with pytest.raises(ValueError, match="Expected `angles`"):
1788
+ Rotation.from_davenport([ez, ey, ez], 'e', [0, 1, 2, 3])
1789
+
1790
+
1791
+ def test_as_davenport():
1792
+ rnd = np.random.RandomState(0)
1793
+ n = 100
1794
+ angles = np.empty((n, 3))
1795
+ angles[:, 0] = rnd.uniform(low=-np.pi, high=np.pi, size=(n,))
1796
+ angles_middle = rnd.uniform(low=0, high=np.pi, size=(n,))
1797
+ angles[:, 2] = rnd.uniform(low=-np.pi, high=np.pi, size=(n,))
1798
+ lambdas = rnd.uniform(low=0, high=np.pi, size=(20,))
1799
+
1800
+ e1 = np.array([1, 0, 0])
1801
+ e2 = np.array([0, 1, 0])
1802
+
1803
+ for lamb in lambdas:
1804
+ ax_lamb = [e1, e2, Rotation.from_rotvec(lamb*e2).apply(e1)]
1805
+ angles[:, 1] = angles_middle - lamb
1806
+ for order in ['extrinsic', 'intrinsic']:
1807
+ ax = ax_lamb if order == 'intrinsic' else ax_lamb[::-1]
1808
+ rot = Rotation.from_davenport(ax, order, angles)
1809
+ angles_dav = rot.as_davenport(ax, order)
1810
+ assert_allclose(angles_dav, angles)
1811
+
1812
+
1813
+ def test_as_davenport_degenerate():
1814
+ # Since we cannot check for angle equality, we check for rotation matrix
1815
+ # equality
1816
+ rnd = np.random.RandomState(0)
1817
+ n = 5
1818
+ angles = np.empty((n, 3))
1819
+
1820
+ # symmetric sequences
1821
+ angles[:, 0] = rnd.uniform(low=-np.pi, high=np.pi, size=(n,))
1822
+ angles_middle = [rnd.choice([0, np.pi]) for i in range(n)]
1823
+ angles[:, 2] = rnd.uniform(low=-np.pi, high=np.pi, size=(n,))
1824
+ lambdas = rnd.uniform(low=0, high=np.pi, size=(5,))
1825
+
1826
+ e1 = np.array([1, 0, 0])
1827
+ e2 = np.array([0, 1, 0])
1828
+
1829
+ for lamb in lambdas:
1830
+ ax_lamb = [e1, e2, Rotation.from_rotvec(lamb*e2).apply(e1)]
1831
+ angles[:, 1] = angles_middle - lamb
1832
+ for order in ['extrinsic', 'intrinsic']:
1833
+ ax = ax_lamb if order == 'intrinsic' else ax_lamb[::-1]
1834
+ rot = Rotation.from_davenport(ax, order, angles)
1835
+ with pytest.warns(UserWarning, match="Gimbal lock"):
1836
+ angles_dav = rot.as_davenport(ax, order)
1837
+ mat_expected = rot.as_matrix()
1838
+ mat_estimated = Rotation.from_davenport(ax, order, angles_dav).as_matrix()
1839
+ assert_array_almost_equal(mat_expected, mat_estimated)
1840
+
1841
+
1842
+ def test_compare_from_davenport_from_euler():
1843
+ rnd = np.random.RandomState(0)
1844
+ n = 100
1845
+ angles = np.empty((n, 3))
1846
+
1847
+ # symmetric sequences
1848
+ angles[:, 0] = rnd.uniform(low=-np.pi, high=np.pi, size=(n,))
1849
+ angles[:, 1] = rnd.uniform(low=0, high=np.pi, size=(n,))
1850
+ angles[:, 2] = rnd.uniform(low=-np.pi, high=np.pi, size=(n,))
1851
+ for order in ['extrinsic', 'intrinsic']:
1852
+ for seq_tuple in permutations('xyz'):
1853
+ seq = ''.join([seq_tuple[0], seq_tuple[1], seq_tuple[0]])
1854
+ ax = [basis_vec(i) for i in seq]
1855
+ if order == 'intrinsic':
1856
+ seq = seq.upper()
1857
+ eul = Rotation.from_euler(seq, angles)
1858
+ dav = Rotation.from_davenport(ax, order, angles)
1859
+ assert_allclose(eul.as_quat(canonical=True), dav.as_quat(canonical=True),
1860
+ rtol=1e-12)
1861
+
1862
+ # asymmetric sequences
1863
+ angles[:, 1] -= np.pi / 2
1864
+ for order in ['extrinsic', 'intrinsic']:
1865
+ for seq_tuple in permutations('xyz'):
1866
+ seq = ''.join(seq_tuple)
1867
+ ax = [basis_vec(i) for i in seq]
1868
+ if order == 'intrinsic':
1869
+ seq = seq.upper()
1870
+ eul = Rotation.from_euler(seq, angles)
1871
+ dav = Rotation.from_davenport(ax, order, angles)
1872
+ assert_allclose(eul.as_quat(), dav.as_quat(), rtol=1e-12)
1873
+
1874
+
1875
+ def test_compare_as_davenport_as_euler():
1876
+ rnd = np.random.RandomState(0)
1877
+ n = 100
1878
+ angles = np.empty((n, 3))
1879
+
1880
+ # symmetric sequences
1881
+ angles[:, 0] = rnd.uniform(low=-np.pi, high=np.pi, size=(n,))
1882
+ angles[:, 1] = rnd.uniform(low=0, high=np.pi, size=(n,))
1883
+ angles[:, 2] = rnd.uniform(low=-np.pi, high=np.pi, size=(n,))
1884
+ for order in ['extrinsic', 'intrinsic']:
1885
+ for seq_tuple in permutations('xyz'):
1886
+ seq = ''.join([seq_tuple[0], seq_tuple[1], seq_tuple[0]])
1887
+ ax = [basis_vec(i) for i in seq]
1888
+ if order == 'intrinsic':
1889
+ seq = seq.upper()
1890
+ rot = Rotation.from_euler(seq, angles)
1891
+ eul = rot.as_euler(seq)
1892
+ dav = rot.as_davenport(ax, order)
1893
+ assert_allclose(eul, dav, rtol=1e-12)
1894
+
1895
+ # asymmetric sequences
1896
+ angles[:, 1] -= np.pi / 2
1897
+ for order in ['extrinsic', 'intrinsic']:
1898
+ for seq_tuple in permutations('xyz'):
1899
+ seq = ''.join(seq_tuple)
1900
+ ax = [basis_vec(i) for i in seq]
1901
+ if order == 'intrinsic':
1902
+ seq = seq.upper()
1903
+ rot = Rotation.from_euler(seq, angles)
1904
+ eul = rot.as_euler(seq)
1905
+ dav = rot.as_davenport(ax, order)
1906
+ assert_allclose(eul, dav, rtol=1e-12)
.venv/Lib/site-packages/scipy/spatial/transform/tests/test_rotation_groups.py ADDED
@@ -0,0 +1,169 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pytest
2
+
3
+ import numpy as np
4
+ from numpy.testing import assert_array_almost_equal
5
+ from scipy.spatial.transform import Rotation
6
+ from scipy.optimize import linear_sum_assignment
7
+ from scipy.spatial.distance import cdist
8
+ from scipy.constants import golden as phi
9
+ from scipy.spatial import cKDTree
10
+
11
+
12
+ TOL = 1E-12
13
+ NS = range(1, 13)
14
+ NAMES = ["I", "O", "T"] + ["C%d" % n for n in NS] + ["D%d" % n for n in NS]
15
+ SIZES = [60, 24, 12] + list(NS) + [2 * n for n in NS]
16
+
17
+
18
+ def _calculate_rmsd(P, Q):
19
+ """Calculates the root-mean-square distance between the points of P and Q.
20
+ The distance is taken as the minimum over all possible matchings. It is
21
+ zero if P and Q are identical and non-zero if not.
22
+ """
23
+ distance_matrix = cdist(P, Q, metric='sqeuclidean')
24
+ matching = linear_sum_assignment(distance_matrix)
25
+ return np.sqrt(distance_matrix[matching].sum())
26
+
27
+
28
+ def _generate_pyramid(n, axis):
29
+ thetas = np.linspace(0, 2 * np.pi, n + 1)[:-1]
30
+ P = np.vstack([np.zeros(n), np.cos(thetas), np.sin(thetas)]).T
31
+ P = np.concatenate((P, [[1, 0, 0]]))
32
+ return np.roll(P, axis, axis=1)
33
+
34
+
35
+ def _generate_prism(n, axis):
36
+ thetas = np.linspace(0, 2 * np.pi, n + 1)[:-1]
37
+ bottom = np.vstack([-np.ones(n), np.cos(thetas), np.sin(thetas)]).T
38
+ top = np.vstack([+np.ones(n), np.cos(thetas), np.sin(thetas)]).T
39
+ P = np.concatenate((bottom, top))
40
+ return np.roll(P, axis, axis=1)
41
+
42
+
43
+ def _generate_icosahedron():
44
+ x = np.array([[0, -1, -phi],
45
+ [0, -1, +phi],
46
+ [0, +1, -phi],
47
+ [0, +1, +phi]])
48
+ return np.concatenate([np.roll(x, i, axis=1) for i in range(3)])
49
+
50
+
51
+ def _generate_octahedron():
52
+ return np.array([[-1, 0, 0], [+1, 0, 0], [0, -1, 0],
53
+ [0, +1, 0], [0, 0, -1], [0, 0, +1]])
54
+
55
+
56
+ def _generate_tetrahedron():
57
+ return np.array([[1, 1, 1], [1, -1, -1], [-1, 1, -1], [-1, -1, 1]])
58
+
59
+
60
+ @pytest.mark.parametrize("name", [-1, None, True, np.array(['C3'])])
61
+ def test_group_type(name):
62
+ with pytest.raises(ValueError,
63
+ match="must be a string"):
64
+ Rotation.create_group(name)
65
+
66
+
67
+ @pytest.mark.parametrize("name", ["Q", " ", "CA", "C ", "DA", "D ", "I2", ""])
68
+ def test_group_name(name):
69
+ with pytest.raises(ValueError,
70
+ match="must be one of 'I', 'O', 'T', 'Dn', 'Cn'"):
71
+ Rotation.create_group(name)
72
+
73
+
74
+ @pytest.mark.parametrize("name", ["C0", "D0"])
75
+ def test_group_order_positive(name):
76
+ with pytest.raises(ValueError,
77
+ match="Group order must be positive"):
78
+ Rotation.create_group(name)
79
+
80
+
81
+ @pytest.mark.parametrize("axis", ['A', 'b', 0, 1, 2, 4, False, None])
82
+ def test_axis_valid(axis):
83
+ with pytest.raises(ValueError,
84
+ match="`axis` must be one of"):
85
+ Rotation.create_group("C1", axis)
86
+
87
+
88
+ def test_icosahedral():
89
+ """The icosahedral group fixes the rotations of an icosahedron. Here we
90
+ test that the icosahedron is invariant after application of the elements
91
+ of the rotation group."""
92
+ P = _generate_icosahedron()
93
+ for g in Rotation.create_group("I"):
94
+ g = Rotation.from_quat(g.as_quat())
95
+ assert _calculate_rmsd(P, g.apply(P)) < TOL
96
+
97
+
98
+ def test_octahedral():
99
+ """Test that the octahedral group correctly fixes the rotations of an
100
+ octahedron."""
101
+ P = _generate_octahedron()
102
+ for g in Rotation.create_group("O"):
103
+ assert _calculate_rmsd(P, g.apply(P)) < TOL
104
+
105
+
106
+ def test_tetrahedral():
107
+ """Test that the tetrahedral group correctly fixes the rotations of a
108
+ tetrahedron."""
109
+ P = _generate_tetrahedron()
110
+ for g in Rotation.create_group("T"):
111
+ assert _calculate_rmsd(P, g.apply(P)) < TOL
112
+
113
+
114
+ @pytest.mark.parametrize("n", NS)
115
+ @pytest.mark.parametrize("axis", 'XYZ')
116
+ def test_dicyclic(n, axis):
117
+ """Test that the dicyclic group correctly fixes the rotations of a
118
+ prism."""
119
+ P = _generate_prism(n, axis='XYZ'.index(axis))
120
+ for g in Rotation.create_group("D%d" % n, axis=axis):
121
+ assert _calculate_rmsd(P, g.apply(P)) < TOL
122
+
123
+
124
+ @pytest.mark.parametrize("n", NS)
125
+ @pytest.mark.parametrize("axis", 'XYZ')
126
+ def test_cyclic(n, axis):
127
+ """Test that the cyclic group correctly fixes the rotations of a
128
+ pyramid."""
129
+ P = _generate_pyramid(n, axis='XYZ'.index(axis))
130
+ for g in Rotation.create_group("C%d" % n, axis=axis):
131
+ assert _calculate_rmsd(P, g.apply(P)) < TOL
132
+
133
+
134
+ @pytest.mark.parametrize("name, size", zip(NAMES, SIZES))
135
+ def test_group_sizes(name, size):
136
+ assert len(Rotation.create_group(name)) == size
137
+
138
+
139
+ @pytest.mark.parametrize("name, size", zip(NAMES, SIZES))
140
+ def test_group_no_duplicates(name, size):
141
+ g = Rotation.create_group(name)
142
+ kdtree = cKDTree(g.as_quat())
143
+ assert len(kdtree.query_pairs(1E-3)) == 0
144
+
145
+
146
+ @pytest.mark.parametrize("name, size", zip(NAMES, SIZES))
147
+ def test_group_symmetry(name, size):
148
+ g = Rotation.create_group(name)
149
+ q = np.concatenate((-g.as_quat(), g.as_quat()))
150
+ distance = np.sort(cdist(q, q))
151
+ deltas = np.max(distance, axis=0) - np.min(distance, axis=0)
152
+ assert (deltas < TOL).all()
153
+
154
+
155
+ @pytest.mark.parametrize("name", NAMES)
156
+ def test_reduction(name):
157
+ """Test that the elements of the rotation group are correctly
158
+ mapped onto the identity rotation."""
159
+ g = Rotation.create_group(name)
160
+ f = g.reduce(g)
161
+ assert_array_almost_equal(f.magnitude(), np.zeros(len(g)))
162
+
163
+
164
+ @pytest.mark.parametrize("name", NAMES)
165
+ def test_single_reduction(name):
166
+ g = Rotation.create_group(name)
167
+ f = g[-1].reduce(g)
168
+ assert_array_almost_equal(f.magnitude(), 0)
169
+ assert f.as_quat().shape == (4,)
.venv/Lib/site-packages/scipy/spatial/transform/tests/test_rotation_spline.py ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from itertools import product
2
+ import numpy as np
3
+ from numpy.testing import assert_allclose
4
+ from pytest import raises
5
+ from scipy.spatial.transform import Rotation, RotationSpline
6
+ from scipy.spatial.transform._rotation_spline import (
7
+ _angular_rate_to_rotvec_dot_matrix,
8
+ _rotvec_dot_to_angular_rate_matrix,
9
+ _matrix_vector_product_of_stacks,
10
+ _angular_acceleration_nonlinear_term,
11
+ _create_block_3_diagonal_matrix)
12
+
13
+
14
+ def test_angular_rate_to_rotvec_conversions():
15
+ np.random.seed(0)
16
+ rv = np.random.randn(4, 3)
17
+ A = _angular_rate_to_rotvec_dot_matrix(rv)
18
+ A_inv = _rotvec_dot_to_angular_rate_matrix(rv)
19
+
20
+ # When the rotation vector is aligned with the angular rate, then
21
+ # the rotation vector rate and angular rate are the same.
22
+ assert_allclose(_matrix_vector_product_of_stacks(A, rv), rv)
23
+ assert_allclose(_matrix_vector_product_of_stacks(A_inv, rv), rv)
24
+
25
+ # A and A_inv must be reciprocal to each other.
26
+ I_stack = np.empty((4, 3, 3))
27
+ I_stack[:] = np.eye(3)
28
+ assert_allclose(np.matmul(A, A_inv), I_stack, atol=1e-15)
29
+
30
+
31
+ def test_angular_rate_nonlinear_term():
32
+ # The only simple test is to check that the term is zero when
33
+ # the rotation vector
34
+ np.random.seed(0)
35
+ rv = np.random.rand(4, 3)
36
+ assert_allclose(_angular_acceleration_nonlinear_term(rv, rv), 0,
37
+ atol=1e-19)
38
+
39
+
40
+ def test_create_block_3_diagonal_matrix():
41
+ np.random.seed(0)
42
+ A = np.empty((4, 3, 3))
43
+ A[:] = np.arange(1, 5)[:, None, None]
44
+
45
+ B = np.empty((4, 3, 3))
46
+ B[:] = -np.arange(1, 5)[:, None, None]
47
+ d = 10 * np.arange(10, 15)
48
+
49
+ banded = _create_block_3_diagonal_matrix(A, B, d)
50
+
51
+ # Convert the banded matrix to the full matrix.
52
+ k, l = list(zip(*product(np.arange(banded.shape[0]),
53
+ np.arange(banded.shape[1]))))
54
+ k = np.asarray(k)
55
+ l = np.asarray(l)
56
+
57
+ i = k - 5 + l
58
+ j = l
59
+ values = banded.ravel()
60
+ mask = (i >= 0) & (i < 15)
61
+ i = i[mask]
62
+ j = j[mask]
63
+ values = values[mask]
64
+ full = np.zeros((15, 15))
65
+ full[i, j] = values
66
+
67
+ zero = np.zeros((3, 3))
68
+ eye = np.eye(3)
69
+
70
+ # Create the reference full matrix in the most straightforward manner.
71
+ ref = np.block([
72
+ [d[0] * eye, B[0], zero, zero, zero],
73
+ [A[0], d[1] * eye, B[1], zero, zero],
74
+ [zero, A[1], d[2] * eye, B[2], zero],
75
+ [zero, zero, A[2], d[3] * eye, B[3]],
76
+ [zero, zero, zero, A[3], d[4] * eye],
77
+ ])
78
+
79
+ assert_allclose(full, ref, atol=1e-19)
80
+
81
+
82
+ def test_spline_2_rotations():
83
+ times = [0, 10]
84
+ rotations = Rotation.from_euler('xyz', [[0, 0, 0], [10, -20, 30]],
85
+ degrees=True)
86
+ spline = RotationSpline(times, rotations)
87
+
88
+ rv = (rotations[0].inv() * rotations[1]).as_rotvec()
89
+ rate = rv / (times[1] - times[0])
90
+ times_check = np.array([-1, 5, 12])
91
+ dt = times_check - times[0]
92
+ rv_ref = rate * dt[:, None]
93
+
94
+ assert_allclose(spline(times_check).as_rotvec(), rv_ref)
95
+ assert_allclose(spline(times_check, 1), np.resize(rate, (3, 3)))
96
+ assert_allclose(spline(times_check, 2), 0, atol=1e-16)
97
+
98
+
99
+ def test_constant_attitude():
100
+ times = np.arange(10)
101
+ rotations = Rotation.from_rotvec(np.ones((10, 3)))
102
+ spline = RotationSpline(times, rotations)
103
+
104
+ times_check = np.linspace(-1, 11)
105
+ assert_allclose(spline(times_check).as_rotvec(), 1, rtol=1e-15)
106
+ assert_allclose(spline(times_check, 1), 0, atol=1e-17)
107
+ assert_allclose(spline(times_check, 2), 0, atol=1e-17)
108
+
109
+ assert_allclose(spline(5.5).as_rotvec(), 1, rtol=1e-15)
110
+ assert_allclose(spline(5.5, 1), 0, atol=1e-17)
111
+ assert_allclose(spline(5.5, 2), 0, atol=1e-17)
112
+
113
+
114
+ def test_spline_properties():
115
+ times = np.array([0, 5, 15, 27])
116
+ angles = [[-5, 10, 27], [3, 5, 38], [-12, 10, 25], [-15, 20, 11]]
117
+
118
+ rotations = Rotation.from_euler('xyz', angles, degrees=True)
119
+ spline = RotationSpline(times, rotations)
120
+
121
+ assert_allclose(spline(times).as_euler('xyz', degrees=True), angles)
122
+ assert_allclose(spline(0).as_euler('xyz', degrees=True), angles[0])
123
+
124
+ h = 1e-8
125
+ rv0 = spline(times).as_rotvec()
126
+ rvm = spline(times - h).as_rotvec()
127
+ rvp = spline(times + h).as_rotvec()
128
+ # rtol bumped from 1e-15 to 1.5e-15 in gh18414 for linux 32 bit
129
+ assert_allclose(rv0, 0.5 * (rvp + rvm), rtol=1.5e-15)
130
+
131
+ r0 = spline(times, 1)
132
+ rm = spline(times - h, 1)
133
+ rp = spline(times + h, 1)
134
+ assert_allclose(r0, 0.5 * (rm + rp), rtol=1e-14)
135
+
136
+ a0 = spline(times, 2)
137
+ am = spline(times - h, 2)
138
+ ap = spline(times + h, 2)
139
+ assert_allclose(a0, am, rtol=1e-7)
140
+ assert_allclose(a0, ap, rtol=1e-7)
141
+
142
+
143
+ def test_error_handling():
144
+ raises(ValueError, RotationSpline, [1.0], Rotation.random())
145
+
146
+ r = Rotation.random(10)
147
+ t = np.arange(10).reshape(5, 2)
148
+ raises(ValueError, RotationSpline, t, r)
149
+
150
+ t = np.arange(9)
151
+ raises(ValueError, RotationSpline, t, r)
152
+
153
+ t = np.arange(10)
154
+ t[5] = 0
155
+ raises(ValueError, RotationSpline, t, r)
156
+
157
+ t = np.arange(10)
158
+
159
+ s = RotationSpline(t, r)
160
+ raises(ValueError, s, 10, -1)
161
+
162
+ raises(ValueError, s, np.arange(10).reshape(5, 2))
.venv/Lib/site-packages/scipy/special/_add_newdocs.py ADDED
The diff for this file is too large to render. See raw diff
 
.venv/Lib/site-packages/scipy/special/_basic.py ADDED
The diff for this file is too large to render. See raw diff
 
.venv/Lib/site-packages/scipy/special/_cdflib.cp39-win_amd64.dll.a ADDED
Binary file (1.54 kB). View file
 
.venv/Lib/site-packages/scipy/special/_cdflib.cp39-win_amd64.pyd ADDED
Binary file (171 kB). View file
 
.venv/Lib/site-packages/scipy/special/_comb.cp39-win_amd64.dll.a ADDED
Binary file (1.52 kB). View file
 
.venv/Lib/site-packages/scipy/special/_comb.cp39-win_amd64.pyd ADDED
Binary file (49.7 kB). View file
 
.venv/Lib/site-packages/scipy/special/_ellip_harm.py ADDED
@@ -0,0 +1,214 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+
3
+ from ._ufuncs import _ellip_harm
4
+ from ._ellip_harm_2 import _ellipsoid, _ellipsoid_norm
5
+
6
+
7
+ def ellip_harm(h2, k2, n, p, s, signm=1, signn=1):
8
+ r"""
9
+ Ellipsoidal harmonic functions E^p_n(l)
10
+
11
+ These are also known as Lame functions of the first kind, and are
12
+ solutions to the Lame equation:
13
+
14
+ .. math:: (s^2 - h^2)(s^2 - k^2)E''(s)
15
+ + s(2s^2 - h^2 - k^2)E'(s) + (a - q s^2)E(s) = 0
16
+
17
+ where :math:`q = (n+1)n` and :math:`a` is the eigenvalue (not
18
+ returned) corresponding to the solutions.
19
+
20
+ Parameters
21
+ ----------
22
+ h2 : float
23
+ ``h**2``
24
+ k2 : float
25
+ ``k**2``; should be larger than ``h**2``
26
+ n : int
27
+ Degree
28
+ s : float
29
+ Coordinate
30
+ p : int
31
+ Order, can range between [1,2n+1]
32
+ signm : {1, -1}, optional
33
+ Sign of prefactor of functions. Can be +/-1. See Notes.
34
+ signn : {1, -1}, optional
35
+ Sign of prefactor of functions. Can be +/-1. See Notes.
36
+
37
+ Returns
38
+ -------
39
+ E : float
40
+ the harmonic :math:`E^p_n(s)`
41
+
42
+ See Also
43
+ --------
44
+ ellip_harm_2, ellip_normal
45
+
46
+ Notes
47
+ -----
48
+ The geometric interpretation of the ellipsoidal functions is
49
+ explained in [2]_, [3]_, [4]_. The `signm` and `signn` arguments control the
50
+ sign of prefactors for functions according to their type::
51
+
52
+ K : +1
53
+ L : signm
54
+ M : signn
55
+ N : signm*signn
56
+
57
+ .. versionadded:: 0.15.0
58
+
59
+ References
60
+ ----------
61
+ .. [1] Digital Library of Mathematical Functions 29.12
62
+ https://dlmf.nist.gov/29.12
63
+ .. [2] Bardhan and Knepley, "Computational science and
64
+ re-discovery: open-source implementations of
65
+ ellipsoidal harmonics for problems in potential theory",
66
+ Comput. Sci. Disc. 5, 014006 (2012)
67
+ :doi:`10.1088/1749-4699/5/1/014006`.
68
+ .. [3] David J.and Dechambre P, "Computation of Ellipsoidal
69
+ Gravity Field Harmonics for small solar system bodies"
70
+ pp. 30-36, 2000
71
+ .. [4] George Dassios, "Ellipsoidal Harmonics: Theory and Applications"
72
+ pp. 418, 2012
73
+
74
+ Examples
75
+ --------
76
+ >>> from scipy.special import ellip_harm
77
+ >>> w = ellip_harm(5,8,1,1,2.5)
78
+ >>> w
79
+ 2.5
80
+
81
+ Check that the functions indeed are solutions to the Lame equation:
82
+
83
+ >>> import numpy as np
84
+ >>> from scipy.interpolate import UnivariateSpline
85
+ >>> def eigenvalue(f, df, ddf):
86
+ ... r = (((s**2 - h**2) * (s**2 - k**2) * ddf
87
+ ... + s * (2*s**2 - h**2 - k**2) * df
88
+ ... - n * (n + 1)*s**2*f) / f)
89
+ ... return -r.mean(), r.std()
90
+ >>> s = np.linspace(0.1, 10, 200)
91
+ >>> k, h, n, p = 8.0, 2.2, 3, 2
92
+ >>> E = ellip_harm(h**2, k**2, n, p, s)
93
+ >>> E_spl = UnivariateSpline(s, E)
94
+ >>> a, a_err = eigenvalue(E_spl(s), E_spl(s,1), E_spl(s,2))
95
+ >>> a, a_err
96
+ (583.44366156701483, 6.4580890640310646e-11)
97
+
98
+ """ # noqa: E501
99
+ return _ellip_harm(h2, k2, n, p, s, signm, signn)
100
+
101
+
102
+ _ellip_harm_2_vec = np.vectorize(_ellipsoid, otypes='d')
103
+
104
+
105
+ def ellip_harm_2(h2, k2, n, p, s):
106
+ r"""
107
+ Ellipsoidal harmonic functions F^p_n(l)
108
+
109
+ These are also known as Lame functions of the second kind, and are
110
+ solutions to the Lame equation:
111
+
112
+ .. math:: (s^2 - h^2)(s^2 - k^2)F''(s)
113
+ + s(2s^2 - h^2 - k^2)F'(s) + (a - q s^2)F(s) = 0
114
+
115
+ where :math:`q = (n+1)n` and :math:`a` is the eigenvalue (not
116
+ returned) corresponding to the solutions.
117
+
118
+ Parameters
119
+ ----------
120
+ h2 : float
121
+ ``h**2``
122
+ k2 : float
123
+ ``k**2``; should be larger than ``h**2``
124
+ n : int
125
+ Degree.
126
+ p : int
127
+ Order, can range between [1,2n+1].
128
+ s : float
129
+ Coordinate
130
+
131
+ Returns
132
+ -------
133
+ F : float
134
+ The harmonic :math:`F^p_n(s)`
135
+
136
+ See Also
137
+ --------
138
+ ellip_harm, ellip_normal
139
+
140
+ Notes
141
+ -----
142
+ Lame functions of the second kind are related to the functions of the first kind:
143
+
144
+ .. math::
145
+
146
+ F^p_n(s)=(2n + 1)E^p_n(s)\int_{0}^{1/s}
147
+ \frac{du}{(E^p_n(1/u))^2\sqrt{(1-u^2k^2)(1-u^2h^2)}}
148
+
149
+ .. versionadded:: 0.15.0
150
+
151
+ Examples
152
+ --------
153
+ >>> from scipy.special import ellip_harm_2
154
+ >>> w = ellip_harm_2(5,8,2,1,10)
155
+ >>> w
156
+ 0.00108056853382
157
+
158
+ """
159
+ with np.errstate(all='ignore'):
160
+ return _ellip_harm_2_vec(h2, k2, n, p, s)
161
+
162
+
163
+ def _ellip_normal_vec(h2, k2, n, p):
164
+ return _ellipsoid_norm(h2, k2, n, p)
165
+
166
+
167
+ _ellip_normal_vec = np.vectorize(_ellip_normal_vec, otypes='d')
168
+
169
+
170
+ def ellip_normal(h2, k2, n, p):
171
+ r"""
172
+ Ellipsoidal harmonic normalization constants gamma^p_n
173
+
174
+ The normalization constant is defined as
175
+
176
+ .. math::
177
+
178
+ \gamma^p_n=8\int_{0}^{h}dx\int_{h}^{k}dy
179
+ \frac{(y^2-x^2)(E^p_n(y)E^p_n(x))^2}{\sqrt((k^2-y^2)(y^2-h^2)(h^2-x^2)(k^2-x^2)}
180
+
181
+ Parameters
182
+ ----------
183
+ h2 : float
184
+ ``h**2``
185
+ k2 : float
186
+ ``k**2``; should be larger than ``h**2``
187
+ n : int
188
+ Degree.
189
+ p : int
190
+ Order, can range between [1,2n+1].
191
+
192
+ Returns
193
+ -------
194
+ gamma : float
195
+ The normalization constant :math:`\gamma^p_n`
196
+
197
+ See Also
198
+ --------
199
+ ellip_harm, ellip_harm_2
200
+
201
+ Notes
202
+ -----
203
+ .. versionadded:: 0.15.0
204
+
205
+ Examples
206
+ --------
207
+ >>> from scipy.special import ellip_normal
208
+ >>> w = ellip_normal(5,8,3,7)
209
+ >>> w
210
+ 1723.38796997
211
+
212
+ """
213
+ with np.errstate(all='ignore'):
214
+ return _ellip_normal_vec(h2, k2, n, p)
.venv/Lib/site-packages/scipy/special/_ellip_harm_2.cp39-win_amd64.dll.a ADDED
Binary file (1.61 kB). View file
 
.venv/Lib/site-packages/scipy/special/_ellip_harm_2.cp39-win_amd64.pyd ADDED
Binary file (109 kB). View file
 
.venv/Lib/site-packages/scipy/special/_lambertw.py ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from ._ufuncs import _lambertw
2
+
3
+ import numpy as np
4
+
5
+
6
+ def lambertw(z, k=0, tol=1e-8):
7
+ r"""
8
+ lambertw(z, k=0, tol=1e-8)
9
+
10
+ Lambert W function.
11
+
12
+ The Lambert W function `W(z)` is defined as the inverse function
13
+ of ``w * exp(w)``. In other words, the value of ``W(z)`` is
14
+ such that ``z = W(z) * exp(W(z))`` for any complex number
15
+ ``z``.
16
+
17
+ The Lambert W function is a multivalued function with infinitely
18
+ many branches. Each branch gives a separate solution of the
19
+ equation ``z = w exp(w)``. Here, the branches are indexed by the
20
+ integer `k`.
21
+
22
+ Parameters
23
+ ----------
24
+ z : array_like
25
+ Input argument.
26
+ k : int, optional
27
+ Branch index.
28
+ tol : float, optional
29
+ Evaluation tolerance.
30
+
31
+ Returns
32
+ -------
33
+ w : array
34
+ `w` will have the same shape as `z`.
35
+
36
+ See Also
37
+ --------
38
+ wrightomega : the Wright Omega function
39
+
40
+ Notes
41
+ -----
42
+ All branches are supported by `lambertw`:
43
+
44
+ * ``lambertw(z)`` gives the principal solution (branch 0)
45
+ * ``lambertw(z, k)`` gives the solution on branch `k`
46
+
47
+ The Lambert W function has two partially real branches: the
48
+ principal branch (`k = 0`) is real for real ``z > -1/e``, and the
49
+ ``k = -1`` branch is real for ``-1/e < z < 0``. All branches except
50
+ ``k = 0`` have a logarithmic singularity at ``z = 0``.
51
+
52
+ **Possible issues**
53
+
54
+ The evaluation can become inaccurate very close to the branch point
55
+ at ``-1/e``. In some corner cases, `lambertw` might currently
56
+ fail to converge, or can end up on the wrong branch.
57
+
58
+ **Algorithm**
59
+
60
+ Halley's iteration is used to invert ``w * exp(w)``, using a first-order
61
+ asymptotic approximation (O(log(w)) or `O(w)`) as the initial estimate.
62
+
63
+ The definition, implementation and choice of branches is based on [2]_.
64
+
65
+ References
66
+ ----------
67
+ .. [1] https://en.wikipedia.org/wiki/Lambert_W_function
68
+ .. [2] Corless et al, "On the Lambert W function", Adv. Comp. Math. 5
69
+ (1996) 329-359.
70
+ https://cs.uwaterloo.ca/research/tr/1993/03/W.pdf
71
+
72
+ Examples
73
+ --------
74
+ The Lambert W function is the inverse of ``w exp(w)``:
75
+
76
+ >>> import numpy as np
77
+ >>> from scipy.special import lambertw
78
+ >>> w = lambertw(1)
79
+ >>> w
80
+ (0.56714329040978384+0j)
81
+ >>> w * np.exp(w)
82
+ (1.0+0j)
83
+
84
+ Any branch gives a valid inverse:
85
+
86
+ >>> w = lambertw(1, k=3)
87
+ >>> w
88
+ (-2.8535817554090377+17.113535539412148j)
89
+ >>> w*np.exp(w)
90
+ (1.0000000000000002+1.609823385706477e-15j)
91
+
92
+ **Applications to equation-solving**
93
+
94
+ The Lambert W function may be used to solve various kinds of
95
+ equations. We give two examples here.
96
+
97
+ First, the function can be used to solve implicit equations of the
98
+ form
99
+
100
+ :math:`x = a + b e^{c x}`
101
+
102
+ for :math:`x`. We assume :math:`c` is not zero. After a little
103
+ algebra, the equation may be written
104
+
105
+ :math:`z e^z = -b c e^{a c}`
106
+
107
+ where :math:`z = c (a - x)`. :math:`z` may then be expressed using
108
+ the Lambert W function
109
+
110
+ :math:`z = W(-b c e^{a c})`
111
+
112
+ giving
113
+
114
+ :math:`x = a - W(-b c e^{a c})/c`
115
+
116
+ For example,
117
+
118
+ >>> a = 3
119
+ >>> b = 2
120
+ >>> c = -0.5
121
+
122
+ The solution to :math:`x = a + b e^{c x}` is:
123
+
124
+ >>> x = a - lambertw(-b*c*np.exp(a*c))/c
125
+ >>> x
126
+ (3.3707498368978794+0j)
127
+
128
+ Verify that it solves the equation:
129
+
130
+ >>> a + b*np.exp(c*x)
131
+ (3.37074983689788+0j)
132
+
133
+ The Lambert W function may also be used find the value of the infinite
134
+ power tower :math:`z^{z^{z^{\ldots}}}`:
135
+
136
+ >>> def tower(z, n):
137
+ ... if n == 0:
138
+ ... return z
139
+ ... return z ** tower(z, n-1)
140
+ ...
141
+ >>> tower(0.5, 100)
142
+ 0.641185744504986
143
+ >>> -lambertw(-np.log(0.5)) / np.log(0.5)
144
+ (0.64118574450498589+0j)
145
+ """
146
+ # TODO: special expert should inspect this
147
+ # interception; better place to do it?
148
+ k = np.asarray(k, dtype=np.dtype("long"))
149
+ return _lambertw(z, k, tol)
.venv/Lib/site-packages/scipy/special/_logsumexp.py ADDED
@@ -0,0 +1,307 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ from scipy._lib._util import _asarray_validated
3
+
4
+ __all__ = ["logsumexp", "softmax", "log_softmax"]
5
+
6
+
7
+ def logsumexp(a, axis=None, b=None, keepdims=False, return_sign=False):
8
+ """Compute the log of the sum of exponentials of input elements.
9
+
10
+ Parameters
11
+ ----------
12
+ a : array_like
13
+ Input array.
14
+ axis : None or int or tuple of ints, optional
15
+ Axis or axes over which the sum is taken. By default `axis` is None,
16
+ and all elements are summed.
17
+
18
+ .. versionadded:: 0.11.0
19
+ b : array-like, optional
20
+ Scaling factor for exp(`a`) must be of the same shape as `a` or
21
+ broadcastable to `a`. These values may be negative in order to
22
+ implement subtraction.
23
+
24
+ .. versionadded:: 0.12.0
25
+ keepdims : bool, optional
26
+ If this is set to True, the axes which are reduced are left in the
27
+ result as dimensions with size one. With this option, the result
28
+ will broadcast correctly against the original array.
29
+
30
+ .. versionadded:: 0.15.0
31
+ return_sign : bool, optional
32
+ If this is set to True, the result will be a pair containing sign
33
+ information; if False, results that are negative will be returned
34
+ as NaN. Default is False (no sign information).
35
+
36
+ .. versionadded:: 0.16.0
37
+
38
+ Returns
39
+ -------
40
+ res : ndarray
41
+ The result, ``np.log(np.sum(np.exp(a)))`` calculated in a numerically
42
+ more stable way. If `b` is given then ``np.log(np.sum(b*np.exp(a)))``
43
+ is returned. If ``return_sign`` is True, ``res`` contains the log of
44
+ the absolute value of the argument.
45
+ sgn : ndarray
46
+ If ``return_sign`` is True, this will be an array of floating-point
47
+ numbers matching res containing +1, 0, -1 (for real-valued inputs)
48
+ or a complex phase (for complex inputs). This gives the sign of the
49
+ argument of the logarithm in ``res``.
50
+ If ``return_sign`` is False, only one result is returned.
51
+
52
+ See Also
53
+ --------
54
+ numpy.logaddexp, numpy.logaddexp2
55
+
56
+ Notes
57
+ -----
58
+ NumPy has a logaddexp function which is very similar to `logsumexp`, but
59
+ only handles two arguments. `logaddexp.reduce` is similar to this
60
+ function, but may be less stable.
61
+
62
+ Examples
63
+ --------
64
+ >>> import numpy as np
65
+ >>> from scipy.special import logsumexp
66
+ >>> a = np.arange(10)
67
+ >>> logsumexp(a)
68
+ 9.4586297444267107
69
+ >>> np.log(np.sum(np.exp(a)))
70
+ 9.4586297444267107
71
+
72
+ With weights
73
+
74
+ >>> a = np.arange(10)
75
+ >>> b = np.arange(10, 0, -1)
76
+ >>> logsumexp(a, b=b)
77
+ 9.9170178533034665
78
+ >>> np.log(np.sum(b*np.exp(a)))
79
+ 9.9170178533034647
80
+
81
+ Returning a sign flag
82
+
83
+ >>> logsumexp([1,2],b=[1,-1],return_sign=True)
84
+ (1.5413248546129181, -1.0)
85
+
86
+ Notice that `logsumexp` does not directly support masked arrays. To use it
87
+ on a masked array, convert the mask into zero weights:
88
+
89
+ >>> a = np.ma.array([np.log(2), 2, np.log(3)],
90
+ ... mask=[False, True, False])
91
+ >>> b = (~a.mask).astype(int)
92
+ >>> logsumexp(a.data, b=b), np.log(5)
93
+ 1.6094379124341005, 1.6094379124341005
94
+
95
+ """
96
+ a = _asarray_validated(a, check_finite=False)
97
+ if b is not None:
98
+ a, b = np.broadcast_arrays(a, b)
99
+ if np.any(b == 0):
100
+ a = a + 0. # promote to at least float
101
+ a[b == 0] = -np.inf
102
+
103
+ # Scale by real part for complex inputs, because this affects
104
+ # the magnitude of the exponential.
105
+ a_max = np.amax(a.real, axis=axis, keepdims=True)
106
+
107
+ if a_max.ndim > 0:
108
+ a_max[~np.isfinite(a_max)] = 0
109
+ elif not np.isfinite(a_max):
110
+ a_max = 0
111
+
112
+ if b is not None:
113
+ b = np.asarray(b)
114
+ tmp = b * np.exp(a - a_max)
115
+ else:
116
+ tmp = np.exp(a - a_max)
117
+
118
+ # suppress warnings about log of zero
119
+ with np.errstate(divide='ignore'):
120
+ s = np.sum(tmp, axis=axis, keepdims=keepdims)
121
+ if return_sign:
122
+ # For complex, use the numpy>=2.0 convention for sign.
123
+ if np.issubdtype(s.dtype, np.complexfloating):
124
+ sgn = s / np.where(s == 0, 1, abs(s))
125
+ else:
126
+ sgn = np.sign(s)
127
+ s = abs(s)
128
+ out = np.log(s)
129
+
130
+ if not keepdims:
131
+ a_max = np.squeeze(a_max, axis=axis)
132
+ out += a_max
133
+
134
+ if return_sign:
135
+ return out, sgn
136
+ else:
137
+ return out
138
+
139
+
140
+ def softmax(x, axis=None):
141
+ r"""Compute the softmax function.
142
+
143
+ The softmax function transforms each element of a collection by
144
+ computing the exponential of each element divided by the sum of the
145
+ exponentials of all the elements. That is, if `x` is a one-dimensional
146
+ numpy array::
147
+
148
+ softmax(x) = np.exp(x)/sum(np.exp(x))
149
+
150
+ Parameters
151
+ ----------
152
+ x : array_like
153
+ Input array.
154
+ axis : int or tuple of ints, optional
155
+ Axis to compute values along. Default is None and softmax will be
156
+ computed over the entire array `x`.
157
+
158
+ Returns
159
+ -------
160
+ s : ndarray
161
+ An array the same shape as `x`. The result will sum to 1 along the
162
+ specified axis.
163
+
164
+ Notes
165
+ -----
166
+ The formula for the softmax function :math:`\sigma(x)` for a vector
167
+ :math:`x = \{x_0, x_1, ..., x_{n-1}\}` is
168
+
169
+ .. math:: \sigma(x)_j = \frac{e^{x_j}}{\sum_k e^{x_k}}
170
+
171
+ The `softmax` function is the gradient of `logsumexp`.
172
+
173
+ The implementation uses shifting to avoid overflow. See [1]_ for more
174
+ details.
175
+
176
+ .. versionadded:: 1.2.0
177
+
178
+ References
179
+ ----------
180
+ .. [1] P. Blanchard, D.J. Higham, N.J. Higham, "Accurately computing the
181
+ log-sum-exp and softmax functions", IMA Journal of Numerical Analysis,
182
+ Vol.41(4), :doi:`10.1093/imanum/draa038`.
183
+
184
+ Examples
185
+ --------
186
+ >>> import numpy as np
187
+ >>> from scipy.special import softmax
188
+ >>> np.set_printoptions(precision=5)
189
+
190
+ >>> x = np.array([[1, 0.5, 0.2, 3],
191
+ ... [1, -1, 7, 3],
192
+ ... [2, 12, 13, 3]])
193
+ ...
194
+
195
+ Compute the softmax transformation over the entire array.
196
+
197
+ >>> m = softmax(x)
198
+ >>> m
199
+ array([[ 4.48309e-06, 2.71913e-06, 2.01438e-06, 3.31258e-05],
200
+ [ 4.48309e-06, 6.06720e-07, 1.80861e-03, 3.31258e-05],
201
+ [ 1.21863e-05, 2.68421e-01, 7.29644e-01, 3.31258e-05]])
202
+
203
+ >>> m.sum()
204
+ 1.0
205
+
206
+ Compute the softmax transformation along the first axis (i.e., the
207
+ columns).
208
+
209
+ >>> m = softmax(x, axis=0)
210
+
211
+ >>> m
212
+ array([[ 2.11942e-01, 1.01300e-05, 2.75394e-06, 3.33333e-01],
213
+ [ 2.11942e-01, 2.26030e-06, 2.47262e-03, 3.33333e-01],
214
+ [ 5.76117e-01, 9.99988e-01, 9.97525e-01, 3.33333e-01]])
215
+
216
+ >>> m.sum(axis=0)
217
+ array([ 1., 1., 1., 1.])
218
+
219
+ Compute the softmax transformation along the second axis (i.e., the rows).
220
+
221
+ >>> m = softmax(x, axis=1)
222
+ >>> m
223
+ array([[ 1.05877e-01, 6.42177e-02, 4.75736e-02, 7.82332e-01],
224
+ [ 2.42746e-03, 3.28521e-04, 9.79307e-01, 1.79366e-02],
225
+ [ 1.22094e-05, 2.68929e-01, 7.31025e-01, 3.31885e-05]])
226
+
227
+ >>> m.sum(axis=1)
228
+ array([ 1., 1., 1.])
229
+
230
+ """
231
+ x = _asarray_validated(x, check_finite=False)
232
+ x_max = np.amax(x, axis=axis, keepdims=True)
233
+ exp_x_shifted = np.exp(x - x_max)
234
+ return exp_x_shifted / np.sum(exp_x_shifted, axis=axis, keepdims=True)
235
+
236
+
237
+ def log_softmax(x, axis=None):
238
+ r"""Compute the logarithm of the softmax function.
239
+
240
+ In principle::
241
+
242
+ log_softmax(x) = log(softmax(x))
243
+
244
+ but using a more accurate implementation.
245
+
246
+ Parameters
247
+ ----------
248
+ x : array_like
249
+ Input array.
250
+ axis : int or tuple of ints, optional
251
+ Axis to compute values along. Default is None and softmax will be
252
+ computed over the entire array `x`.
253
+
254
+ Returns
255
+ -------
256
+ s : ndarray or scalar
257
+ An array with the same shape as `x`. Exponential of the result will
258
+ sum to 1 along the specified axis. If `x` is a scalar, a scalar is
259
+ returned.
260
+
261
+ Notes
262
+ -----
263
+ `log_softmax` is more accurate than ``np.log(softmax(x))`` with inputs that
264
+ make `softmax` saturate (see examples below).
265
+
266
+ .. versionadded:: 1.5.0
267
+
268
+ Examples
269
+ --------
270
+ >>> import numpy as np
271
+ >>> from scipy.special import log_softmax
272
+ >>> from scipy.special import softmax
273
+ >>> np.set_printoptions(precision=5)
274
+
275
+ >>> x = np.array([1000.0, 1.0])
276
+
277
+ >>> y = log_softmax(x)
278
+ >>> y
279
+ array([ 0., -999.])
280
+
281
+ >>> with np.errstate(divide='ignore'):
282
+ ... y = np.log(softmax(x))
283
+ ...
284
+ >>> y
285
+ array([ 0., -inf])
286
+
287
+ """
288
+
289
+ x = _asarray_validated(x, check_finite=False)
290
+
291
+ x_max = np.amax(x, axis=axis, keepdims=True)
292
+
293
+ if x_max.ndim > 0:
294
+ x_max[~np.isfinite(x_max)] = 0
295
+ elif not np.isfinite(x_max):
296
+ x_max = 0
297
+
298
+ tmp = x - x_max
299
+ exp_tmp = np.exp(tmp)
300
+
301
+ # suppress warnings about log of zero
302
+ with np.errstate(divide='ignore'):
303
+ s = np.sum(exp_tmp, axis=axis, keepdims=True)
304
+ out = np.log(s)
305
+
306
+ out = tmp - out
307
+ return out
.venv/Lib/site-packages/scipy/special/_mptestutils.py ADDED
@@ -0,0 +1,453 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import time
4
+ from itertools import zip_longest
5
+
6
+ import numpy as np
7
+ from numpy.testing import assert_
8
+ import pytest
9
+
10
+ from scipy.special._testutils import assert_func_equal
11
+
12
+ try:
13
+ import mpmath
14
+ except ImportError:
15
+ pass
16
+
17
+
18
+ # ------------------------------------------------------------------------------
19
+ # Machinery for systematic tests with mpmath
20
+ # ------------------------------------------------------------------------------
21
+
22
+ class Arg:
23
+ """Generate a set of numbers on the real axis, concentrating on
24
+ 'interesting' regions and covering all orders of magnitude.
25
+
26
+ """
27
+
28
+ def __init__(self, a=-np.inf, b=np.inf, inclusive_a=True, inclusive_b=True):
29
+ if a > b:
30
+ raise ValueError("a should be less than or equal to b")
31
+ if a == -np.inf:
32
+ a = -0.5*np.finfo(float).max
33
+ if b == np.inf:
34
+ b = 0.5*np.finfo(float).max
35
+ self.a, self.b = a, b
36
+
37
+ self.inclusive_a, self.inclusive_b = inclusive_a, inclusive_b
38
+
39
+ def _positive_values(self, a, b, n):
40
+ if a < 0:
41
+ raise ValueError("a should be positive")
42
+
43
+ # Try to put half of the points into a linspace between a and
44
+ # 10 the other half in a logspace.
45
+ if n % 2 == 0:
46
+ nlogpts = n//2
47
+ nlinpts = nlogpts
48
+ else:
49
+ nlogpts = n//2
50
+ nlinpts = nlogpts + 1
51
+
52
+ if a >= 10:
53
+ # Outside of linspace range; just return a logspace.
54
+ pts = np.logspace(np.log10(a), np.log10(b), n)
55
+ elif a > 0 and b < 10:
56
+ # Outside of logspace range; just return a linspace
57
+ pts = np.linspace(a, b, n)
58
+ elif a > 0:
59
+ # Linspace between a and 10 and a logspace between 10 and
60
+ # b.
61
+ linpts = np.linspace(a, 10, nlinpts, endpoint=False)
62
+ logpts = np.logspace(1, np.log10(b), nlogpts)
63
+ pts = np.hstack((linpts, logpts))
64
+ elif a == 0 and b <= 10:
65
+ # Linspace between 0 and b and a logspace between 0 and
66
+ # the smallest positive point of the linspace
67
+ linpts = np.linspace(0, b, nlinpts)
68
+ if linpts.size > 1:
69
+ right = np.log10(linpts[1])
70
+ else:
71
+ right = -30
72
+ logpts = np.logspace(-30, right, nlogpts, endpoint=False)
73
+ pts = np.hstack((logpts, linpts))
74
+ else:
75
+ # Linspace between 0 and 10, logspace between 0 and the
76
+ # smallest positive point of the linspace, and a logspace
77
+ # between 10 and b.
78
+ if nlogpts % 2 == 0:
79
+ nlogpts1 = nlogpts//2
80
+ nlogpts2 = nlogpts1
81
+ else:
82
+ nlogpts1 = nlogpts//2
83
+ nlogpts2 = nlogpts1 + 1
84
+ linpts = np.linspace(0, 10, nlinpts, endpoint=False)
85
+ if linpts.size > 1:
86
+ right = np.log10(linpts[1])
87
+ else:
88
+ right = -30
89
+ logpts1 = np.logspace(-30, right, nlogpts1, endpoint=False)
90
+ logpts2 = np.logspace(1, np.log10(b), nlogpts2)
91
+ pts = np.hstack((logpts1, linpts, logpts2))
92
+
93
+ return np.sort(pts)
94
+
95
+ def values(self, n):
96
+ """Return an array containing n numbers."""
97
+ a, b = self.a, self.b
98
+ if a == b:
99
+ return np.zeros(n)
100
+
101
+ if not self.inclusive_a:
102
+ n += 1
103
+ if not self.inclusive_b:
104
+ n += 1
105
+
106
+ if n % 2 == 0:
107
+ n1 = n//2
108
+ n2 = n1
109
+ else:
110
+ n1 = n//2
111
+ n2 = n1 + 1
112
+
113
+ if a >= 0:
114
+ pospts = self._positive_values(a, b, n)
115
+ negpts = []
116
+ elif b <= 0:
117
+ pospts = []
118
+ negpts = -self._positive_values(-b, -a, n)
119
+ else:
120
+ pospts = self._positive_values(0, b, n1)
121
+ negpts = -self._positive_values(0, -a, n2 + 1)
122
+ # Don't want to get zero twice
123
+ negpts = negpts[1:]
124
+ pts = np.hstack((negpts[::-1], pospts))
125
+
126
+ if not self.inclusive_a:
127
+ pts = pts[1:]
128
+ if not self.inclusive_b:
129
+ pts = pts[:-1]
130
+ return pts
131
+
132
+
133
+ class FixedArg:
134
+ def __init__(self, values):
135
+ self._values = np.asarray(values)
136
+
137
+ def values(self, n):
138
+ return self._values
139
+
140
+
141
+ class ComplexArg:
142
+ def __init__(self, a=complex(-np.inf, -np.inf), b=complex(np.inf, np.inf)):
143
+ self.real = Arg(a.real, b.real)
144
+ self.imag = Arg(a.imag, b.imag)
145
+
146
+ def values(self, n):
147
+ m = int(np.floor(np.sqrt(n)))
148
+ x = self.real.values(m)
149
+ y = self.imag.values(m + 1)
150
+ return (x[:,None] + 1j*y[None,:]).ravel()
151
+
152
+
153
+ class IntArg:
154
+ def __init__(self, a=-1000, b=1000):
155
+ self.a = a
156
+ self.b = b
157
+
158
+ def values(self, n):
159
+ v1 = Arg(self.a, self.b).values(max(1 + n//2, n-5)).astype(int)
160
+ v2 = np.arange(-5, 5)
161
+ v = np.unique(np.r_[v1, v2])
162
+ v = v[(v >= self.a) & (v < self.b)]
163
+ return v
164
+
165
+
166
+ def get_args(argspec, n):
167
+ if isinstance(argspec, np.ndarray):
168
+ args = argspec.copy()
169
+ else:
170
+ nargs = len(argspec)
171
+ ms = np.asarray(
172
+ [1.5 if isinstance(spec, ComplexArg) else 1.0 for spec in argspec]
173
+ )
174
+ ms = (n**(ms/sum(ms))).astype(int) + 1
175
+
176
+ args = [spec.values(m) for spec, m in zip(argspec, ms)]
177
+ args = np.array(np.broadcast_arrays(*np.ix_(*args))).reshape(nargs, -1).T
178
+
179
+ return args
180
+
181
+
182
+ class MpmathData:
183
+ def __init__(self, scipy_func, mpmath_func, arg_spec, name=None,
184
+ dps=None, prec=None, n=None, rtol=1e-7, atol=1e-300,
185
+ ignore_inf_sign=False, distinguish_nan_and_inf=True,
186
+ nan_ok=True, param_filter=None):
187
+
188
+ # mpmath tests are really slow (see gh-6989). Use a small number of
189
+ # points by default, increase back to 5000 (old default) if XSLOW is
190
+ # set
191
+ if n is None:
192
+ try:
193
+ is_xslow = int(os.environ.get('SCIPY_XSLOW', '0'))
194
+ except ValueError:
195
+ is_xslow = False
196
+
197
+ n = 5000 if is_xslow else 500
198
+
199
+ self.scipy_func = scipy_func
200
+ self.mpmath_func = mpmath_func
201
+ self.arg_spec = arg_spec
202
+ self.dps = dps
203
+ self.prec = prec
204
+ self.n = n
205
+ self.rtol = rtol
206
+ self.atol = atol
207
+ self.ignore_inf_sign = ignore_inf_sign
208
+ self.nan_ok = nan_ok
209
+ if isinstance(self.arg_spec, np.ndarray):
210
+ self.is_complex = np.issubdtype(self.arg_spec.dtype, np.complexfloating)
211
+ else:
212
+ self.is_complex = any(
213
+ [isinstance(arg, ComplexArg) for arg in self.arg_spec]
214
+ )
215
+ self.ignore_inf_sign = ignore_inf_sign
216
+ self.distinguish_nan_and_inf = distinguish_nan_and_inf
217
+ if not name or name == '<lambda>':
218
+ name = getattr(scipy_func, '__name__', None)
219
+ if not name or name == '<lambda>':
220
+ name = getattr(mpmath_func, '__name__', None)
221
+ self.name = name
222
+ self.param_filter = param_filter
223
+
224
+ def check(self):
225
+ np.random.seed(1234)
226
+
227
+ # Generate values for the arguments
228
+ argarr = get_args(self.arg_spec, self.n)
229
+
230
+ # Check
231
+ old_dps, old_prec = mpmath.mp.dps, mpmath.mp.prec
232
+ try:
233
+ if self.dps is not None:
234
+ dps_list = [self.dps]
235
+ else:
236
+ dps_list = [20]
237
+ if self.prec is not None:
238
+ mpmath.mp.prec = self.prec
239
+
240
+ # Proper casting of mpmath input and output types. Using
241
+ # native mpmath types as inputs gives improved precision
242
+ # in some cases.
243
+ if np.issubdtype(argarr.dtype, np.complexfloating):
244
+ pytype = mpc2complex
245
+
246
+ def mptype(x):
247
+ return mpmath.mpc(complex(x))
248
+ else:
249
+ def mptype(x):
250
+ return mpmath.mpf(float(x))
251
+
252
+ def pytype(x):
253
+ if abs(x.imag) > 1e-16*(1 + abs(x.real)):
254
+ return np.nan
255
+ else:
256
+ return mpf2float(x.real)
257
+
258
+ # Try out different dps until one (or none) works
259
+ for j, dps in enumerate(dps_list):
260
+ mpmath.mp.dps = dps
261
+
262
+ try:
263
+ assert_func_equal(
264
+ self.scipy_func,
265
+ lambda *a: pytype(self.mpmath_func(*map(mptype, a))),
266
+ argarr,
267
+ vectorized=False,
268
+ rtol=self.rtol,
269
+ atol=self.atol,
270
+ ignore_inf_sign=self.ignore_inf_sign,
271
+ distinguish_nan_and_inf=self.distinguish_nan_and_inf,
272
+ nan_ok=self.nan_ok,
273
+ param_filter=self.param_filter
274
+ )
275
+ break
276
+ except AssertionError:
277
+ if j >= len(dps_list)-1:
278
+ # reraise the Exception
279
+ tp, value, tb = sys.exc_info()
280
+ if value.__traceback__ is not tb:
281
+ raise value.with_traceback(tb)
282
+ raise value
283
+ finally:
284
+ mpmath.mp.dps, mpmath.mp.prec = old_dps, old_prec
285
+
286
+ def __repr__(self):
287
+ if self.is_complex:
288
+ return f"<MpmathData: {self.name} (complex)>"
289
+ else:
290
+ return f"<MpmathData: {self.name}>"
291
+
292
+
293
+ def assert_mpmath_equal(*a, **kw):
294
+ d = MpmathData(*a, **kw)
295
+ d.check()
296
+
297
+
298
+ def nonfunctional_tooslow(func):
299
+ return pytest.mark.skip(
300
+ reason=" Test not yet functional (too slow), needs more work."
301
+ )(func)
302
+
303
+
304
+ # ------------------------------------------------------------------------------
305
+ # Tools for dealing with mpmath quirks
306
+ # ------------------------------------------------------------------------------
307
+
308
+ def mpf2float(x):
309
+ """
310
+ Convert an mpf to the nearest floating point number. Just using
311
+ float directly doesn't work because of results like this:
312
+
313
+ with mp.workdps(50):
314
+ float(mpf("0.99999999999999999")) = 0.9999999999999999
315
+
316
+ """
317
+ return float(mpmath.nstr(x, 17, min_fixed=0, max_fixed=0))
318
+
319
+
320
+ def mpc2complex(x):
321
+ return complex(mpf2float(x.real), mpf2float(x.imag))
322
+
323
+
324
+ def trace_args(func):
325
+ def tofloat(x):
326
+ if isinstance(x, mpmath.mpc):
327
+ return complex(x)
328
+ else:
329
+ return float(x)
330
+
331
+ def wrap(*a, **kw):
332
+ sys.stderr.write(f"{tuple(map(tofloat, a))!r}: ")
333
+ sys.stderr.flush()
334
+ try:
335
+ r = func(*a, **kw)
336
+ sys.stderr.write("-> %r" % r)
337
+ finally:
338
+ sys.stderr.write("\n")
339
+ sys.stderr.flush()
340
+ return r
341
+ return wrap
342
+
343
+
344
+ try:
345
+ import signal
346
+ POSIX = ('setitimer' in dir(signal))
347
+ except ImportError:
348
+ POSIX = False
349
+
350
+
351
+ class TimeoutError(Exception):
352
+ pass
353
+
354
+
355
+ def time_limited(timeout=0.5, return_val=np.nan, use_sigalrm=True):
356
+ """
357
+ Decorator for setting a timeout for pure-Python functions.
358
+
359
+ If the function does not return within `timeout` seconds, the
360
+ value `return_val` is returned instead.
361
+
362
+ On POSIX this uses SIGALRM by default. On non-POSIX, settrace is
363
+ used. Do not use this with threads: the SIGALRM implementation
364
+ does probably not work well. The settrace implementation only
365
+ traces the current thread.
366
+
367
+ The settrace implementation slows down execution speed. Slowdown
368
+ by a factor around 10 is probably typical.
369
+ """
370
+ if POSIX and use_sigalrm:
371
+ def sigalrm_handler(signum, frame):
372
+ raise TimeoutError()
373
+
374
+ def deco(func):
375
+ def wrap(*a, **kw):
376
+ old_handler = signal.signal(signal.SIGALRM, sigalrm_handler)
377
+ signal.setitimer(signal.ITIMER_REAL, timeout)
378
+ try:
379
+ return func(*a, **kw)
380
+ except TimeoutError:
381
+ return return_val
382
+ finally:
383
+ signal.setitimer(signal.ITIMER_REAL, 0)
384
+ signal.signal(signal.SIGALRM, old_handler)
385
+ return wrap
386
+ else:
387
+ def deco(func):
388
+ def wrap(*a, **kw):
389
+ start_time = time.time()
390
+
391
+ def trace(frame, event, arg):
392
+ if time.time() - start_time > timeout:
393
+ raise TimeoutError()
394
+ return trace
395
+ sys.settrace(trace)
396
+ try:
397
+ return func(*a, **kw)
398
+ except TimeoutError:
399
+ sys.settrace(None)
400
+ return return_val
401
+ finally:
402
+ sys.settrace(None)
403
+ return wrap
404
+ return deco
405
+
406
+
407
+ def exception_to_nan(func):
408
+ """Decorate function to return nan if it raises an exception"""
409
+ def wrap(*a, **kw):
410
+ try:
411
+ return func(*a, **kw)
412
+ except Exception:
413
+ return np.nan
414
+ return wrap
415
+
416
+
417
+ def inf_to_nan(func):
418
+ """Decorate function to return nan if it returns inf"""
419
+ def wrap(*a, **kw):
420
+ v = func(*a, **kw)
421
+ if not np.isfinite(v):
422
+ return np.nan
423
+ return v
424
+ return wrap
425
+
426
+
427
+ def mp_assert_allclose(res, std, atol=0, rtol=1e-17):
428
+ """
429
+ Compare lists of mpmath.mpf's or mpmath.mpc's directly so that it
430
+ can be done to higher precision than double.
431
+ """
432
+ failures = []
433
+ for k, (resval, stdval) in enumerate(zip_longest(res, std)):
434
+ if resval is None or stdval is None:
435
+ raise ValueError('Lengths of inputs res and std are not equal.')
436
+ if mpmath.fabs(resval - stdval) > atol + rtol*mpmath.fabs(stdval):
437
+ failures.append((k, resval, stdval))
438
+
439
+ nfail = len(failures)
440
+ if nfail > 0:
441
+ ndigits = int(abs(np.log10(rtol)))
442
+ msg = [""]
443
+ msg.append(f"Bad results ({nfail} out of {k + 1}) for the following points:")
444
+ for k, resval, stdval in failures:
445
+ resrep = mpmath.nstr(resval, ndigits, min_fixed=0, max_fixed=0)
446
+ stdrep = mpmath.nstr(stdval, ndigits, min_fixed=0, max_fixed=0)
447
+ if stdval == 0:
448
+ rdiff = "inf"
449
+ else:
450
+ rdiff = mpmath.fabs((resval - stdval)/stdval)
451
+ rdiff = mpmath.nstr(rdiff, 3)
452
+ msg.append(f"{k}: {resrep} != {stdrep} (rdiff {rdiff})")
453
+ assert_(False, "\n".join(msg))
.venv/Lib/site-packages/scipy/special/_orthogonal.py ADDED
@@ -0,0 +1,2605 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ A collection of functions to find the weights and abscissas for
3
+ Gaussian Quadrature.
4
+
5
+ These calculations are done by finding the eigenvalues of a
6
+ tridiagonal matrix whose entries are dependent on the coefficients
7
+ in the recursion formula for the orthogonal polynomials with the
8
+ corresponding weighting function over the interval.
9
+
10
+ Many recursion relations for orthogonal polynomials are given:
11
+
12
+ .. math::
13
+
14
+ a1n f_{n+1} (x) = (a2n + a3n x ) f_n (x) - a4n f_{n-1} (x)
15
+
16
+ The recursion relation of interest is
17
+
18
+ .. math::
19
+
20
+ P_{n+1} (x) = (x - A_n) P_n (x) - B_n P_{n-1} (x)
21
+
22
+ where :math:`P` has a different normalization than :math:`f`.
23
+
24
+ The coefficients can be found as:
25
+
26
+ .. math::
27
+
28
+ A_n = -a2n / a3n
29
+ \\qquad
30
+ B_n = ( a4n / a3n \\sqrt{h_n-1 / h_n})^2
31
+
32
+ where
33
+
34
+ .. math::
35
+
36
+ h_n = \\int_a^b w(x) f_n(x)^2
37
+
38
+ assume:
39
+
40
+ .. math::
41
+
42
+ P_0 (x) = 1
43
+ \\qquad
44
+ P_{-1} (x) == 0
45
+
46
+ For the mathematical background, see [golub.welsch-1969-mathcomp]_ and
47
+ [abramowitz.stegun-1965]_.
48
+
49
+ References
50
+ ----------
51
+ .. [golub.welsch-1969-mathcomp]
52
+ Golub, Gene H, and John H Welsch. 1969. Calculation of Gauss
53
+ Quadrature Rules. *Mathematics of Computation* 23, 221-230+s1--s10.
54
+
55
+ .. [abramowitz.stegun-1965]
56
+ Abramowitz, Milton, and Irene A Stegun. (1965) *Handbook of
57
+ Mathematical Functions: with Formulas, Graphs, and Mathematical
58
+ Tables*. Gaithersburg, MD: National Bureau of Standards.
59
+ http://www.math.sfu.ca/~cbm/aands/
60
+
61
+ .. [townsend.trogdon.olver-2014]
62
+ Townsend, A. and Trogdon, T. and Olver, S. (2014)
63
+ *Fast computation of Gauss quadrature nodes and
64
+ weights on the whole real line*. :arXiv:`1410.5286`.
65
+
66
+ .. [townsend.trogdon.olver-2015]
67
+ Townsend, A. and Trogdon, T. and Olver, S. (2015)
68
+ *Fast computation of Gauss quadrature nodes and
69
+ weights on the whole real line*.
70
+ IMA Journal of Numerical Analysis
71
+ :doi:`10.1093/imanum/drv002`.
72
+ """
73
+ #
74
+ # Author: Travis Oliphant 2000
75
+ # Updated Sep. 2003 (fixed bugs --- tested to be accurate)
76
+
77
+ # SciPy imports.
78
+ import numpy as np
79
+ from numpy import (exp, inf, pi, sqrt, floor, sin, cos, around,
80
+ hstack, arccos, arange)
81
+ from scipy import linalg
82
+ from scipy.special import airy
83
+
84
+ # Local imports.
85
+ # There is no .pyi file for _specfun
86
+ from . import _specfun # type: ignore
87
+ from . import _ufuncs
88
+ _gam = _ufuncs.gamma
89
+
90
+ _polyfuns = ['legendre', 'chebyt', 'chebyu', 'chebyc', 'chebys',
91
+ 'jacobi', 'laguerre', 'genlaguerre', 'hermite',
92
+ 'hermitenorm', 'gegenbauer', 'sh_legendre', 'sh_chebyt',
93
+ 'sh_chebyu', 'sh_jacobi']
94
+
95
+ # Correspondence between new and old names of root functions
96
+ _rootfuns_map = {'roots_legendre': 'p_roots',
97
+ 'roots_chebyt': 't_roots',
98
+ 'roots_chebyu': 'u_roots',
99
+ 'roots_chebyc': 'c_roots',
100
+ 'roots_chebys': 's_roots',
101
+ 'roots_jacobi': 'j_roots',
102
+ 'roots_laguerre': 'l_roots',
103
+ 'roots_genlaguerre': 'la_roots',
104
+ 'roots_hermite': 'h_roots',
105
+ 'roots_hermitenorm': 'he_roots',
106
+ 'roots_gegenbauer': 'cg_roots',
107
+ 'roots_sh_legendre': 'ps_roots',
108
+ 'roots_sh_chebyt': 'ts_roots',
109
+ 'roots_sh_chebyu': 'us_roots',
110
+ 'roots_sh_jacobi': 'js_roots'}
111
+
112
+ __all__ = _polyfuns + list(_rootfuns_map.keys())
113
+
114
+
115
+ class orthopoly1d(np.poly1d):
116
+
117
+ def __init__(self, roots, weights=None, hn=1.0, kn=1.0, wfunc=None,
118
+ limits=None, monic=False, eval_func=None):
119
+ equiv_weights = [weights[k] / wfunc(roots[k]) for
120
+ k in range(len(roots))]
121
+ mu = sqrt(hn)
122
+ if monic:
123
+ evf = eval_func
124
+ if evf:
125
+ knn = kn
126
+ def eval_func(x):
127
+ return evf(x) / knn
128
+ mu = mu / abs(kn)
129
+ kn = 1.0
130
+
131
+ # compute coefficients from roots, then scale
132
+ poly = np.poly1d(roots, r=True)
133
+ np.poly1d.__init__(self, poly.coeffs * float(kn))
134
+
135
+ self.weights = np.array(list(zip(roots, weights, equiv_weights)))
136
+ self.weight_func = wfunc
137
+ self.limits = limits
138
+ self.normcoef = mu
139
+
140
+ # Note: eval_func will be discarded on arithmetic
141
+ self._eval_func = eval_func
142
+
143
+ def __call__(self, v):
144
+ if self._eval_func and not isinstance(v, np.poly1d):
145
+ return self._eval_func(v)
146
+ else:
147
+ return np.poly1d.__call__(self, v)
148
+
149
+ def _scale(self, p):
150
+ if p == 1.0:
151
+ return
152
+ self._coeffs *= p
153
+
154
+ evf = self._eval_func
155
+ if evf:
156
+ self._eval_func = lambda x: evf(x) * p
157
+ self.normcoef *= p
158
+
159
+
160
+ def _gen_roots_and_weights(n, mu0, an_func, bn_func, f, df, symmetrize, mu):
161
+ """[x,w] = gen_roots_and_weights(n,an_func,sqrt_bn_func,mu)
162
+
163
+ Returns the roots (x) of an nth order orthogonal polynomial,
164
+ and weights (w) to use in appropriate Gaussian quadrature with that
165
+ orthogonal polynomial.
166
+
167
+ The polynomials have the recurrence relation
168
+ P_n+1(x) = (x - A_n) P_n(x) - B_n P_n-1(x)
169
+
170
+ an_func(n) should return A_n
171
+ sqrt_bn_func(n) should return sqrt(B_n)
172
+ mu ( = h_0 ) is the integral of the weight over the orthogonal
173
+ interval
174
+ """
175
+ k = np.arange(n, dtype='d')
176
+ c = np.zeros((2, n))
177
+ c[0,1:] = bn_func(k[1:])
178
+ c[1,:] = an_func(k)
179
+ x = linalg.eigvals_banded(c, overwrite_a_band=True)
180
+
181
+ # improve roots by one application of Newton's method
182
+ y = f(n, x)
183
+ dy = df(n, x)
184
+ x -= y/dy
185
+
186
+ # fm and dy may contain very large/small values, so we
187
+ # log-normalize them to maintain precision in the product fm*dy
188
+ fm = f(n-1, x)
189
+ log_fm = np.log(np.abs(fm))
190
+ log_dy = np.log(np.abs(dy))
191
+ fm /= np.exp((log_fm.max() + log_fm.min()) / 2.)
192
+ dy /= np.exp((log_dy.max() + log_dy.min()) / 2.)
193
+ w = 1.0 / (fm * dy)
194
+
195
+ if symmetrize:
196
+ w = (w + w[::-1]) / 2
197
+ x = (x - x[::-1]) / 2
198
+
199
+ w *= mu0 / w.sum()
200
+
201
+ if mu:
202
+ return x, w, mu0
203
+ else:
204
+ return x, w
205
+
206
+ # Jacobi Polynomials 1 P^(alpha,beta)_n(x)
207
+
208
+
209
+ def roots_jacobi(n, alpha, beta, mu=False):
210
+ r"""Gauss-Jacobi quadrature.
211
+
212
+ Compute the sample points and weights for Gauss-Jacobi
213
+ quadrature. The sample points are the roots of the nth degree
214
+ Jacobi polynomial, :math:`P^{\alpha, \beta}_n(x)`. These sample
215
+ points and weights correctly integrate polynomials of degree
216
+ :math:`2n - 1` or less over the interval :math:`[-1, 1]` with
217
+ weight function :math:`w(x) = (1 - x)^{\alpha} (1 +
218
+ x)^{\beta}`. See 22.2.1 in [AS]_ for details.
219
+
220
+ Parameters
221
+ ----------
222
+ n : int
223
+ quadrature order
224
+ alpha : float
225
+ alpha must be > -1
226
+ beta : float
227
+ beta must be > -1
228
+ mu : bool, optional
229
+ If True, return the sum of the weights, optional.
230
+
231
+ Returns
232
+ -------
233
+ x : ndarray
234
+ Sample points
235
+ w : ndarray
236
+ Weights
237
+ mu : float
238
+ Sum of the weights
239
+
240
+ See Also
241
+ --------
242
+ scipy.integrate.quadrature
243
+ scipy.integrate.fixed_quad
244
+
245
+ References
246
+ ----------
247
+ .. [AS] Milton Abramowitz and Irene A. Stegun, eds.
248
+ Handbook of Mathematical Functions with Formulas,
249
+ Graphs, and Mathematical Tables. New York: Dover, 1972.
250
+
251
+ """
252
+ m = int(n)
253
+ if n < 1 or n != m:
254
+ raise ValueError("n must be a positive integer.")
255
+ if alpha <= -1 or beta <= -1:
256
+ raise ValueError("alpha and beta must be greater than -1.")
257
+
258
+ if alpha == 0.0 and beta == 0.0:
259
+ return roots_legendre(m, mu)
260
+ if alpha == beta:
261
+ return roots_gegenbauer(m, alpha+0.5, mu)
262
+
263
+ if (alpha + beta) <= 1000:
264
+ mu0 = 2.0**(alpha+beta+1) * _ufuncs.beta(alpha+1, beta+1)
265
+ else:
266
+ # Avoid overflows in pow and beta for very large parameters
267
+ mu0 = np.exp((alpha + beta + 1) * np.log(2.0)
268
+ + _ufuncs.betaln(alpha+1, beta+1))
269
+ a = alpha
270
+ b = beta
271
+ if a + b == 0.0:
272
+ def an_func(k):
273
+ return np.where(k == 0, (b - a) / (2 + a + b), 0.0)
274
+ else:
275
+ def an_func(k):
276
+ return np.where(
277
+ k == 0,
278
+ (b - a) / (2 + a + b),
279
+ (b * b - a * a) / ((2.0 * k + a + b) * (2.0 * k + a + b + 2))
280
+ )
281
+
282
+ def bn_func(k):
283
+ return (
284
+ 2.0 / (2.0 * k + a + b)
285
+ * np.sqrt((k + a) * (k + b) / (2 * k + a + b + 1))
286
+ * np.where(k == 1, 1.0, np.sqrt(k * (k + a + b) / (2.0 * k + a + b - 1)))
287
+ )
288
+
289
+ def f(n, x):
290
+ return _ufuncs.eval_jacobi(n, a, b, x)
291
+ def df(n, x):
292
+ return 0.5 * (n + a + b + 1) * _ufuncs.eval_jacobi(n - 1, a + 1, b + 1, x)
293
+ return _gen_roots_and_weights(m, mu0, an_func, bn_func, f, df, False, mu)
294
+
295
+
296
+ def jacobi(n, alpha, beta, monic=False):
297
+ r"""Jacobi polynomial.
298
+
299
+ Defined to be the solution of
300
+
301
+ .. math::
302
+ (1 - x^2)\frac{d^2}{dx^2}P_n^{(\alpha, \beta)}
303
+ + (\beta - \alpha - (\alpha + \beta + 2)x)
304
+ \frac{d}{dx}P_n^{(\alpha, \beta)}
305
+ + n(n + \alpha + \beta + 1)P_n^{(\alpha, \beta)} = 0
306
+
307
+ for :math:`\alpha, \beta > -1`; :math:`P_n^{(\alpha, \beta)}` is a
308
+ polynomial of degree :math:`n`.
309
+
310
+ Parameters
311
+ ----------
312
+ n : int
313
+ Degree of the polynomial.
314
+ alpha : float
315
+ Parameter, must be greater than -1.
316
+ beta : float
317
+ Parameter, must be greater than -1.
318
+ monic : bool, optional
319
+ If `True`, scale the leading coefficient to be 1. Default is
320
+ `False`.
321
+
322
+ Returns
323
+ -------
324
+ P : orthopoly1d
325
+ Jacobi polynomial.
326
+
327
+ Notes
328
+ -----
329
+ For fixed :math:`\alpha, \beta`, the polynomials
330
+ :math:`P_n^{(\alpha, \beta)}` are orthogonal over :math:`[-1, 1]`
331
+ with weight function :math:`(1 - x)^\alpha(1 + x)^\beta`.
332
+
333
+ References
334
+ ----------
335
+ .. [AS] Milton Abramowitz and Irene A. Stegun, eds.
336
+ Handbook of Mathematical Functions with Formulas,
337
+ Graphs, and Mathematical Tables. New York: Dover, 1972.
338
+
339
+ Examples
340
+ --------
341
+ The Jacobi polynomials satisfy the recurrence relation:
342
+
343
+ .. math::
344
+ P_n^{(\alpha, \beta-1)}(x) - P_n^{(\alpha-1, \beta)}(x)
345
+ = P_{n-1}^{(\alpha, \beta)}(x)
346
+
347
+ This can be verified, for example, for :math:`\alpha = \beta = 2`
348
+ and :math:`n = 1` over the interval :math:`[-1, 1]`:
349
+
350
+ >>> import numpy as np
351
+ >>> from scipy.special import jacobi
352
+ >>> x = np.arange(-1.0, 1.0, 0.01)
353
+ >>> np.allclose(jacobi(0, 2, 2)(x),
354
+ ... jacobi(1, 2, 1)(x) - jacobi(1, 1, 2)(x))
355
+ True
356
+
357
+ Plot of the Jacobi polynomial :math:`P_5^{(\alpha, -0.5)}` for
358
+ different values of :math:`\alpha`:
359
+
360
+ >>> import matplotlib.pyplot as plt
361
+ >>> x = np.arange(-1.0, 1.0, 0.01)
362
+ >>> fig, ax = plt.subplots()
363
+ >>> ax.set_ylim(-2.0, 2.0)
364
+ >>> ax.set_title(r'Jacobi polynomials $P_5^{(\alpha, -0.5)}$')
365
+ >>> for alpha in np.arange(0, 4, 1):
366
+ ... ax.plot(x, jacobi(5, alpha, -0.5)(x), label=rf'$\alpha={alpha}$')
367
+ >>> plt.legend(loc='best')
368
+ >>> plt.show()
369
+
370
+ """
371
+ if n < 0:
372
+ raise ValueError("n must be nonnegative.")
373
+
374
+ def wfunc(x):
375
+ return (1 - x) ** alpha * (1 + x) ** beta
376
+ if n == 0:
377
+ return orthopoly1d([], [], 1.0, 1.0, wfunc, (-1, 1), monic,
378
+ eval_func=np.ones_like)
379
+ x, w, mu = roots_jacobi(n, alpha, beta, mu=True)
380
+ ab1 = alpha + beta + 1.0
381
+ hn = 2**ab1 / (2 * n + ab1) * _gam(n + alpha + 1)
382
+ hn *= _gam(n + beta + 1.0) / _gam(n + 1) / _gam(n + ab1)
383
+ kn = _gam(2 * n + ab1) / 2.0**n / _gam(n + 1) / _gam(n + ab1)
384
+ # here kn = coefficient on x^n term
385
+ p = orthopoly1d(x, w, hn, kn, wfunc, (-1, 1), monic,
386
+ lambda x: _ufuncs.eval_jacobi(n, alpha, beta, x))
387
+ return p
388
+
389
+ # Jacobi Polynomials shifted G_n(p,q,x)
390
+
391
+
392
+ def roots_sh_jacobi(n, p1, q1, mu=False):
393
+ """Gauss-Jacobi (shifted) quadrature.
394
+
395
+ Compute the sample points and weights for Gauss-Jacobi (shifted)
396
+ quadrature. The sample points are the roots of the nth degree
397
+ shifted Jacobi polynomial, :math:`G^{p,q}_n(x)`. These sample
398
+ points and weights correctly integrate polynomials of degree
399
+ :math:`2n - 1` or less over the interval :math:`[0, 1]` with
400
+ weight function :math:`w(x) = (1 - x)^{p-q} x^{q-1}`. See 22.2.2
401
+ in [AS]_ for details.
402
+
403
+ Parameters
404
+ ----------
405
+ n : int
406
+ quadrature order
407
+ p1 : float
408
+ (p1 - q1) must be > -1
409
+ q1 : float
410
+ q1 must be > 0
411
+ mu : bool, optional
412
+ If True, return the sum of the weights, optional.
413
+
414
+ Returns
415
+ -------
416
+ x : ndarray
417
+ Sample points
418
+ w : ndarray
419
+ Weights
420
+ mu : float
421
+ Sum of the weights
422
+
423
+ See Also
424
+ --------
425
+ scipy.integrate.quadrature
426
+ scipy.integrate.fixed_quad
427
+
428
+ References
429
+ ----------
430
+ .. [AS] Milton Abramowitz and Irene A. Stegun, eds.
431
+ Handbook of Mathematical Functions with Formulas,
432
+ Graphs, and Mathematical Tables. New York: Dover, 1972.
433
+
434
+ """
435
+ if (p1-q1) <= -1 or q1 <= 0:
436
+ message = "(p - q) must be greater than -1, and q must be greater than 0."
437
+ raise ValueError(message)
438
+ x, w, m = roots_jacobi(n, p1-q1, q1-1, True)
439
+ x = (x + 1) / 2
440
+ scale = 2.0**p1
441
+ w /= scale
442
+ m /= scale
443
+ if mu:
444
+ return x, w, m
445
+ else:
446
+ return x, w
447
+
448
+
449
+ def sh_jacobi(n, p, q, monic=False):
450
+ r"""Shifted Jacobi polynomial.
451
+
452
+ Defined by
453
+
454
+ .. math::
455
+
456
+ G_n^{(p, q)}(x)
457
+ = \binom{2n + p - 1}{n}^{-1}P_n^{(p - q, q - 1)}(2x - 1),
458
+
459
+ where :math:`P_n^{(\cdot, \cdot)}` is the nth Jacobi polynomial.
460
+
461
+ Parameters
462
+ ----------
463
+ n : int
464
+ Degree of the polynomial.
465
+ p : float
466
+ Parameter, must have :math:`p > q - 1`.
467
+ q : float
468
+ Parameter, must be greater than 0.
469
+ monic : bool, optional
470
+ If `True`, scale the leading coefficient to be 1. Default is
471
+ `False`.
472
+
473
+ Returns
474
+ -------
475
+ G : orthopoly1d
476
+ Shifted Jacobi polynomial.
477
+
478
+ Notes
479
+ -----
480
+ For fixed :math:`p, q`, the polynomials :math:`G_n^{(p, q)}` are
481
+ orthogonal over :math:`[0, 1]` with weight function :math:`(1 -
482
+ x)^{p - q}x^{q - 1}`.
483
+
484
+ """
485
+ if n < 0:
486
+ raise ValueError("n must be nonnegative.")
487
+
488
+ def wfunc(x):
489
+ return (1.0 - x) ** (p - q) * x ** (q - 1.0)
490
+ if n == 0:
491
+ return orthopoly1d([], [], 1.0, 1.0, wfunc, (-1, 1), monic,
492
+ eval_func=np.ones_like)
493
+ n1 = n
494
+ x, w = roots_sh_jacobi(n1, p, q)
495
+ hn = _gam(n + 1) * _gam(n + q) * _gam(n + p) * _gam(n + p - q + 1)
496
+ hn /= (2 * n + p) * (_gam(2 * n + p)**2)
497
+ # kn = 1.0 in standard form so monic is redundant. Kept for compatibility.
498
+ kn = 1.0
499
+ pp = orthopoly1d(x, w, hn, kn, wfunc=wfunc, limits=(0, 1), monic=monic,
500
+ eval_func=lambda x: _ufuncs.eval_sh_jacobi(n, p, q, x))
501
+ return pp
502
+
503
+ # Generalized Laguerre L^(alpha)_n(x)
504
+
505
+
506
+ def roots_genlaguerre(n, alpha, mu=False):
507
+ r"""Gauss-generalized Laguerre quadrature.
508
+
509
+ Compute the sample points and weights for Gauss-generalized
510
+ Laguerre quadrature. The sample points are the roots of the nth
511
+ degree generalized Laguerre polynomial, :math:`L^{\alpha}_n(x)`.
512
+ These sample points and weights correctly integrate polynomials of
513
+ degree :math:`2n - 1` or less over the interval :math:`[0,
514
+ \infty]` with weight function :math:`w(x) = x^{\alpha}
515
+ e^{-x}`. See 22.3.9 in [AS]_ for details.
516
+
517
+ Parameters
518
+ ----------
519
+ n : int
520
+ quadrature order
521
+ alpha : float
522
+ alpha must be > -1
523
+ mu : bool, optional
524
+ If True, return the sum of the weights, optional.
525
+
526
+ Returns
527
+ -------
528
+ x : ndarray
529
+ Sample points
530
+ w : ndarray
531
+ Weights
532
+ mu : float
533
+ Sum of the weights
534
+
535
+ See Also
536
+ --------
537
+ scipy.integrate.quadrature
538
+ scipy.integrate.fixed_quad
539
+
540
+ References
541
+ ----------
542
+ .. [AS] Milton Abramowitz and Irene A. Stegun, eds.
543
+ Handbook of Mathematical Functions with Formulas,
544
+ Graphs, and Mathematical Tables. New York: Dover, 1972.
545
+
546
+ """
547
+ m = int(n)
548
+ if n < 1 or n != m:
549
+ raise ValueError("n must be a positive integer.")
550
+ if alpha < -1:
551
+ raise ValueError("alpha must be greater than -1.")
552
+
553
+ mu0 = _ufuncs.gamma(alpha + 1)
554
+
555
+ if m == 1:
556
+ x = np.array([alpha+1.0], 'd')
557
+ w = np.array([mu0], 'd')
558
+ if mu:
559
+ return x, w, mu0
560
+ else:
561
+ return x, w
562
+
563
+ def an_func(k):
564
+ return 2 * k + alpha + 1
565
+ def bn_func(k):
566
+ return -np.sqrt(k * (k + alpha))
567
+ def f(n, x):
568
+ return _ufuncs.eval_genlaguerre(n, alpha, x)
569
+ def df(n, x):
570
+ return (n * _ufuncs.eval_genlaguerre(n, alpha, x)
571
+ - (n + alpha) * _ufuncs.eval_genlaguerre(n - 1, alpha, x)) / x
572
+ return _gen_roots_and_weights(m, mu0, an_func, bn_func, f, df, False, mu)
573
+
574
+
575
+ def genlaguerre(n, alpha, monic=False):
576
+ r"""Generalized (associated) Laguerre polynomial.
577
+
578
+ Defined to be the solution of
579
+
580
+ .. math::
581
+ x\frac{d^2}{dx^2}L_n^{(\alpha)}
582
+ + (\alpha + 1 - x)\frac{d}{dx}L_n^{(\alpha)}
583
+ + nL_n^{(\alpha)} = 0,
584
+
585
+ where :math:`\alpha > -1`; :math:`L_n^{(\alpha)}` is a polynomial
586
+ of degree :math:`n`.
587
+
588
+ Parameters
589
+ ----------
590
+ n : int
591
+ Degree of the polynomial.
592
+ alpha : float
593
+ Parameter, must be greater than -1.
594
+ monic : bool, optional
595
+ If `True`, scale the leading coefficient to be 1. Default is
596
+ `False`.
597
+
598
+ Returns
599
+ -------
600
+ L : orthopoly1d
601
+ Generalized Laguerre polynomial.
602
+
603
+ See Also
604
+ --------
605
+ laguerre : Laguerre polynomial.
606
+ hyp1f1 : confluent hypergeometric function
607
+
608
+ Notes
609
+ -----
610
+ For fixed :math:`\alpha`, the polynomials :math:`L_n^{(\alpha)}`
611
+ are orthogonal over :math:`[0, \infty)` with weight function
612
+ :math:`e^{-x}x^\alpha`.
613
+
614
+ The Laguerre polynomials are the special case where :math:`\alpha
615
+ = 0`.
616
+
617
+ References
618
+ ----------
619
+ .. [AS] Milton Abramowitz and Irene A. Stegun, eds.
620
+ Handbook of Mathematical Functions with Formulas,
621
+ Graphs, and Mathematical Tables. New York: Dover, 1972.
622
+
623
+ Examples
624
+ --------
625
+ The generalized Laguerre polynomials are closely related to the confluent
626
+ hypergeometric function :math:`{}_1F_1`:
627
+
628
+ .. math::
629
+ L_n^{(\alpha)} = \binom{n + \alpha}{n} {}_1F_1(-n, \alpha +1, x)
630
+
631
+ This can be verified, for example, for :math:`n = \alpha = 3` over the
632
+ interval :math:`[-1, 1]`:
633
+
634
+ >>> import numpy as np
635
+ >>> from scipy.special import binom
636
+ >>> from scipy.special import genlaguerre
637
+ >>> from scipy.special import hyp1f1
638
+ >>> x = np.arange(-1.0, 1.0, 0.01)
639
+ >>> np.allclose(genlaguerre(3, 3)(x), binom(6, 3) * hyp1f1(-3, 4, x))
640
+ True
641
+
642
+ This is the plot of the generalized Laguerre polynomials
643
+ :math:`L_3^{(\alpha)}` for some values of :math:`\alpha`:
644
+
645
+ >>> import matplotlib.pyplot as plt
646
+ >>> x = np.arange(-4.0, 12.0, 0.01)
647
+ >>> fig, ax = plt.subplots()
648
+ >>> ax.set_ylim(-5.0, 10.0)
649
+ >>> ax.set_title(r'Generalized Laguerre polynomials $L_3^{\alpha}$')
650
+ >>> for alpha in np.arange(0, 5):
651
+ ... ax.plot(x, genlaguerre(3, alpha)(x), label=rf'$L_3^{(alpha)}$')
652
+ >>> plt.legend(loc='best')
653
+ >>> plt.show()
654
+
655
+ """
656
+ if alpha <= -1:
657
+ raise ValueError("alpha must be > -1")
658
+ if n < 0:
659
+ raise ValueError("n must be nonnegative.")
660
+
661
+ if n == 0:
662
+ n1 = n + 1
663
+ else:
664
+ n1 = n
665
+ x, w = roots_genlaguerre(n1, alpha)
666
+ def wfunc(x):
667
+ return exp(-x) * x ** alpha
668
+ if n == 0:
669
+ x, w = [], []
670
+ hn = _gam(n + alpha + 1) / _gam(n + 1)
671
+ kn = (-1)**n / _gam(n + 1)
672
+ p = orthopoly1d(x, w, hn, kn, wfunc, (0, inf), monic,
673
+ lambda x: _ufuncs.eval_genlaguerre(n, alpha, x))
674
+ return p
675
+
676
+ # Laguerre L_n(x)
677
+
678
+
679
+ def roots_laguerre(n, mu=False):
680
+ r"""Gauss-Laguerre quadrature.
681
+
682
+ Compute the sample points and weights for Gauss-Laguerre
683
+ quadrature. The sample points are the roots of the nth degree
684
+ Laguerre polynomial, :math:`L_n(x)`. These sample points and
685
+ weights correctly integrate polynomials of degree :math:`2n - 1`
686
+ or less over the interval :math:`[0, \infty]` with weight function
687
+ :math:`w(x) = e^{-x}`. See 22.2.13 in [AS]_ for details.
688
+
689
+ Parameters
690
+ ----------
691
+ n : int
692
+ quadrature order
693
+ mu : bool, optional
694
+ If True, return the sum of the weights, optional.
695
+
696
+ Returns
697
+ -------
698
+ x : ndarray
699
+ Sample points
700
+ w : ndarray
701
+ Weights
702
+ mu : float
703
+ Sum of the weights
704
+
705
+ See Also
706
+ --------
707
+ scipy.integrate.quadrature
708
+ scipy.integrate.fixed_quad
709
+ numpy.polynomial.laguerre.laggauss
710
+
711
+ References
712
+ ----------
713
+ .. [AS] Milton Abramowitz and Irene A. Stegun, eds.
714
+ Handbook of Mathematical Functions with Formulas,
715
+ Graphs, and Mathematical Tables. New York: Dover, 1972.
716
+
717
+ """
718
+ return roots_genlaguerre(n, 0.0, mu=mu)
719
+
720
+
721
+ def laguerre(n, monic=False):
722
+ r"""Laguerre polynomial.
723
+
724
+ Defined to be the solution of
725
+
726
+ .. math::
727
+ x\frac{d^2}{dx^2}L_n + (1 - x)\frac{d}{dx}L_n + nL_n = 0;
728
+
729
+ :math:`L_n` is a polynomial of degree :math:`n`.
730
+
731
+ Parameters
732
+ ----------
733
+ n : int
734
+ Degree of the polynomial.
735
+ monic : bool, optional
736
+ If `True`, scale the leading coefficient to be 1. Default is
737
+ `False`.
738
+
739
+ Returns
740
+ -------
741
+ L : orthopoly1d
742
+ Laguerre Polynomial.
743
+
744
+ See Also
745
+ --------
746
+ genlaguerre : Generalized (associated) Laguerre polynomial.
747
+
748
+ Notes
749
+ -----
750
+ The polynomials :math:`L_n` are orthogonal over :math:`[0,
751
+ \infty)` with weight function :math:`e^{-x}`.
752
+
753
+ References
754
+ ----------
755
+ .. [AS] Milton Abramowitz and Irene A. Stegun, eds.
756
+ Handbook of Mathematical Functions with Formulas,
757
+ Graphs, and Mathematical Tables. New York: Dover, 1972.
758
+
759
+ Examples
760
+ --------
761
+ The Laguerre polynomials :math:`L_n` are the special case
762
+ :math:`\alpha = 0` of the generalized Laguerre polynomials
763
+ :math:`L_n^{(\alpha)}`.
764
+ Let's verify it on the interval :math:`[-1, 1]`:
765
+
766
+ >>> import numpy as np
767
+ >>> from scipy.special import genlaguerre
768
+ >>> from scipy.special import laguerre
769
+ >>> x = np.arange(-1.0, 1.0, 0.01)
770
+ >>> np.allclose(genlaguerre(3, 0)(x), laguerre(3)(x))
771
+ True
772
+
773
+ The polynomials :math:`L_n` also satisfy the recurrence relation:
774
+
775
+ .. math::
776
+ (n + 1)L_{n+1}(x) = (2n +1 -x)L_n(x) - nL_{n-1}(x)
777
+
778
+ This can be easily checked on :math:`[0, 1]` for :math:`n = 3`:
779
+
780
+ >>> x = np.arange(0.0, 1.0, 0.01)
781
+ >>> np.allclose(4 * laguerre(4)(x),
782
+ ... (7 - x) * laguerre(3)(x) - 3 * laguerre(2)(x))
783
+ True
784
+
785
+ This is the plot of the first few Laguerre polynomials :math:`L_n`:
786
+
787
+ >>> import matplotlib.pyplot as plt
788
+ >>> x = np.arange(-1.0, 5.0, 0.01)
789
+ >>> fig, ax = plt.subplots()
790
+ >>> ax.set_ylim(-5.0, 5.0)
791
+ >>> ax.set_title(r'Laguerre polynomials $L_n$')
792
+ >>> for n in np.arange(0, 5):
793
+ ... ax.plot(x, laguerre(n)(x), label=rf'$L_{n}$')
794
+ >>> plt.legend(loc='best')
795
+ >>> plt.show()
796
+
797
+ """
798
+ if n < 0:
799
+ raise ValueError("n must be nonnegative.")
800
+
801
+ if n == 0:
802
+ n1 = n + 1
803
+ else:
804
+ n1 = n
805
+ x, w = roots_laguerre(n1)
806
+ if n == 0:
807
+ x, w = [], []
808
+ hn = 1.0
809
+ kn = (-1)**n / _gam(n + 1)
810
+ p = orthopoly1d(x, w, hn, kn, lambda x: exp(-x), (0, inf), monic,
811
+ lambda x: _ufuncs.eval_laguerre(n, x))
812
+ return p
813
+
814
+ # Hermite 1 H_n(x)
815
+
816
+
817
+ def roots_hermite(n, mu=False):
818
+ r"""Gauss-Hermite (physicist's) quadrature.
819
+
820
+ Compute the sample points and weights for Gauss-Hermite
821
+ quadrature. The sample points are the roots of the nth degree
822
+ Hermite polynomial, :math:`H_n(x)`. These sample points and
823
+ weights correctly integrate polynomials of degree :math:`2n - 1`
824
+ or less over the interval :math:`[-\infty, \infty]` with weight
825
+ function :math:`w(x) = e^{-x^2}`. See 22.2.14 in [AS]_ for
826
+ details.
827
+
828
+ Parameters
829
+ ----------
830
+ n : int
831
+ quadrature order
832
+ mu : bool, optional
833
+ If True, return the sum of the weights, optional.
834
+
835
+ Returns
836
+ -------
837
+ x : ndarray
838
+ Sample points
839
+ w : ndarray
840
+ Weights
841
+ mu : float
842
+ Sum of the weights
843
+
844
+ See Also
845
+ --------
846
+ scipy.integrate.quadrature
847
+ scipy.integrate.fixed_quad
848
+ numpy.polynomial.hermite.hermgauss
849
+ roots_hermitenorm
850
+
851
+ Notes
852
+ -----
853
+ For small n up to 150 a modified version of the Golub-Welsch
854
+ algorithm is used. Nodes are computed from the eigenvalue
855
+ problem and improved by one step of a Newton iteration.
856
+ The weights are computed from the well-known analytical formula.
857
+
858
+ For n larger than 150 an optimal asymptotic algorithm is applied
859
+ which computes nodes and weights in a numerically stable manner.
860
+ The algorithm has linear runtime making computation for very
861
+ large n (several thousand or more) feasible.
862
+
863
+ References
864
+ ----------
865
+ .. [townsend.trogdon.olver-2014]
866
+ Townsend, A. and Trogdon, T. and Olver, S. (2014)
867
+ *Fast computation of Gauss quadrature nodes and
868
+ weights on the whole real line*. :arXiv:`1410.5286`.
869
+ .. [townsend.trogdon.olver-2015]
870
+ Townsend, A. and Trogdon, T. and Olver, S. (2015)
871
+ *Fast computation of Gauss quadrature nodes and
872
+ weights on the whole real line*.
873
+ IMA Journal of Numerical Analysis
874
+ :doi:`10.1093/imanum/drv002`.
875
+ .. [AS] Milton Abramowitz and Irene A. Stegun, eds.
876
+ Handbook of Mathematical Functions with Formulas,
877
+ Graphs, and Mathematical Tables. New York: Dover, 1972.
878
+
879
+ """
880
+ m = int(n)
881
+ if n < 1 or n != m:
882
+ raise ValueError("n must be a positive integer.")
883
+
884
+ mu0 = np.sqrt(np.pi)
885
+ if n <= 150:
886
+ def an_func(k):
887
+ return 0.0 * k
888
+ def bn_func(k):
889
+ return np.sqrt(k / 2.0)
890
+ f = _ufuncs.eval_hermite
891
+ def df(n, x):
892
+ return 2.0 * n * _ufuncs.eval_hermite(n - 1, x)
893
+ return _gen_roots_and_weights(m, mu0, an_func, bn_func, f, df, True, mu)
894
+ else:
895
+ nodes, weights = _roots_hermite_asy(m)
896
+ if mu:
897
+ return nodes, weights, mu0
898
+ else:
899
+ return nodes, weights
900
+
901
+
902
+ def _compute_tauk(n, k, maxit=5):
903
+ """Helper function for Tricomi initial guesses
904
+
905
+ For details, see formula 3.1 in lemma 3.1 in the
906
+ original paper.
907
+
908
+ Parameters
909
+ ----------
910
+ n : int
911
+ Quadrature order
912
+ k : ndarray of type int
913
+ Index of roots :math:`\tau_k` to compute
914
+ maxit : int
915
+ Number of Newton maxit performed, the default
916
+ value of 5 is sufficient.
917
+
918
+ Returns
919
+ -------
920
+ tauk : ndarray
921
+ Roots of equation 3.1
922
+
923
+ See Also
924
+ --------
925
+ initial_nodes_a
926
+ roots_hermite_asy
927
+ """
928
+ a = n % 2 - 0.5
929
+ c = (4.0*floor(n/2.0) - 4.0*k + 3.0)*pi / (4.0*floor(n/2.0) + 2.0*a + 2.0)
930
+ def f(x):
931
+ return x - sin(x) - c
932
+ def df(x):
933
+ return 1.0 - cos(x)
934
+ xi = 0.5*pi
935
+ for i in range(maxit):
936
+ xi = xi - f(xi)/df(xi)
937
+ return xi
938
+
939
+
940
+ def _initial_nodes_a(n, k):
941
+ r"""Tricomi initial guesses
942
+
943
+ Computes an initial approximation to the square of the `k`-th
944
+ (positive) root :math:`x_k` of the Hermite polynomial :math:`H_n`
945
+ of order :math:`n`. The formula is the one from lemma 3.1 in the
946
+ original paper. The guesses are accurate except in the region
947
+ near :math:`\sqrt{2n + 1}`.
948
+
949
+ Parameters
950
+ ----------
951
+ n : int
952
+ Quadrature order
953
+ k : ndarray of type int
954
+ Index of roots to compute
955
+
956
+ Returns
957
+ -------
958
+ xksq : ndarray
959
+ Square of the approximate roots
960
+
961
+ See Also
962
+ --------
963
+ initial_nodes
964
+ roots_hermite_asy
965
+ """
966
+ tauk = _compute_tauk(n, k)
967
+ sigk = cos(0.5*tauk)**2
968
+ a = n % 2 - 0.5
969
+ nu = 4.0*floor(n/2.0) + 2.0*a + 2.0
970
+ # Initial approximation of Hermite roots (square)
971
+ xksq = nu*sigk - 1.0/(3.0*nu) * (5.0/(4.0*(1.0-sigk)**2) - 1.0/(1.0-sigk) - 0.25)
972
+ return xksq
973
+
974
+
975
+ def _initial_nodes_b(n, k):
976
+ r"""Gatteschi initial guesses
977
+
978
+ Computes an initial approximation to the square of the kth
979
+ (positive) root :math:`x_k` of the Hermite polynomial :math:`H_n`
980
+ of order :math:`n`. The formula is the one from lemma 3.2 in the
981
+ original paper. The guesses are accurate in the region just
982
+ below :math:`\sqrt{2n + 1}`.
983
+
984
+ Parameters
985
+ ----------
986
+ n : int
987
+ Quadrature order
988
+ k : ndarray of type int
989
+ Index of roots to compute
990
+
991
+ Returns
992
+ -------
993
+ xksq : ndarray
994
+ Square of the approximate root
995
+
996
+ See Also
997
+ --------
998
+ initial_nodes
999
+ roots_hermite_asy
1000
+ """
1001
+ a = n % 2 - 0.5
1002
+ nu = 4.0*floor(n/2.0) + 2.0*a + 2.0
1003
+ # Airy roots by approximation
1004
+ ak = _specfun.airyzo(k.max(), 1)[0][::-1]
1005
+ # Initial approximation of Hermite roots (square)
1006
+ xksq = (nu
1007
+ + 2.0**(2.0/3.0) * ak * nu**(1.0/3.0)
1008
+ + 1.0/5.0 * 2.0**(4.0/3.0) * ak**2 * nu**(-1.0/3.0)
1009
+ + (9.0/140.0 - 12.0/175.0 * ak**3) * nu**(-1.0)
1010
+ + (16.0/1575.0 * ak + 92.0/7875.0 * ak**4) * 2.0**(2.0/3.0) * nu**(-5.0/3.0)
1011
+ - (15152.0/3031875.0 * ak**5 + 1088.0/121275.0 * ak**2)
1012
+ * 2.0**(1.0/3.0) * nu**(-7.0/3.0))
1013
+ return xksq
1014
+
1015
+
1016
+ def _initial_nodes(n):
1017
+ """Initial guesses for the Hermite roots
1018
+
1019
+ Computes an initial approximation to the non-negative
1020
+ roots :math:`x_k` of the Hermite polynomial :math:`H_n`
1021
+ of order :math:`n`. The Tricomi and Gatteschi initial
1022
+ guesses are used in the region where they are accurate.
1023
+
1024
+ Parameters
1025
+ ----------
1026
+ n : int
1027
+ Quadrature order
1028
+
1029
+ Returns
1030
+ -------
1031
+ xk : ndarray
1032
+ Approximate roots
1033
+
1034
+ See Also
1035
+ --------
1036
+ roots_hermite_asy
1037
+ """
1038
+ # Turnover point
1039
+ # linear polynomial fit to error of 10, 25, 40, ..., 1000 point rules
1040
+ fit = 0.49082003*n - 4.37859653
1041
+ turnover = around(fit).astype(int)
1042
+ # Compute all approximations
1043
+ ia = arange(1, int(floor(n*0.5)+1))
1044
+ ib = ia[::-1]
1045
+ xasq = _initial_nodes_a(n, ia[:turnover+1])
1046
+ xbsq = _initial_nodes_b(n, ib[turnover+1:])
1047
+ # Combine
1048
+ iv = sqrt(hstack([xasq, xbsq]))
1049
+ # Central node is always zero
1050
+ if n % 2 == 1:
1051
+ iv = hstack([0.0, iv])
1052
+ return iv
1053
+
1054
+
1055
+ def _pbcf(n, theta):
1056
+ r"""Asymptotic series expansion of parabolic cylinder function
1057
+
1058
+ The implementation is based on sections 3.2 and 3.3 from the
1059
+ original paper. Compared to the published version this code
1060
+ adds one more term to the asymptotic series. The detailed
1061
+ formulas can be found at [parabolic-asymptotics]_. The evaluation
1062
+ is done in a transformed variable :math:`\theta := \arccos(t)`
1063
+ where :math:`t := x / \mu` and :math:`\mu := \sqrt{2n + 1}`.
1064
+
1065
+ Parameters
1066
+ ----------
1067
+ n : int
1068
+ Quadrature order
1069
+ theta : ndarray
1070
+ Transformed position variable
1071
+
1072
+ Returns
1073
+ -------
1074
+ U : ndarray
1075
+ Value of the parabolic cylinder function :math:`U(a, \theta)`.
1076
+ Ud : ndarray
1077
+ Value of the derivative :math:`U^{\prime}(a, \theta)` of
1078
+ the parabolic cylinder function.
1079
+
1080
+ See Also
1081
+ --------
1082
+ roots_hermite_asy
1083
+
1084
+ References
1085
+ ----------
1086
+ .. [parabolic-asymptotics]
1087
+ https://dlmf.nist.gov/12.10#vii
1088
+ """
1089
+ st = sin(theta)
1090
+ ct = cos(theta)
1091
+ # https://dlmf.nist.gov/12.10#vii
1092
+ mu = 2.0*n + 1.0
1093
+ # https://dlmf.nist.gov/12.10#E23
1094
+ eta = 0.5*theta - 0.5*st*ct
1095
+ # https://dlmf.nist.gov/12.10#E39
1096
+ zeta = -(3.0*eta/2.0) ** (2.0/3.0)
1097
+ # https://dlmf.nist.gov/12.10#E40
1098
+ phi = (-zeta / st**2) ** (0.25)
1099
+ # Coefficients
1100
+ # https://dlmf.nist.gov/12.10#E43
1101
+ a0 = 1.0
1102
+ a1 = 0.10416666666666666667
1103
+ a2 = 0.08355034722222222222
1104
+ a3 = 0.12822657455632716049
1105
+ a4 = 0.29184902646414046425
1106
+ a5 = 0.88162726744375765242
1107
+ b0 = 1.0
1108
+ b1 = -0.14583333333333333333
1109
+ b2 = -0.09874131944444444444
1110
+ b3 = -0.14331205391589506173
1111
+ b4 = -0.31722720267841354810
1112
+ b5 = -0.94242914795712024914
1113
+ # Polynomials
1114
+ # https://dlmf.nist.gov/12.10#E9
1115
+ # https://dlmf.nist.gov/12.10#E10
1116
+ ctp = ct ** arange(16).reshape((-1,1))
1117
+ u0 = 1.0
1118
+ u1 = (1.0*ctp[3,:] - 6.0*ct) / 24.0
1119
+ u2 = (-9.0*ctp[4,:] + 249.0*ctp[2,:] + 145.0) / 1152.0
1120
+ u3 = (-4042.0*ctp[9,:] + 18189.0*ctp[7,:] - 28287.0*ctp[5,:]
1121
+ - 151995.0*ctp[3,:] - 259290.0*ct) / 414720.0
1122
+ u4 = (72756.0*ctp[10,:] - 321339.0*ctp[8,:] - 154982.0*ctp[6,:]
1123
+ + 50938215.0*ctp[4,:] + 122602962.0*ctp[2,:] + 12773113.0) / 39813120.0
1124
+ u5 = (82393456.0*ctp[15,:] - 617950920.0*ctp[13,:] + 1994971575.0*ctp[11,:]
1125
+ - 3630137104.0*ctp[9,:] + 4433574213.0*ctp[7,:] - 37370295816.0*ctp[5,:]
1126
+ - 119582875013.0*ctp[3,:] - 34009066266.0*ct) / 6688604160.0
1127
+ v0 = 1.0
1128
+ v1 = (1.0*ctp[3,:] + 6.0*ct) / 24.0
1129
+ v2 = (15.0*ctp[4,:] - 327.0*ctp[2,:] - 143.0) / 1152.0
1130
+ v3 = (-4042.0*ctp[9,:] + 18189.0*ctp[7,:] - 36387.0*ctp[5,:]
1131
+ + 238425.0*ctp[3,:] + 259290.0*ct) / 414720.0
1132
+ v4 = (-121260.0*ctp[10,:] + 551733.0*ctp[8,:] - 151958.0*ctp[6,:]
1133
+ - 57484425.0*ctp[4,:] - 132752238.0*ctp[2,:] - 12118727) / 39813120.0
1134
+ v5 = (82393456.0*ctp[15,:] - 617950920.0*ctp[13,:] + 2025529095.0*ctp[11,:]
1135
+ - 3750839308.0*ctp[9,:] + 3832454253.0*ctp[7,:] + 35213253348.0*ctp[5,:]
1136
+ + 130919230435.0*ctp[3,:] + 34009066266*ct) / 6688604160.0
1137
+ # Airy Evaluation (Bi and Bip unused)
1138
+ Ai, Aip, Bi, Bip = airy(mu**(4.0/6.0) * zeta)
1139
+ # Prefactor for U
1140
+ P = 2.0*sqrt(pi) * mu**(1.0/6.0) * phi
1141
+ # Terms for U
1142
+ # https://dlmf.nist.gov/12.10#E42
1143
+ phip = phi ** arange(6, 31, 6).reshape((-1,1))
1144
+ A0 = b0*u0
1145
+ A1 = (b2*u0 + phip[0,:]*b1*u1 + phip[1,:]*b0*u2) / zeta**3
1146
+ A2 = (b4*u0 + phip[0,:]*b3*u1 + phip[1,:]*b2*u2 + phip[2,:]*b1*u3
1147
+ + phip[3,:]*b0*u4) / zeta**6
1148
+ B0 = -(a1*u0 + phip[0,:]*a0*u1) / zeta**2
1149
+ B1 = -(a3*u0 + phip[0,:]*a2*u1 + phip[1,:]*a1*u2 + phip[2,:]*a0*u3) / zeta**5
1150
+ B2 = -(a5*u0 + phip[0,:]*a4*u1 + phip[1,:]*a3*u2 + phip[2,:]*a2*u3
1151
+ + phip[3,:]*a1*u4 + phip[4,:]*a0*u5) / zeta**8
1152
+ # U
1153
+ # https://dlmf.nist.gov/12.10#E35
1154
+ U = P * (Ai * (A0 + A1/mu**2.0 + A2/mu**4.0) +
1155
+ Aip * (B0 + B1/mu**2.0 + B2/mu**4.0) / mu**(8.0/6.0))
1156
+ # Prefactor for derivative of U
1157
+ Pd = sqrt(2.0*pi) * mu**(2.0/6.0) / phi
1158
+ # Terms for derivative of U
1159
+ # https://dlmf.nist.gov/12.10#E46
1160
+ C0 = -(b1*v0 + phip[0,:]*b0*v1) / zeta
1161
+ C1 = -(b3*v0 + phip[0,:]*b2*v1 + phip[1,:]*b1*v2 + phip[2,:]*b0*v3) / zeta**4
1162
+ C2 = -(b5*v0 + phip[0,:]*b4*v1 + phip[1,:]*b3*v2 + phip[2,:]*b2*v3
1163
+ + phip[3,:]*b1*v4 + phip[4,:]*b0*v5) / zeta**7
1164
+ D0 = a0*v0
1165
+ D1 = (a2*v0 + phip[0,:]*a1*v1 + phip[1,:]*a0*v2) / zeta**3
1166
+ D2 = (a4*v0 + phip[0,:]*a3*v1 + phip[1,:]*a2*v2 + phip[2,:]*a1*v3
1167
+ + phip[3,:]*a0*v4) / zeta**6
1168
+ # Derivative of U
1169
+ # https://dlmf.nist.gov/12.10#E36
1170
+ Ud = Pd * (Ai * (C0 + C1/mu**2.0 + C2/mu**4.0) / mu**(4.0/6.0) +
1171
+ Aip * (D0 + D1/mu**2.0 + D2/mu**4.0))
1172
+ return U, Ud
1173
+
1174
+
1175
+ def _newton(n, x_initial, maxit=5):
1176
+ """Newton iteration for polishing the asymptotic approximation
1177
+ to the zeros of the Hermite polynomials.
1178
+
1179
+ Parameters
1180
+ ----------
1181
+ n : int
1182
+ Quadrature order
1183
+ x_initial : ndarray
1184
+ Initial guesses for the roots
1185
+ maxit : int
1186
+ Maximal number of Newton iterations.
1187
+ The default 5 is sufficient, usually
1188
+ only one or two steps are needed.
1189
+
1190
+ Returns
1191
+ -------
1192
+ nodes : ndarray
1193
+ Quadrature nodes
1194
+ weights : ndarray
1195
+ Quadrature weights
1196
+
1197
+ See Also
1198
+ --------
1199
+ roots_hermite_asy
1200
+ """
1201
+ # Variable transformation
1202
+ mu = sqrt(2.0*n + 1.0)
1203
+ t = x_initial / mu
1204
+ theta = arccos(t)
1205
+ # Newton iteration
1206
+ for i in range(maxit):
1207
+ u, ud = _pbcf(n, theta)
1208
+ dtheta = u / (sqrt(2.0) * mu * sin(theta) * ud)
1209
+ theta = theta + dtheta
1210
+ if max(abs(dtheta)) < 1e-14:
1211
+ break
1212
+ # Undo variable transformation
1213
+ x = mu * cos(theta)
1214
+ # Central node is always zero
1215
+ if n % 2 == 1:
1216
+ x[0] = 0.0
1217
+ # Compute weights
1218
+ w = exp(-x**2) / (2.0*ud**2)
1219
+ return x, w
1220
+
1221
+
1222
+ def _roots_hermite_asy(n):
1223
+ r"""Gauss-Hermite (physicist's) quadrature for large n.
1224
+
1225
+ Computes the sample points and weights for Gauss-Hermite quadrature.
1226
+ The sample points are the roots of the nth degree Hermite polynomial,
1227
+ :math:`H_n(x)`. These sample points and weights correctly integrate
1228
+ polynomials of degree :math:`2n - 1` or less over the interval
1229
+ :math:`[-\infty, \infty]` with weight function :math:`f(x) = e^{-x^2}`.
1230
+
1231
+ This method relies on asymptotic expansions which work best for n > 150.
1232
+ The algorithm has linear runtime making computation for very large n
1233
+ feasible.
1234
+
1235
+ Parameters
1236
+ ----------
1237
+ n : int
1238
+ quadrature order
1239
+
1240
+ Returns
1241
+ -------
1242
+ nodes : ndarray
1243
+ Quadrature nodes
1244
+ weights : ndarray
1245
+ Quadrature weights
1246
+
1247
+ See Also
1248
+ --------
1249
+ roots_hermite
1250
+
1251
+ References
1252
+ ----------
1253
+ .. [townsend.trogdon.olver-2014]
1254
+ Townsend, A. and Trogdon, T. and Olver, S. (2014)
1255
+ *Fast computation of Gauss quadrature nodes and
1256
+ weights on the whole real line*. :arXiv:`1410.5286`.
1257
+
1258
+ .. [townsend.trogdon.olver-2015]
1259
+ Townsend, A. and Trogdon, T. and Olver, S. (2015)
1260
+ *Fast computation of Gauss quadrature nodes and
1261
+ weights on the whole real line*.
1262
+ IMA Journal of Numerical Analysis
1263
+ :doi:`10.1093/imanum/drv002`.
1264
+ """
1265
+ iv = _initial_nodes(n)
1266
+ nodes, weights = _newton(n, iv)
1267
+ # Combine with negative parts
1268
+ if n % 2 == 0:
1269
+ nodes = hstack([-nodes[::-1], nodes])
1270
+ weights = hstack([weights[::-1], weights])
1271
+ else:
1272
+ nodes = hstack([-nodes[-1:0:-1], nodes])
1273
+ weights = hstack([weights[-1:0:-1], weights])
1274
+ # Scale weights
1275
+ weights *= sqrt(pi) / sum(weights)
1276
+ return nodes, weights
1277
+
1278
+
1279
+ def hermite(n, monic=False):
1280
+ r"""Physicist's Hermite polynomial.
1281
+
1282
+ Defined by
1283
+
1284
+ .. math::
1285
+
1286
+ H_n(x) = (-1)^ne^{x^2}\frac{d^n}{dx^n}e^{-x^2};
1287
+
1288
+ :math:`H_n` is a polynomial of degree :math:`n`.
1289
+
1290
+ Parameters
1291
+ ----------
1292
+ n : int
1293
+ Degree of the polynomial.
1294
+ monic : bool, optional
1295
+ If `True`, scale the leading coefficient to be 1. Default is
1296
+ `False`.
1297
+
1298
+ Returns
1299
+ -------
1300
+ H : orthopoly1d
1301
+ Hermite polynomial.
1302
+
1303
+ Notes
1304
+ -----
1305
+ The polynomials :math:`H_n` are orthogonal over :math:`(-\infty,
1306
+ \infty)` with weight function :math:`e^{-x^2}`.
1307
+
1308
+ Examples
1309
+ --------
1310
+ >>> from scipy import special
1311
+ >>> import matplotlib.pyplot as plt
1312
+ >>> import numpy as np
1313
+
1314
+ >>> p_monic = special.hermite(3, monic=True)
1315
+ >>> p_monic
1316
+ poly1d([ 1. , 0. , -1.5, 0. ])
1317
+ >>> p_monic(1)
1318
+ -0.49999999999999983
1319
+ >>> x = np.linspace(-3, 3, 400)
1320
+ >>> y = p_monic(x)
1321
+ >>> plt.plot(x, y)
1322
+ >>> plt.title("Monic Hermite polynomial of degree 3")
1323
+ >>> plt.xlabel("x")
1324
+ >>> plt.ylabel("H_3(x)")
1325
+ >>> plt.show()
1326
+
1327
+ """
1328
+ if n < 0:
1329
+ raise ValueError("n must be nonnegative.")
1330
+
1331
+ if n == 0:
1332
+ n1 = n + 1
1333
+ else:
1334
+ n1 = n
1335
+ x, w = roots_hermite(n1)
1336
+ def wfunc(x):
1337
+ return exp(-x * x)
1338
+ if n == 0:
1339
+ x, w = [], []
1340
+ hn = 2**n * _gam(n + 1) * sqrt(pi)
1341
+ kn = 2**n
1342
+ p = orthopoly1d(x, w, hn, kn, wfunc, (-inf, inf), monic,
1343
+ lambda x: _ufuncs.eval_hermite(n, x))
1344
+ return p
1345
+
1346
+ # Hermite 2 He_n(x)
1347
+
1348
+
1349
+ def roots_hermitenorm(n, mu=False):
1350
+ r"""Gauss-Hermite (statistician's) quadrature.
1351
+
1352
+ Compute the sample points and weights for Gauss-Hermite
1353
+ quadrature. The sample points are the roots of the nth degree
1354
+ Hermite polynomial, :math:`He_n(x)`. These sample points and
1355
+ weights correctly integrate polynomials of degree :math:`2n - 1`
1356
+ or less over the interval :math:`[-\infty, \infty]` with weight
1357
+ function :math:`w(x) = e^{-x^2/2}`. See 22.2.15 in [AS]_ for more
1358
+ details.
1359
+
1360
+ Parameters
1361
+ ----------
1362
+ n : int
1363
+ quadrature order
1364
+ mu : bool, optional
1365
+ If True, return the sum of the weights, optional.
1366
+
1367
+ Returns
1368
+ -------
1369
+ x : ndarray
1370
+ Sample points
1371
+ w : ndarray
1372
+ Weights
1373
+ mu : float
1374
+ Sum of the weights
1375
+
1376
+ See Also
1377
+ --------
1378
+ scipy.integrate.quadrature
1379
+ scipy.integrate.fixed_quad
1380
+ numpy.polynomial.hermite_e.hermegauss
1381
+
1382
+ Notes
1383
+ -----
1384
+ For small n up to 150 a modified version of the Golub-Welsch
1385
+ algorithm is used. Nodes are computed from the eigenvalue
1386
+ problem and improved by one step of a Newton iteration.
1387
+ The weights are computed from the well-known analytical formula.
1388
+
1389
+ For n larger than 150 an optimal asymptotic algorithm is used
1390
+ which computes nodes and weights in a numerical stable manner.
1391
+ The algorithm has linear runtime making computation for very
1392
+ large n (several thousand or more) feasible.
1393
+
1394
+ References
1395
+ ----------
1396
+ .. [AS] Milton Abramowitz and Irene A. Stegun, eds.
1397
+ Handbook of Mathematical Functions with Formulas,
1398
+ Graphs, and Mathematical Tables. New York: Dover, 1972.
1399
+
1400
+ """
1401
+ m = int(n)
1402
+ if n < 1 or n != m:
1403
+ raise ValueError("n must be a positive integer.")
1404
+
1405
+ mu0 = np.sqrt(2.0*np.pi)
1406
+ if n <= 150:
1407
+ def an_func(k):
1408
+ return 0.0 * k
1409
+ def bn_func(k):
1410
+ return np.sqrt(k)
1411
+ f = _ufuncs.eval_hermitenorm
1412
+ def df(n, x):
1413
+ return n * _ufuncs.eval_hermitenorm(n - 1, x)
1414
+ return _gen_roots_and_weights(m, mu0, an_func, bn_func, f, df, True, mu)
1415
+ else:
1416
+ nodes, weights = _roots_hermite_asy(m)
1417
+ # Transform
1418
+ nodes *= sqrt(2)
1419
+ weights *= sqrt(2)
1420
+ if mu:
1421
+ return nodes, weights, mu0
1422
+ else:
1423
+ return nodes, weights
1424
+
1425
+
1426
+ def hermitenorm(n, monic=False):
1427
+ r"""Normalized (probabilist's) Hermite polynomial.
1428
+
1429
+ Defined by
1430
+
1431
+ .. math::
1432
+
1433
+ He_n(x) = (-1)^ne^{x^2/2}\frac{d^n}{dx^n}e^{-x^2/2};
1434
+
1435
+ :math:`He_n` is a polynomial of degree :math:`n`.
1436
+
1437
+ Parameters
1438
+ ----------
1439
+ n : int
1440
+ Degree of the polynomial.
1441
+ monic : bool, optional
1442
+ If `True`, scale the leading coefficient to be 1. Default is
1443
+ `False`.
1444
+
1445
+ Returns
1446
+ -------
1447
+ He : orthopoly1d
1448
+ Hermite polynomial.
1449
+
1450
+ Notes
1451
+ -----
1452
+
1453
+ The polynomials :math:`He_n` are orthogonal over :math:`(-\infty,
1454
+ \infty)` with weight function :math:`e^{-x^2/2}`.
1455
+
1456
+ """
1457
+ if n < 0:
1458
+ raise ValueError("n must be nonnegative.")
1459
+
1460
+ if n == 0:
1461
+ n1 = n + 1
1462
+ else:
1463
+ n1 = n
1464
+ x, w = roots_hermitenorm(n1)
1465
+ def wfunc(x):
1466
+ return exp(-x * x / 2.0)
1467
+ if n == 0:
1468
+ x, w = [], []
1469
+ hn = sqrt(2 * pi) * _gam(n + 1)
1470
+ kn = 1.0
1471
+ p = orthopoly1d(x, w, hn, kn, wfunc=wfunc, limits=(-inf, inf), monic=monic,
1472
+ eval_func=lambda x: _ufuncs.eval_hermitenorm(n, x))
1473
+ return p
1474
+
1475
+ # The remainder of the polynomials can be derived from the ones above.
1476
+
1477
+ # Ultraspherical (Gegenbauer) C^(alpha)_n(x)
1478
+
1479
+
1480
+ def roots_gegenbauer(n, alpha, mu=False):
1481
+ r"""Gauss-Gegenbauer quadrature.
1482
+
1483
+ Compute the sample points and weights for Gauss-Gegenbauer
1484
+ quadrature. The sample points are the roots of the nth degree
1485
+ Gegenbauer polynomial, :math:`C^{\alpha}_n(x)`. These sample
1486
+ points and weights correctly integrate polynomials of degree
1487
+ :math:`2n - 1` or less over the interval :math:`[-1, 1]` with
1488
+ weight function :math:`w(x) = (1 - x^2)^{\alpha - 1/2}`. See
1489
+ 22.2.3 in [AS]_ for more details.
1490
+
1491
+ Parameters
1492
+ ----------
1493
+ n : int
1494
+ quadrature order
1495
+ alpha : float
1496
+ alpha must be > -0.5
1497
+ mu : bool, optional
1498
+ If True, return the sum of the weights, optional.
1499
+
1500
+ Returns
1501
+ -------
1502
+ x : ndarray
1503
+ Sample points
1504
+ w : ndarray
1505
+ Weights
1506
+ mu : float
1507
+ Sum of the weights
1508
+
1509
+ See Also
1510
+ --------
1511
+ scipy.integrate.quadrature
1512
+ scipy.integrate.fixed_quad
1513
+
1514
+ References
1515
+ ----------
1516
+ .. [AS] Milton Abramowitz and Irene A. Stegun, eds.
1517
+ Handbook of Mathematical Functions with Formulas,
1518
+ Graphs, and Mathematical Tables. New York: Dover, 1972.
1519
+
1520
+ """
1521
+ m = int(n)
1522
+ if n < 1 or n != m:
1523
+ raise ValueError("n must be a positive integer.")
1524
+ if alpha < -0.5:
1525
+ raise ValueError("alpha must be greater than -0.5.")
1526
+ elif alpha == 0.0:
1527
+ # C(n,0,x) == 0 uniformly, however, as alpha->0, C(n,alpha,x)->T(n,x)
1528
+ # strictly, we should just error out here, since the roots are not
1529
+ # really defined, but we used to return something useful, so let's
1530
+ # keep doing so.
1531
+ return roots_chebyt(n, mu)
1532
+
1533
+ if alpha <= 170:
1534
+ mu0 = (np.sqrt(np.pi) * _ufuncs.gamma(alpha + 0.5)) \
1535
+ / _ufuncs.gamma(alpha + 1)
1536
+ else:
1537
+ # For large alpha we use a Taylor series expansion around inf,
1538
+ # expressed as a 6th order polynomial of a^-1 and using Horner's
1539
+ # method to minimize computation and maximize precision
1540
+ inv_alpha = 1. / alpha
1541
+ coeffs = np.array([0.000207186, -0.00152206, -0.000640869,
1542
+ 0.00488281, 0.0078125, -0.125, 1.])
1543
+ mu0 = coeffs[0]
1544
+ for term in range(1, len(coeffs)):
1545
+ mu0 = mu0 * inv_alpha + coeffs[term]
1546
+ mu0 = mu0 * np.sqrt(np.pi / alpha)
1547
+ def an_func(k):
1548
+ return 0.0 * k
1549
+ def bn_func(k):
1550
+ return np.sqrt(k * (k + 2 * alpha - 1) / (4 * (k + alpha) * (k + alpha - 1)))
1551
+ def f(n, x):
1552
+ return _ufuncs.eval_gegenbauer(n, alpha, x)
1553
+ def df(n, x):
1554
+ return (
1555
+ -n * x * _ufuncs.eval_gegenbauer(n, alpha, x)
1556
+ + (n + 2 * alpha - 1) * _ufuncs.eval_gegenbauer(n - 1, alpha, x)
1557
+ ) / (1 - x ** 2)
1558
+ return _gen_roots_and_weights(m, mu0, an_func, bn_func, f, df, True, mu)
1559
+
1560
+
1561
+ def gegenbauer(n, alpha, monic=False):
1562
+ r"""Gegenbauer (ultraspherical) polynomial.
1563
+
1564
+ Defined to be the solution of
1565
+
1566
+ .. math::
1567
+ (1 - x^2)\frac{d^2}{dx^2}C_n^{(\alpha)}
1568
+ - (2\alpha + 1)x\frac{d}{dx}C_n^{(\alpha)}
1569
+ + n(n + 2\alpha)C_n^{(\alpha)} = 0
1570
+
1571
+ for :math:`\alpha > -1/2`; :math:`C_n^{(\alpha)}` is a polynomial
1572
+ of degree :math:`n`.
1573
+
1574
+ Parameters
1575
+ ----------
1576
+ n : int
1577
+ Degree of the polynomial.
1578
+ alpha : float
1579
+ Parameter, must be greater than -0.5.
1580
+ monic : bool, optional
1581
+ If `True`, scale the leading coefficient to be 1. Default is
1582
+ `False`.
1583
+
1584
+ Returns
1585
+ -------
1586
+ C : orthopoly1d
1587
+ Gegenbauer polynomial.
1588
+
1589
+ Notes
1590
+ -----
1591
+ The polynomials :math:`C_n^{(\alpha)}` are orthogonal over
1592
+ :math:`[-1,1]` with weight function :math:`(1 - x^2)^{(\alpha -
1593
+ 1/2)}`.
1594
+
1595
+ Examples
1596
+ --------
1597
+ >>> import numpy as np
1598
+ >>> from scipy import special
1599
+ >>> import matplotlib.pyplot as plt
1600
+
1601
+ We can initialize a variable ``p`` as a Gegenbauer polynomial using the
1602
+ `gegenbauer` function and evaluate at a point ``x = 1``.
1603
+
1604
+ >>> p = special.gegenbauer(3, 0.5, monic=False)
1605
+ >>> p
1606
+ poly1d([ 2.5, 0. , -1.5, 0. ])
1607
+ >>> p(1)
1608
+ 1.0
1609
+
1610
+ To evaluate ``p`` at various points ``x`` in the interval ``(-3, 3)``,
1611
+ simply pass an array ``x`` to ``p`` as follows:
1612
+
1613
+ >>> x = np.linspace(-3, 3, 400)
1614
+ >>> y = p(x)
1615
+
1616
+ We can then visualize ``x, y`` using `matplotlib.pyplot`.
1617
+
1618
+ >>> fig, ax = plt.subplots()
1619
+ >>> ax.plot(x, y)
1620
+ >>> ax.set_title("Gegenbauer (ultraspherical) polynomial of degree 3")
1621
+ >>> ax.set_xlabel("x")
1622
+ >>> ax.set_ylabel("G_3(x)")
1623
+ >>> plt.show()
1624
+
1625
+ """
1626
+ base = jacobi(n, alpha - 0.5, alpha - 0.5, monic=monic)
1627
+ if monic:
1628
+ return base
1629
+ # Abrahmowitz and Stegan 22.5.20
1630
+ factor = (_gam(2*alpha + n) * _gam(alpha + 0.5) /
1631
+ _gam(2*alpha) / _gam(alpha + 0.5 + n))
1632
+ base._scale(factor)
1633
+ base.__dict__['_eval_func'] = lambda x: _ufuncs.eval_gegenbauer(float(n),
1634
+ alpha, x)
1635
+ return base
1636
+
1637
+ # Chebyshev of the first kind: T_n(x) =
1638
+ # n! sqrt(pi) / _gam(n+1./2)* P^(-1/2,-1/2)_n(x)
1639
+ # Computed anew.
1640
+
1641
+
1642
+ def roots_chebyt(n, mu=False):
1643
+ r"""Gauss-Chebyshev (first kind) quadrature.
1644
+
1645
+ Computes the sample points and weights for Gauss-Chebyshev
1646
+ quadrature. The sample points are the roots of the nth degree
1647
+ Chebyshev polynomial of the first kind, :math:`T_n(x)`. These
1648
+ sample points and weights correctly integrate polynomials of
1649
+ degree :math:`2n - 1` or less over the interval :math:`[-1, 1]`
1650
+ with weight function :math:`w(x) = 1/\sqrt{1 - x^2}`. See 22.2.4
1651
+ in [AS]_ for more details.
1652
+
1653
+ Parameters
1654
+ ----------
1655
+ n : int
1656
+ quadrature order
1657
+ mu : bool, optional
1658
+ If True, return the sum of the weights, optional.
1659
+
1660
+ Returns
1661
+ -------
1662
+ x : ndarray
1663
+ Sample points
1664
+ w : ndarray
1665
+ Weights
1666
+ mu : float
1667
+ Sum of the weights
1668
+
1669
+ See Also
1670
+ --------
1671
+ scipy.integrate.quadrature
1672
+ scipy.integrate.fixed_quad
1673
+ numpy.polynomial.chebyshev.chebgauss
1674
+
1675
+ References
1676
+ ----------
1677
+ .. [AS] Milton Abramowitz and Irene A. Stegun, eds.
1678
+ Handbook of Mathematical Functions with Formulas,
1679
+ Graphs, and Mathematical Tables. New York: Dover, 1972.
1680
+
1681
+ """
1682
+ m = int(n)
1683
+ if n < 1 or n != m:
1684
+ raise ValueError('n must be a positive integer.')
1685
+ x = _ufuncs._sinpi(np.arange(-m + 1, m, 2) / (2*m))
1686
+ w = np.full_like(x, pi/m)
1687
+ if mu:
1688
+ return x, w, pi
1689
+ else:
1690
+ return x, w
1691
+
1692
+
1693
+ def chebyt(n, monic=False):
1694
+ r"""Chebyshev polynomial of the first kind.
1695
+
1696
+ Defined to be the solution of
1697
+
1698
+ .. math::
1699
+ (1 - x^2)\frac{d^2}{dx^2}T_n - x\frac{d}{dx}T_n + n^2T_n = 0;
1700
+
1701
+ :math:`T_n` is a polynomial of degree :math:`n`.
1702
+
1703
+ Parameters
1704
+ ----------
1705
+ n : int
1706
+ Degree of the polynomial.
1707
+ monic : bool, optional
1708
+ If `True`, scale the leading coefficient to be 1. Default is
1709
+ `False`.
1710
+
1711
+ Returns
1712
+ -------
1713
+ T : orthopoly1d
1714
+ Chebyshev polynomial of the first kind.
1715
+
1716
+ See Also
1717
+ --------
1718
+ chebyu : Chebyshev polynomial of the second kind.
1719
+
1720
+ Notes
1721
+ -----
1722
+ The polynomials :math:`T_n` are orthogonal over :math:`[-1, 1]`
1723
+ with weight function :math:`(1 - x^2)^{-1/2}`.
1724
+
1725
+ References
1726
+ ----------
1727
+ .. [AS] Milton Abramowitz and Irene A. Stegun, eds.
1728
+ Handbook of Mathematical Functions with Formulas,
1729
+ Graphs, and Mathematical Tables. New York: Dover, 1972.
1730
+
1731
+ Examples
1732
+ --------
1733
+ Chebyshev polynomials of the first kind of order :math:`n` can
1734
+ be obtained as the determinant of specific :math:`n \times n`
1735
+ matrices. As an example we can check how the points obtained from
1736
+ the determinant of the following :math:`3 \times 3` matrix
1737
+ lay exactly on :math:`T_3`:
1738
+
1739
+ >>> import numpy as np
1740
+ >>> import matplotlib.pyplot as plt
1741
+ >>> from scipy.linalg import det
1742
+ >>> from scipy.special import chebyt
1743
+ >>> x = np.arange(-1.0, 1.0, 0.01)
1744
+ >>> fig, ax = plt.subplots()
1745
+ >>> ax.set_ylim(-2.0, 2.0)
1746
+ >>> ax.set_title(r'Chebyshev polynomial $T_3$')
1747
+ >>> ax.plot(x, chebyt(3)(x), label=rf'$T_3$')
1748
+ >>> for p in np.arange(-1.0, 1.0, 0.1):
1749
+ ... ax.plot(p,
1750
+ ... det(np.array([[p, 1, 0], [1, 2*p, 1], [0, 1, 2*p]])),
1751
+ ... 'rx')
1752
+ >>> plt.legend(loc='best')
1753
+ >>> plt.show()
1754
+
1755
+ They are also related to the Jacobi Polynomials
1756
+ :math:`P_n^{(-0.5, -0.5)}` through the relation:
1757
+
1758
+ .. math::
1759
+ P_n^{(-0.5, -0.5)}(x) = \frac{1}{4^n} \binom{2n}{n} T_n(x)
1760
+
1761
+ Let's verify it for :math:`n = 3`:
1762
+
1763
+ >>> from scipy.special import binom
1764
+ >>> from scipy.special import jacobi
1765
+ >>> x = np.arange(-1.0, 1.0, 0.01)
1766
+ >>> np.allclose(jacobi(3, -0.5, -0.5)(x),
1767
+ ... 1/64 * binom(6, 3) * chebyt(3)(x))
1768
+ True
1769
+
1770
+ We can plot the Chebyshev polynomials :math:`T_n` for some values
1771
+ of :math:`n`:
1772
+
1773
+ >>> x = np.arange(-1.5, 1.5, 0.01)
1774
+ >>> fig, ax = plt.subplots()
1775
+ >>> ax.set_ylim(-4.0, 4.0)
1776
+ >>> ax.set_title(r'Chebyshev polynomials $T_n$')
1777
+ >>> for n in np.arange(2,5):
1778
+ ... ax.plot(x, chebyt(n)(x), label=rf'$T_n={n}$')
1779
+ >>> plt.legend(loc='best')
1780
+ >>> plt.show()
1781
+
1782
+ """
1783
+ if n < 0:
1784
+ raise ValueError("n must be nonnegative.")
1785
+
1786
+ def wfunc(x):
1787
+ return 1.0 / sqrt(1 - x * x)
1788
+ if n == 0:
1789
+ return orthopoly1d([], [], pi, 1.0, wfunc, (-1, 1), monic,
1790
+ lambda x: _ufuncs.eval_chebyt(n, x))
1791
+ n1 = n
1792
+ x, w, mu = roots_chebyt(n1, mu=True)
1793
+ hn = pi / 2
1794
+ kn = 2**(n - 1)
1795
+ p = orthopoly1d(x, w, hn, kn, wfunc, (-1, 1), monic,
1796
+ lambda x: _ufuncs.eval_chebyt(n, x))
1797
+ return p
1798
+
1799
+ # Chebyshev of the second kind
1800
+ # U_n(x) = (n+1)! sqrt(pi) / (2*_gam(n+3./2)) * P^(1/2,1/2)_n(x)
1801
+
1802
+
1803
+ def roots_chebyu(n, mu=False):
1804
+ r"""Gauss-Chebyshev (second kind) quadrature.
1805
+
1806
+ Computes the sample points and weights for Gauss-Chebyshev
1807
+ quadrature. The sample points are the roots of the nth degree
1808
+ Chebyshev polynomial of the second kind, :math:`U_n(x)`. These
1809
+ sample points and weights correctly integrate polynomials of
1810
+ degree :math:`2n - 1` or less over the interval :math:`[-1, 1]`
1811
+ with weight function :math:`w(x) = \sqrt{1 - x^2}`. See 22.2.5 in
1812
+ [AS]_ for details.
1813
+
1814
+ Parameters
1815
+ ----------
1816
+ n : int
1817
+ quadrature order
1818
+ mu : bool, optional
1819
+ If True, return the sum of the weights, optional.
1820
+
1821
+ Returns
1822
+ -------
1823
+ x : ndarray
1824
+ Sample points
1825
+ w : ndarray
1826
+ Weights
1827
+ mu : float
1828
+ Sum of the weights
1829
+
1830
+ See Also
1831
+ --------
1832
+ scipy.integrate.quadrature
1833
+ scipy.integrate.fixed_quad
1834
+
1835
+ References
1836
+ ----------
1837
+ .. [AS] Milton Abramowitz and Irene A. Stegun, eds.
1838
+ Handbook of Mathematical Functions with Formulas,
1839
+ Graphs, and Mathematical Tables. New York: Dover, 1972.
1840
+
1841
+ """
1842
+ m = int(n)
1843
+ if n < 1 or n != m:
1844
+ raise ValueError('n must be a positive integer.')
1845
+ t = np.arange(m, 0, -1) * pi / (m + 1)
1846
+ x = np.cos(t)
1847
+ w = pi * np.sin(t)**2 / (m + 1)
1848
+ if mu:
1849
+ return x, w, pi / 2
1850
+ else:
1851
+ return x, w
1852
+
1853
+
1854
+ def chebyu(n, monic=False):
1855
+ r"""Chebyshev polynomial of the second kind.
1856
+
1857
+ Defined to be the solution of
1858
+
1859
+ .. math::
1860
+ (1 - x^2)\frac{d^2}{dx^2}U_n - 3x\frac{d}{dx}U_n
1861
+ + n(n + 2)U_n = 0;
1862
+
1863
+ :math:`U_n` is a polynomial of degree :math:`n`.
1864
+
1865
+ Parameters
1866
+ ----------
1867
+ n : int
1868
+ Degree of the polynomial.
1869
+ monic : bool, optional
1870
+ If `True`, scale the leading coefficient to be 1. Default is
1871
+ `False`.
1872
+
1873
+ Returns
1874
+ -------
1875
+ U : orthopoly1d
1876
+ Chebyshev polynomial of the second kind.
1877
+
1878
+ See Also
1879
+ --------
1880
+ chebyt : Chebyshev polynomial of the first kind.
1881
+
1882
+ Notes
1883
+ -----
1884
+ The polynomials :math:`U_n` are orthogonal over :math:`[-1, 1]`
1885
+ with weight function :math:`(1 - x^2)^{1/2}`.
1886
+
1887
+ References
1888
+ ----------
1889
+ .. [AS] Milton Abramowitz and Irene A. Stegun, eds.
1890
+ Handbook of Mathematical Functions with Formulas,
1891
+ Graphs, and Mathematical Tables. New York: Dover, 1972.
1892
+
1893
+ Examples
1894
+ --------
1895
+ Chebyshev polynomials of the second kind of order :math:`n` can
1896
+ be obtained as the determinant of specific :math:`n \times n`
1897
+ matrices. As an example we can check how the points obtained from
1898
+ the determinant of the following :math:`3 \times 3` matrix
1899
+ lay exactly on :math:`U_3`:
1900
+
1901
+ >>> import numpy as np
1902
+ >>> import matplotlib.pyplot as plt
1903
+ >>> from scipy.linalg import det
1904
+ >>> from scipy.special import chebyu
1905
+ >>> x = np.arange(-1.0, 1.0, 0.01)
1906
+ >>> fig, ax = plt.subplots()
1907
+ >>> ax.set_ylim(-2.0, 2.0)
1908
+ >>> ax.set_title(r'Chebyshev polynomial $U_3$')
1909
+ >>> ax.plot(x, chebyu(3)(x), label=rf'$U_3$')
1910
+ >>> for p in np.arange(-1.0, 1.0, 0.1):
1911
+ ... ax.plot(p,
1912
+ ... det(np.array([[2*p, 1, 0], [1, 2*p, 1], [0, 1, 2*p]])),
1913
+ ... 'rx')
1914
+ >>> plt.legend(loc='best')
1915
+ >>> plt.show()
1916
+
1917
+ They satisfy the recurrence relation:
1918
+
1919
+ .. math::
1920
+ U_{2n-1}(x) = 2 T_n(x)U_{n-1}(x)
1921
+
1922
+ where the :math:`T_n` are the Chebyshev polynomial of the first kind.
1923
+ Let's verify it for :math:`n = 2`:
1924
+
1925
+ >>> from scipy.special import chebyt
1926
+ >>> x = np.arange(-1.0, 1.0, 0.01)
1927
+ >>> np.allclose(chebyu(3)(x), 2 * chebyt(2)(x) * chebyu(1)(x))
1928
+ True
1929
+
1930
+ We can plot the Chebyshev polynomials :math:`U_n` for some values
1931
+ of :math:`n`:
1932
+
1933
+ >>> x = np.arange(-1.0, 1.0, 0.01)
1934
+ >>> fig, ax = plt.subplots()
1935
+ >>> ax.set_ylim(-1.5, 1.5)
1936
+ >>> ax.set_title(r'Chebyshev polynomials $U_n$')
1937
+ >>> for n in np.arange(1,5):
1938
+ ... ax.plot(x, chebyu(n)(x), label=rf'$U_n={n}$')
1939
+ >>> plt.legend(loc='best')
1940
+ >>> plt.show()
1941
+
1942
+ """
1943
+ base = jacobi(n, 0.5, 0.5, monic=monic)
1944
+ if monic:
1945
+ return base
1946
+ factor = sqrt(pi) / 2.0 * _gam(n + 2) / _gam(n + 1.5)
1947
+ base._scale(factor)
1948
+ return base
1949
+
1950
+ # Chebyshev of the first kind C_n(x)
1951
+
1952
+
1953
+ def roots_chebyc(n, mu=False):
1954
+ r"""Gauss-Chebyshev (first kind) quadrature.
1955
+
1956
+ Compute the sample points and weights for Gauss-Chebyshev
1957
+ quadrature. The sample points are the roots of the nth degree
1958
+ Chebyshev polynomial of the first kind, :math:`C_n(x)`. These
1959
+ sample points and weights correctly integrate polynomials of
1960
+ degree :math:`2n - 1` or less over the interval :math:`[-2, 2]`
1961
+ with weight function :math:`w(x) = 1 / \sqrt{1 - (x/2)^2}`. See
1962
+ 22.2.6 in [AS]_ for more details.
1963
+
1964
+ Parameters
1965
+ ----------
1966
+ n : int
1967
+ quadrature order
1968
+ mu : bool, optional
1969
+ If True, return the sum of the weights, optional.
1970
+
1971
+ Returns
1972
+ -------
1973
+ x : ndarray
1974
+ Sample points
1975
+ w : ndarray
1976
+ Weights
1977
+ mu : float
1978
+ Sum of the weights
1979
+
1980
+ See Also
1981
+ --------
1982
+ scipy.integrate.quadrature
1983
+ scipy.integrate.fixed_quad
1984
+
1985
+ References
1986
+ ----------
1987
+ .. [AS] Milton Abramowitz and Irene A. Stegun, eds.
1988
+ Handbook of Mathematical Functions with Formulas,
1989
+ Graphs, and Mathematical Tables. New York: Dover, 1972.
1990
+
1991
+ """
1992
+ x, w, m = roots_chebyt(n, True)
1993
+ x *= 2
1994
+ w *= 2
1995
+ m *= 2
1996
+ if mu:
1997
+ return x, w, m
1998
+ else:
1999
+ return x, w
2000
+
2001
+
2002
+ def chebyc(n, monic=False):
2003
+ r"""Chebyshev polynomial of the first kind on :math:`[-2, 2]`.
2004
+
2005
+ Defined as :math:`C_n(x) = 2T_n(x/2)`, where :math:`T_n` is the
2006
+ nth Chebychev polynomial of the first kind.
2007
+
2008
+ Parameters
2009
+ ----------
2010
+ n : int
2011
+ Degree of the polynomial.
2012
+ monic : bool, optional
2013
+ If `True`, scale the leading coefficient to be 1. Default is
2014
+ `False`.
2015
+
2016
+ Returns
2017
+ -------
2018
+ C : orthopoly1d
2019
+ Chebyshev polynomial of the first kind on :math:`[-2, 2]`.
2020
+
2021
+ See Also
2022
+ --------
2023
+ chebyt : Chebyshev polynomial of the first kind.
2024
+
2025
+ Notes
2026
+ -----
2027
+ The polynomials :math:`C_n(x)` are orthogonal over :math:`[-2, 2]`
2028
+ with weight function :math:`1/\sqrt{1 - (x/2)^2}`.
2029
+
2030
+ References
2031
+ ----------
2032
+ .. [1] Abramowitz and Stegun, "Handbook of Mathematical Functions"
2033
+ Section 22. National Bureau of Standards, 1972.
2034
+
2035
+ """
2036
+ if n < 0:
2037
+ raise ValueError("n must be nonnegative.")
2038
+
2039
+ if n == 0:
2040
+ n1 = n + 1
2041
+ else:
2042
+ n1 = n
2043
+ x, w = roots_chebyc(n1)
2044
+ if n == 0:
2045
+ x, w = [], []
2046
+ hn = 4 * pi * ((n == 0) + 1)
2047
+ kn = 1.0
2048
+ p = orthopoly1d(x, w, hn, kn,
2049
+ wfunc=lambda x: 1.0 / sqrt(1 - x * x / 4.0),
2050
+ limits=(-2, 2), monic=monic)
2051
+ if not monic:
2052
+ p._scale(2.0 / p(2))
2053
+ p.__dict__['_eval_func'] = lambda x: _ufuncs.eval_chebyc(n, x)
2054
+ return p
2055
+
2056
+ # Chebyshev of the second kind S_n(x)
2057
+
2058
+
2059
+ def roots_chebys(n, mu=False):
2060
+ r"""Gauss-Chebyshev (second kind) quadrature.
2061
+
2062
+ Compute the sample points and weights for Gauss-Chebyshev
2063
+ quadrature. The sample points are the roots of the nth degree
2064
+ Chebyshev polynomial of the second kind, :math:`S_n(x)`. These
2065
+ sample points and weights correctly integrate polynomials of
2066
+ degree :math:`2n - 1` or less over the interval :math:`[-2, 2]`
2067
+ with weight function :math:`w(x) = \sqrt{1 - (x/2)^2}`. See 22.2.7
2068
+ in [AS]_ for more details.
2069
+
2070
+ Parameters
2071
+ ----------
2072
+ n : int
2073
+ quadrature order
2074
+ mu : bool, optional
2075
+ If True, return the sum of the weights, optional.
2076
+
2077
+ Returns
2078
+ -------
2079
+ x : ndarray
2080
+ Sample points
2081
+ w : ndarray
2082
+ Weights
2083
+ mu : float
2084
+ Sum of the weights
2085
+
2086
+ See Also
2087
+ --------
2088
+ scipy.integrate.quadrature
2089
+ scipy.integrate.fixed_quad
2090
+
2091
+ References
2092
+ ----------
2093
+ .. [AS] Milton Abramowitz and Irene A. Stegun, eds.
2094
+ Handbook of Mathematical Functions with Formulas,
2095
+ Graphs, and Mathematical Tables. New York: Dover, 1972.
2096
+
2097
+ """
2098
+ x, w, m = roots_chebyu(n, True)
2099
+ x *= 2
2100
+ w *= 2
2101
+ m *= 2
2102
+ if mu:
2103
+ return x, w, m
2104
+ else:
2105
+ return x, w
2106
+
2107
+
2108
+ def chebys(n, monic=False):
2109
+ r"""Chebyshev polynomial of the second kind on :math:`[-2, 2]`.
2110
+
2111
+ Defined as :math:`S_n(x) = U_n(x/2)` where :math:`U_n` is the
2112
+ nth Chebychev polynomial of the second kind.
2113
+
2114
+ Parameters
2115
+ ----------
2116
+ n : int
2117
+ Degree of the polynomial.
2118
+ monic : bool, optional
2119
+ If `True`, scale the leading coefficient to be 1. Default is
2120
+ `False`.
2121
+
2122
+ Returns
2123
+ -------
2124
+ S : orthopoly1d
2125
+ Chebyshev polynomial of the second kind on :math:`[-2, 2]`.
2126
+
2127
+ See Also
2128
+ --------
2129
+ chebyu : Chebyshev polynomial of the second kind
2130
+
2131
+ Notes
2132
+ -----
2133
+ The polynomials :math:`S_n(x)` are orthogonal over :math:`[-2, 2]`
2134
+ with weight function :math:`\sqrt{1 - (x/2)}^2`.
2135
+
2136
+ References
2137
+ ----------
2138
+ .. [1] Abramowitz and Stegun, "Handbook of Mathematical Functions"
2139
+ Section 22. National Bureau of Standards, 1972.
2140
+
2141
+ """
2142
+ if n < 0:
2143
+ raise ValueError("n must be nonnegative.")
2144
+
2145
+ if n == 0:
2146
+ n1 = n + 1
2147
+ else:
2148
+ n1 = n
2149
+ x, w = roots_chebys(n1)
2150
+ if n == 0:
2151
+ x, w = [], []
2152
+ hn = pi
2153
+ kn = 1.0
2154
+ p = orthopoly1d(x, w, hn, kn,
2155
+ wfunc=lambda x: sqrt(1 - x * x / 4.0),
2156
+ limits=(-2, 2), monic=monic)
2157
+ if not monic:
2158
+ factor = (n + 1.0) / p(2)
2159
+ p._scale(factor)
2160
+ p.__dict__['_eval_func'] = lambda x: _ufuncs.eval_chebys(n, x)
2161
+ return p
2162
+
2163
+ # Shifted Chebyshev of the first kind T^*_n(x)
2164
+
2165
+
2166
+ def roots_sh_chebyt(n, mu=False):
2167
+ r"""Gauss-Chebyshev (first kind, shifted) quadrature.
2168
+
2169
+ Compute the sample points and weights for Gauss-Chebyshev
2170
+ quadrature. The sample points are the roots of the nth degree
2171
+ shifted Chebyshev polynomial of the first kind, :math:`T_n(x)`.
2172
+ These sample points and weights correctly integrate polynomials of
2173
+ degree :math:`2n - 1` or less over the interval :math:`[0, 1]`
2174
+ with weight function :math:`w(x) = 1/\sqrt{x - x^2}`. See 22.2.8
2175
+ in [AS]_ for more details.
2176
+
2177
+ Parameters
2178
+ ----------
2179
+ n : int
2180
+ quadrature order
2181
+ mu : bool, optional
2182
+ If True, return the sum of the weights, optional.
2183
+
2184
+ Returns
2185
+ -------
2186
+ x : ndarray
2187
+ Sample points
2188
+ w : ndarray
2189
+ Weights
2190
+ mu : float
2191
+ Sum of the weights
2192
+
2193
+ See Also
2194
+ --------
2195
+ scipy.integrate.quadrature
2196
+ scipy.integrate.fixed_quad
2197
+
2198
+ References
2199
+ ----------
2200
+ .. [AS] Milton Abramowitz and Irene A. Stegun, eds.
2201
+ Handbook of Mathematical Functions with Formulas,
2202
+ Graphs, and Mathematical Tables. New York: Dover, 1972.
2203
+
2204
+ """
2205
+ xw = roots_chebyt(n, mu)
2206
+ return ((xw[0] + 1) / 2,) + xw[1:]
2207
+
2208
+
2209
+ def sh_chebyt(n, monic=False):
2210
+ r"""Shifted Chebyshev polynomial of the first kind.
2211
+
2212
+ Defined as :math:`T^*_n(x) = T_n(2x - 1)` for :math:`T_n` the nth
2213
+ Chebyshev polynomial of the first kind.
2214
+
2215
+ Parameters
2216
+ ----------
2217
+ n : int
2218
+ Degree of the polynomial.
2219
+ monic : bool, optional
2220
+ If `True`, scale the leading coefficient to be 1. Default is
2221
+ `False`.
2222
+
2223
+ Returns
2224
+ -------
2225
+ T : orthopoly1d
2226
+ Shifted Chebyshev polynomial of the first kind.
2227
+
2228
+ Notes
2229
+ -----
2230
+ The polynomials :math:`T^*_n` are orthogonal over :math:`[0, 1]`
2231
+ with weight function :math:`(x - x^2)^{-1/2}`.
2232
+
2233
+ """
2234
+ base = sh_jacobi(n, 0.0, 0.5, monic=monic)
2235
+ if monic:
2236
+ return base
2237
+ if n > 0:
2238
+ factor = 4**n / 2.0
2239
+ else:
2240
+ factor = 1.0
2241
+ base._scale(factor)
2242
+ return base
2243
+
2244
+
2245
+ # Shifted Chebyshev of the second kind U^*_n(x)
2246
+ def roots_sh_chebyu(n, mu=False):
2247
+ r"""Gauss-Chebyshev (second kind, shifted) quadrature.
2248
+
2249
+ Computes the sample points and weights for Gauss-Chebyshev
2250
+ quadrature. The sample points are the roots of the nth degree
2251
+ shifted Chebyshev polynomial of the second kind, :math:`U_n(x)`.
2252
+ These sample points and weights correctly integrate polynomials of
2253
+ degree :math:`2n - 1` or less over the interval :math:`[0, 1]`
2254
+ with weight function :math:`w(x) = \sqrt{x - x^2}`. See 22.2.9 in
2255
+ [AS]_ for more details.
2256
+
2257
+ Parameters
2258
+ ----------
2259
+ n : int
2260
+ quadrature order
2261
+ mu : bool, optional
2262
+ If True, return the sum of the weights, optional.
2263
+
2264
+ Returns
2265
+ -------
2266
+ x : ndarray
2267
+ Sample points
2268
+ w : ndarray
2269
+ Weights
2270
+ mu : float
2271
+ Sum of the weights
2272
+
2273
+ See Also
2274
+ --------
2275
+ scipy.integrate.quadrature
2276
+ scipy.integrate.fixed_quad
2277
+
2278
+ References
2279
+ ----------
2280
+ .. [AS] Milton Abramowitz and Irene A. Stegun, eds.
2281
+ Handbook of Mathematical Functions with Formulas,
2282
+ Graphs, and Mathematical Tables. New York: Dover, 1972.
2283
+
2284
+ """
2285
+ x, w, m = roots_chebyu(n, True)
2286
+ x = (x + 1) / 2
2287
+ m_us = _ufuncs.beta(1.5, 1.5)
2288
+ w *= m_us / m
2289
+ if mu:
2290
+ return x, w, m_us
2291
+ else:
2292
+ return x, w
2293
+
2294
+
2295
+ def sh_chebyu(n, monic=False):
2296
+ r"""Shifted Chebyshev polynomial of the second kind.
2297
+
2298
+ Defined as :math:`U^*_n(x) = U_n(2x - 1)` for :math:`U_n` the nth
2299
+ Chebyshev polynomial of the second kind.
2300
+
2301
+ Parameters
2302
+ ----------
2303
+ n : int
2304
+ Degree of the polynomial.
2305
+ monic : bool, optional
2306
+ If `True`, scale the leading coefficient to be 1. Default is
2307
+ `False`.
2308
+
2309
+ Returns
2310
+ -------
2311
+ U : orthopoly1d
2312
+ Shifted Chebyshev polynomial of the second kind.
2313
+
2314
+ Notes
2315
+ -----
2316
+ The polynomials :math:`U^*_n` are orthogonal over :math:`[0, 1]`
2317
+ with weight function :math:`(x - x^2)^{1/2}`.
2318
+
2319
+ """
2320
+ base = sh_jacobi(n, 2.0, 1.5, monic=monic)
2321
+ if monic:
2322
+ return base
2323
+ factor = 4**n
2324
+ base._scale(factor)
2325
+ return base
2326
+
2327
+ # Legendre
2328
+
2329
+
2330
+ def roots_legendre(n, mu=False):
2331
+ r"""Gauss-Legendre quadrature.
2332
+
2333
+ Compute the sample points and weights for Gauss-Legendre
2334
+ quadrature [GL]_. The sample points are the roots of the nth degree
2335
+ Legendre polynomial :math:`P_n(x)`. These sample points and
2336
+ weights correctly integrate polynomials of degree :math:`2n - 1`
2337
+ or less over the interval :math:`[-1, 1]` with weight function
2338
+ :math:`w(x) = 1`. See 2.2.10 in [AS]_ for more details.
2339
+
2340
+ Parameters
2341
+ ----------
2342
+ n : int
2343
+ quadrature order
2344
+ mu : bool, optional
2345
+ If True, return the sum of the weights, optional.
2346
+
2347
+ Returns
2348
+ -------
2349
+ x : ndarray
2350
+ Sample points
2351
+ w : ndarray
2352
+ Weights
2353
+ mu : float
2354
+ Sum of the weights
2355
+
2356
+ See Also
2357
+ --------
2358
+ scipy.integrate.quadrature
2359
+ scipy.integrate.fixed_quad
2360
+ numpy.polynomial.legendre.leggauss
2361
+
2362
+ References
2363
+ ----------
2364
+ .. [AS] Milton Abramowitz and Irene A. Stegun, eds.
2365
+ Handbook of Mathematical Functions with Formulas,
2366
+ Graphs, and Mathematical Tables. New York: Dover, 1972.
2367
+ .. [GL] Gauss-Legendre quadrature, Wikipedia,
2368
+ https://en.wikipedia.org/wiki/Gauss%E2%80%93Legendre_quadrature
2369
+
2370
+ Examples
2371
+ --------
2372
+ >>> import numpy as np
2373
+ >>> from scipy.special import roots_legendre, eval_legendre
2374
+ >>> roots, weights = roots_legendre(9)
2375
+
2376
+ ``roots`` holds the roots, and ``weights`` holds the weights for
2377
+ Gauss-Legendre quadrature.
2378
+
2379
+ >>> roots
2380
+ array([-0.96816024, -0.83603111, -0.61337143, -0.32425342, 0. ,
2381
+ 0.32425342, 0.61337143, 0.83603111, 0.96816024])
2382
+ >>> weights
2383
+ array([0.08127439, 0.18064816, 0.2606107 , 0.31234708, 0.33023936,
2384
+ 0.31234708, 0.2606107 , 0.18064816, 0.08127439])
2385
+
2386
+ Verify that we have the roots by evaluating the degree 9 Legendre
2387
+ polynomial at ``roots``. All the values are approximately zero:
2388
+
2389
+ >>> eval_legendre(9, roots)
2390
+ array([-8.88178420e-16, -2.22044605e-16, 1.11022302e-16, 1.11022302e-16,
2391
+ 0.00000000e+00, -5.55111512e-17, -1.94289029e-16, 1.38777878e-16,
2392
+ -8.32667268e-17])
2393
+
2394
+ Here we'll show how the above values can be used to estimate the
2395
+ integral from 1 to 2 of f(t) = t + 1/t with Gauss-Legendre
2396
+ quadrature [GL]_. First define the function and the integration
2397
+ limits.
2398
+
2399
+ >>> def f(t):
2400
+ ... return t + 1/t
2401
+ ...
2402
+ >>> a = 1
2403
+ >>> b = 2
2404
+
2405
+ We'll use ``integral(f(t), t=a, t=b)`` to denote the definite integral
2406
+ of f from t=a to t=b. The sample points in ``roots`` are from the
2407
+ interval [-1, 1], so we'll rewrite the integral with the simple change
2408
+ of variable::
2409
+
2410
+ x = 2/(b - a) * t - (a + b)/(b - a)
2411
+
2412
+ with inverse::
2413
+
2414
+ t = (b - a)/2 * x + (a + 2)/2
2415
+
2416
+ Then::
2417
+
2418
+ integral(f(t), a, b) =
2419
+ (b - a)/2 * integral(f((b-a)/2*x + (a+b)/2), x=-1, x=1)
2420
+
2421
+ We can approximate the latter integral with the values returned
2422
+ by `roots_legendre`.
2423
+
2424
+ Map the roots computed above from [-1, 1] to [a, b].
2425
+
2426
+ >>> t = (b - a)/2 * roots + (a + b)/2
2427
+
2428
+ Approximate the integral as the weighted sum of the function values.
2429
+
2430
+ >>> (b - a)/2 * f(t).dot(weights)
2431
+ 2.1931471805599276
2432
+
2433
+ Compare that to the exact result, which is 3/2 + log(2):
2434
+
2435
+ >>> 1.5 + np.log(2)
2436
+ 2.1931471805599454
2437
+
2438
+ """
2439
+ m = int(n)
2440
+ if n < 1 or n != m:
2441
+ raise ValueError("n must be a positive integer.")
2442
+
2443
+ mu0 = 2.0
2444
+ def an_func(k):
2445
+ return 0.0 * k
2446
+ def bn_func(k):
2447
+ return k * np.sqrt(1.0 / (4 * k * k - 1))
2448
+ f = _ufuncs.eval_legendre
2449
+ def df(n, x):
2450
+ return (-n * x * _ufuncs.eval_legendre(n, x)
2451
+ + n * _ufuncs.eval_legendre(n - 1, x)) / (1 - x ** 2)
2452
+ return _gen_roots_and_weights(m, mu0, an_func, bn_func, f, df, True, mu)
2453
+
2454
+
2455
+ def legendre(n, monic=False):
2456
+ r"""Legendre polynomial.
2457
+
2458
+ Defined to be the solution of
2459
+
2460
+ .. math::
2461
+ \frac{d}{dx}\left[(1 - x^2)\frac{d}{dx}P_n(x)\right]
2462
+ + n(n + 1)P_n(x) = 0;
2463
+
2464
+ :math:`P_n(x)` is a polynomial of degree :math:`n`.
2465
+
2466
+ Parameters
2467
+ ----------
2468
+ n : int
2469
+ Degree of the polynomial.
2470
+ monic : bool, optional
2471
+ If `True`, scale the leading coefficient to be 1. Default is
2472
+ `False`.
2473
+
2474
+ Returns
2475
+ -------
2476
+ P : orthopoly1d
2477
+ Legendre polynomial.
2478
+
2479
+ Notes
2480
+ -----
2481
+ The polynomials :math:`P_n` are orthogonal over :math:`[-1, 1]`
2482
+ with weight function 1.
2483
+
2484
+ Examples
2485
+ --------
2486
+ Generate the 3rd-order Legendre polynomial 1/2*(5x^3 + 0x^2 - 3x + 0):
2487
+
2488
+ >>> from scipy.special import legendre
2489
+ >>> legendre(3)
2490
+ poly1d([ 2.5, 0. , -1.5, 0. ])
2491
+
2492
+ """
2493
+ if n < 0:
2494
+ raise ValueError("n must be nonnegative.")
2495
+
2496
+ if n == 0:
2497
+ n1 = n + 1
2498
+ else:
2499
+ n1 = n
2500
+ x, w = roots_legendre(n1)
2501
+ if n == 0:
2502
+ x, w = [], []
2503
+ hn = 2.0 / (2 * n + 1)
2504
+ kn = _gam(2 * n + 1) / _gam(n + 1)**2 / 2.0**n
2505
+ p = orthopoly1d(x, w, hn, kn, wfunc=lambda x: 1.0, limits=(-1, 1),
2506
+ monic=monic,
2507
+ eval_func=lambda x: _ufuncs.eval_legendre(n, x))
2508
+ return p
2509
+
2510
+ # Shifted Legendre P^*_n(x)
2511
+
2512
+
2513
+ def roots_sh_legendre(n, mu=False):
2514
+ r"""Gauss-Legendre (shifted) quadrature.
2515
+
2516
+ Compute the sample points and weights for Gauss-Legendre
2517
+ quadrature. The sample points are the roots of the nth degree
2518
+ shifted Legendre polynomial :math:`P^*_n(x)`. These sample points
2519
+ and weights correctly integrate polynomials of degree :math:`2n -
2520
+ 1` or less over the interval :math:`[0, 1]` with weight function
2521
+ :math:`w(x) = 1.0`. See 2.2.11 in [AS]_ for details.
2522
+
2523
+ Parameters
2524
+ ----------
2525
+ n : int
2526
+ quadrature order
2527
+ mu : bool, optional
2528
+ If True, return the sum of the weights, optional.
2529
+
2530
+ Returns
2531
+ -------
2532
+ x : ndarray
2533
+ Sample points
2534
+ w : ndarray
2535
+ Weights
2536
+ mu : float
2537
+ Sum of the weights
2538
+
2539
+ See Also
2540
+ --------
2541
+ scipy.integrate.quadrature
2542
+ scipy.integrate.fixed_quad
2543
+
2544
+ References
2545
+ ----------
2546
+ .. [AS] Milton Abramowitz and Irene A. Stegun, eds.
2547
+ Handbook of Mathematical Functions with Formulas,
2548
+ Graphs, and Mathematical Tables. New York: Dover, 1972.
2549
+
2550
+ """
2551
+ x, w = roots_legendre(n)
2552
+ x = (x + 1) / 2
2553
+ w /= 2
2554
+ if mu:
2555
+ return x, w, 1.0
2556
+ else:
2557
+ return x, w
2558
+
2559
+
2560
+ def sh_legendre(n, monic=False):
2561
+ r"""Shifted Legendre polynomial.
2562
+
2563
+ Defined as :math:`P^*_n(x) = P_n(2x - 1)` for :math:`P_n` the nth
2564
+ Legendre polynomial.
2565
+
2566
+ Parameters
2567
+ ----------
2568
+ n : int
2569
+ Degree of the polynomial.
2570
+ monic : bool, optional
2571
+ If `True`, scale the leading coefficient to be 1. Default is
2572
+ `False`.
2573
+
2574
+ Returns
2575
+ -------
2576
+ P : orthopoly1d
2577
+ Shifted Legendre polynomial.
2578
+
2579
+ Notes
2580
+ -----
2581
+ The polynomials :math:`P^*_n` are orthogonal over :math:`[0, 1]`
2582
+ with weight function 1.
2583
+
2584
+ """
2585
+ if n < 0:
2586
+ raise ValueError("n must be nonnegative.")
2587
+
2588
+ def wfunc(x):
2589
+ return 0.0 * x + 1.0
2590
+ if n == 0:
2591
+ return orthopoly1d([], [], 1.0, 1.0, wfunc, (0, 1), monic,
2592
+ lambda x: _ufuncs.eval_sh_legendre(n, x))
2593
+ x, w = roots_sh_legendre(n)
2594
+ hn = 1.0 / (2 * n + 1.0)
2595
+ kn = _gam(2 * n + 1) / _gam(n + 1)**2
2596
+ p = orthopoly1d(x, w, hn, kn, wfunc, limits=(0, 1), monic=monic,
2597
+ eval_func=lambda x: _ufuncs.eval_sh_legendre(n, x))
2598
+ return p
2599
+
2600
+
2601
+ # Make the old root function names an alias for the new ones
2602
+ _modattrs = globals()
2603
+ for newfun, oldfun in _rootfuns_map.items():
2604
+ _modattrs[oldfun] = _modattrs[newfun]
2605
+ __all__.append(oldfun)
.venv/Lib/site-packages/scipy/special/_orthogonal.pyi ADDED
@@ -0,0 +1,331 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+ from typing import (
3
+ Any,
4
+ Callable,
5
+ Literal,
6
+ Optional,
7
+ overload,
8
+ )
9
+
10
+ import numpy
11
+
12
+ _IntegerType = int | numpy.integer
13
+ _FloatingType = float | numpy.floating
14
+ _PointsAndWeights = tuple[numpy.ndarray, numpy.ndarray]
15
+ _PointsAndWeightsAndMu = tuple[numpy.ndarray, numpy.ndarray, float]
16
+
17
+ _ArrayLike0D = bool | int | float | complex | str | bytes | numpy.generic
18
+
19
+ __all__ = [
20
+ 'legendre',
21
+ 'chebyt',
22
+ 'chebyu',
23
+ 'chebyc',
24
+ 'chebys',
25
+ 'jacobi',
26
+ 'laguerre',
27
+ 'genlaguerre',
28
+ 'hermite',
29
+ 'hermitenorm',
30
+ 'gegenbauer',
31
+ 'sh_legendre',
32
+ 'sh_chebyt',
33
+ 'sh_chebyu',
34
+ 'sh_jacobi',
35
+ 'roots_legendre',
36
+ 'roots_chebyt',
37
+ 'roots_chebyu',
38
+ 'roots_chebyc',
39
+ 'roots_chebys',
40
+ 'roots_jacobi',
41
+ 'roots_laguerre',
42
+ 'roots_genlaguerre',
43
+ 'roots_hermite',
44
+ 'roots_hermitenorm',
45
+ 'roots_gegenbauer',
46
+ 'roots_sh_legendre',
47
+ 'roots_sh_chebyt',
48
+ 'roots_sh_chebyu',
49
+ 'roots_sh_jacobi',
50
+ ]
51
+
52
+ @overload
53
+ def roots_jacobi(
54
+ n: _IntegerType,
55
+ alpha: _FloatingType,
56
+ beta: _FloatingType,
57
+ ) -> _PointsAndWeights: ...
58
+ @overload
59
+ def roots_jacobi(
60
+ n: _IntegerType,
61
+ alpha: _FloatingType,
62
+ beta: _FloatingType,
63
+ mu: Literal[False],
64
+ ) -> _PointsAndWeights: ...
65
+ @overload
66
+ def roots_jacobi(
67
+ n: _IntegerType,
68
+ alpha: _FloatingType,
69
+ beta: _FloatingType,
70
+ mu: Literal[True],
71
+ ) -> _PointsAndWeightsAndMu: ...
72
+
73
+ @overload
74
+ def roots_sh_jacobi(
75
+ n: _IntegerType,
76
+ p1: _FloatingType,
77
+ q1: _FloatingType,
78
+ ) -> _PointsAndWeights: ...
79
+ @overload
80
+ def roots_sh_jacobi(
81
+ n: _IntegerType,
82
+ p1: _FloatingType,
83
+ q1: _FloatingType,
84
+ mu: Literal[False],
85
+ ) -> _PointsAndWeights: ...
86
+ @overload
87
+ def roots_sh_jacobi(
88
+ n: _IntegerType,
89
+ p1: _FloatingType,
90
+ q1: _FloatingType,
91
+ mu: Literal[True],
92
+ ) -> _PointsAndWeightsAndMu: ...
93
+
94
+ @overload
95
+ def roots_genlaguerre(
96
+ n: _IntegerType,
97
+ alpha: _FloatingType,
98
+ ) -> _PointsAndWeights: ...
99
+ @overload
100
+ def roots_genlaguerre(
101
+ n: _IntegerType,
102
+ alpha: _FloatingType,
103
+ mu: Literal[False],
104
+ ) -> _PointsAndWeights: ...
105
+ @overload
106
+ def roots_genlaguerre(
107
+ n: _IntegerType,
108
+ alpha: _FloatingType,
109
+ mu: Literal[True],
110
+ ) -> _PointsAndWeightsAndMu: ...
111
+
112
+ @overload
113
+ def roots_laguerre(n: _IntegerType) -> _PointsAndWeights: ...
114
+ @overload
115
+ def roots_laguerre(
116
+ n: _IntegerType,
117
+ mu: Literal[False],
118
+ ) -> _PointsAndWeights: ...
119
+ @overload
120
+ def roots_laguerre(
121
+ n: _IntegerType,
122
+ mu: Literal[True],
123
+ ) -> _PointsAndWeightsAndMu: ...
124
+
125
+ @overload
126
+ def roots_hermite(n: _IntegerType) -> _PointsAndWeights: ...
127
+ @overload
128
+ def roots_hermite(
129
+ n: _IntegerType,
130
+ mu: Literal[False],
131
+ ) -> _PointsAndWeights: ...
132
+ @overload
133
+ def roots_hermite(
134
+ n: _IntegerType,
135
+ mu: Literal[True],
136
+ ) -> _PointsAndWeightsAndMu: ...
137
+
138
+ @overload
139
+ def roots_hermitenorm(n: _IntegerType) -> _PointsAndWeights: ...
140
+ @overload
141
+ def roots_hermitenorm(
142
+ n: _IntegerType,
143
+ mu: Literal[False],
144
+ ) -> _PointsAndWeights: ...
145
+ @overload
146
+ def roots_hermitenorm(
147
+ n: _IntegerType,
148
+ mu: Literal[True],
149
+ ) -> _PointsAndWeightsAndMu: ...
150
+
151
+ @overload
152
+ def roots_gegenbauer(
153
+ n: _IntegerType,
154
+ alpha: _FloatingType,
155
+ ) -> _PointsAndWeights: ...
156
+ @overload
157
+ def roots_gegenbauer(
158
+ n: _IntegerType,
159
+ alpha: _FloatingType,
160
+ mu: Literal[False],
161
+ ) -> _PointsAndWeights: ...
162
+ @overload
163
+ def roots_gegenbauer(
164
+ n: _IntegerType,
165
+ alpha: _FloatingType,
166
+ mu: Literal[True],
167
+ ) -> _PointsAndWeightsAndMu: ...
168
+
169
+ @overload
170
+ def roots_chebyt(n: _IntegerType) -> _PointsAndWeights: ...
171
+ @overload
172
+ def roots_chebyt(
173
+ n: _IntegerType,
174
+ mu: Literal[False],
175
+ ) -> _PointsAndWeights: ...
176
+ @overload
177
+ def roots_chebyt(
178
+ n: _IntegerType,
179
+ mu: Literal[True],
180
+ ) -> _PointsAndWeightsAndMu: ...
181
+
182
+ @overload
183
+ def roots_chebyu(n: _IntegerType) -> _PointsAndWeights: ...
184
+ @overload
185
+ def roots_chebyu(
186
+ n: _IntegerType,
187
+ mu: Literal[False],
188
+ ) -> _PointsAndWeights: ...
189
+ @overload
190
+ def roots_chebyu(
191
+ n: _IntegerType,
192
+ mu: Literal[True],
193
+ ) -> _PointsAndWeightsAndMu: ...
194
+
195
+ @overload
196
+ def roots_chebyc(n: _IntegerType) -> _PointsAndWeights: ...
197
+ @overload
198
+ def roots_chebyc(
199
+ n: _IntegerType,
200
+ mu: Literal[False],
201
+ ) -> _PointsAndWeights: ...
202
+ @overload
203
+ def roots_chebyc(
204
+ n: _IntegerType,
205
+ mu: Literal[True],
206
+ ) -> _PointsAndWeightsAndMu: ...
207
+
208
+ @overload
209
+ def roots_chebys(n: _IntegerType) -> _PointsAndWeights: ...
210
+ @overload
211
+ def roots_chebys(
212
+ n: _IntegerType,
213
+ mu: Literal[False],
214
+ ) -> _PointsAndWeights: ...
215
+ @overload
216
+ def roots_chebys(
217
+ n: _IntegerType,
218
+ mu: Literal[True],
219
+ ) -> _PointsAndWeightsAndMu: ...
220
+
221
+ @overload
222
+ def roots_sh_chebyt(n: _IntegerType) -> _PointsAndWeights: ...
223
+ @overload
224
+ def roots_sh_chebyt(
225
+ n: _IntegerType,
226
+ mu: Literal[False],
227
+ ) -> _PointsAndWeights: ...
228
+ @overload
229
+ def roots_sh_chebyt(
230
+ n: _IntegerType,
231
+ mu: Literal[True],
232
+ ) -> _PointsAndWeightsAndMu: ...
233
+
234
+ @overload
235
+ def roots_sh_chebyu(n: _IntegerType) -> _PointsAndWeights: ...
236
+ @overload
237
+ def roots_sh_chebyu(
238
+ n: _IntegerType,
239
+ mu: Literal[False],
240
+ ) -> _PointsAndWeights: ...
241
+ @overload
242
+ def roots_sh_chebyu(
243
+ n: _IntegerType,
244
+ mu: Literal[True],
245
+ ) -> _PointsAndWeightsAndMu: ...
246
+
247
+ @overload
248
+ def roots_legendre(n: _IntegerType) -> _PointsAndWeights: ...
249
+ @overload
250
+ def roots_legendre(
251
+ n: _IntegerType,
252
+ mu: Literal[False],
253
+ ) -> _PointsAndWeights: ...
254
+ @overload
255
+ def roots_legendre(
256
+ n: _IntegerType,
257
+ mu: Literal[True],
258
+ ) -> _PointsAndWeightsAndMu: ...
259
+
260
+ @overload
261
+ def roots_sh_legendre(n: _IntegerType) -> _PointsAndWeights: ...
262
+ @overload
263
+ def roots_sh_legendre(
264
+ n: _IntegerType,
265
+ mu: Literal[False],
266
+ ) -> _PointsAndWeights: ...
267
+ @overload
268
+ def roots_sh_legendre(
269
+ n: _IntegerType,
270
+ mu: Literal[True],
271
+ ) -> _PointsAndWeightsAndMu: ...
272
+
273
+ class orthopoly1d(numpy.poly1d):
274
+ def __init__(
275
+ self,
276
+ roots: numpy.typing.ArrayLike,
277
+ weights: numpy.typing.ArrayLike | None,
278
+ hn: float = ...,
279
+ kn: float = ...,
280
+ wfunc = Optional[Callable[[float], float]], # noqa: UP007
281
+ limits = tuple[float, float] | None,
282
+ monic: bool = ...,
283
+ eval_func: numpy.ufunc = ...,
284
+ ) -> None: ...
285
+ @property
286
+ def limits(self) -> tuple[float, float]: ...
287
+ def weight_func(self, x: float) -> float: ...
288
+ @overload
289
+ def __call__(self, x: _ArrayLike0D) -> Any: ...
290
+ @overload
291
+ def __call__(self, x: numpy.poly1d) -> numpy.poly1d: ... # type: ignore[misc]
292
+ @overload
293
+ def __call__(self, x: numpy.typing.ArrayLike) -> numpy.ndarray: ...
294
+
295
+ def legendre(n: _IntegerType, monic: bool = ...) -> orthopoly1d: ...
296
+ def chebyt(n: _IntegerType, monic: bool = ...) -> orthopoly1d: ...
297
+ def chebyu(n: _IntegerType, monic: bool = ...) -> orthopoly1d: ...
298
+ def chebyc(n: _IntegerType, monic: bool = ...) -> orthopoly1d: ...
299
+ def chebys(n: _IntegerType, monic: bool = ...) -> orthopoly1d: ...
300
+ def jacobi(
301
+ n: _IntegerType,
302
+ alpha: _FloatingType,
303
+ beta: _FloatingType,
304
+ monic: bool = ...,
305
+ ) -> orthopoly1d: ...
306
+ def laguerre(n: _IntegerType, monic: bool = ...) -> orthopoly1d: ...
307
+ def genlaguerre(
308
+ n: _IntegerType,
309
+ alpha: _FloatingType,
310
+ monic: bool = ...,
311
+ ) -> orthopoly1d: ...
312
+ def hermite(n: _IntegerType, monic: bool = ...) -> orthopoly1d: ...
313
+ def hermitenorm(n: _IntegerType, monic: bool = ...) -> orthopoly1d: ...
314
+ def gegenbauer(
315
+ n: _IntegerType,
316
+ alpha: _FloatingType,
317
+ monic: bool = ...,
318
+ ) -> orthopoly1d: ...
319
+ def sh_legendre(n: _IntegerType, monic: bool = ...) -> orthopoly1d: ...
320
+ def sh_chebyt(n: _IntegerType, monic: bool = ...) -> orthopoly1d: ...
321
+ def sh_chebyu(n: _IntegerType, monic: bool = ...) -> orthopoly1d: ...
322
+ def sh_jacobi(
323
+ n: _IntegerType,
324
+ p: _FloatingType,
325
+ q: _FloatingType,
326
+ monic: bool = ...,
327
+ ) -> orthopoly1d: ...
328
+
329
+ # These functions are not public, but still need stubs because they
330
+ # get checked in the tests.
331
+ def _roots_hermite_asy(n: _IntegerType) -> _PointsAndWeights: ...
.venv/Lib/site-packages/scipy/special/_sf_error.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Warnings and Exceptions that can be raised by special functions."""
2
+ import warnings
3
+
4
+
5
+ class SpecialFunctionWarning(Warning):
6
+ """Warning that can be emitted by special functions."""
7
+ pass
8
+
9
+
10
+ warnings.simplefilter("always", category=SpecialFunctionWarning)
11
+
12
+
13
+ class SpecialFunctionError(Exception):
14
+ """Exception that can be raised by special functions."""
15
+ pass
.venv/Lib/site-packages/scipy/special/_specfun.cp39-win_amd64.dll.a ADDED
Binary file (1.55 kB). View file
 
.venv/Lib/site-packages/scipy/special/_specfun.cp39-win_amd64.pyd ADDED
Binary file (265 kB). View file
 
.venv/Lib/site-packages/scipy/special/_spfun_stats.py ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Last Change: Sat Mar 21 02:00 PM 2009 J
2
+
3
+ # Copyright (c) 2001, 2002 Enthought, Inc.
4
+ #
5
+ # All rights reserved.
6
+ #
7
+ # Redistribution and use in source and binary forms, with or without
8
+ # modification, are permitted provided that the following conditions are met:
9
+ #
10
+ # a. Redistributions of source code must retain the above copyright notice,
11
+ # this list of conditions and the following disclaimer.
12
+ # b. Redistributions in binary form must reproduce the above copyright
13
+ # notice, this list of conditions and the following disclaimer in the
14
+ # documentation and/or other materials provided with the distribution.
15
+ # c. Neither the name of the Enthought nor the names of its contributors
16
+ # may be used to endorse or promote products derived from this software
17
+ # without specific prior written permission.
18
+ #
19
+ #
20
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
24
+ # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28
+ # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29
+ # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
30
+ # DAMAGE.
31
+
32
+ """Some more special functions which may be useful for multivariate statistical
33
+ analysis."""
34
+
35
+ import numpy as np
36
+ from scipy.special import gammaln as loggam
37
+
38
+
39
+ __all__ = ['multigammaln']
40
+
41
+
42
+ def multigammaln(a, d):
43
+ r"""Returns the log of multivariate gamma, also sometimes called the
44
+ generalized gamma.
45
+
46
+ Parameters
47
+ ----------
48
+ a : ndarray
49
+ The multivariate gamma is computed for each item of `a`.
50
+ d : int
51
+ The dimension of the space of integration.
52
+
53
+ Returns
54
+ -------
55
+ res : ndarray
56
+ The values of the log multivariate gamma at the given points `a`.
57
+
58
+ Notes
59
+ -----
60
+ The formal definition of the multivariate gamma of dimension d for a real
61
+ `a` is
62
+
63
+ .. math::
64
+
65
+ \Gamma_d(a) = \int_{A>0} e^{-tr(A)} |A|^{a - (d+1)/2} dA
66
+
67
+ with the condition :math:`a > (d-1)/2`, and :math:`A > 0` being the set of
68
+ all the positive definite matrices of dimension `d`. Note that `a` is a
69
+ scalar: the integrand only is multivariate, the argument is not (the
70
+ function is defined over a subset of the real set).
71
+
72
+ This can be proven to be equal to the much friendlier equation
73
+
74
+ .. math::
75
+
76
+ \Gamma_d(a) = \pi^{d(d-1)/4} \prod_{i=1}^{d} \Gamma(a - (i-1)/2).
77
+
78
+ References
79
+ ----------
80
+ R. J. Muirhead, Aspects of multivariate statistical theory (Wiley Series in
81
+ probability and mathematical statistics).
82
+
83
+ Examples
84
+ --------
85
+ >>> import numpy as np
86
+ >>> from scipy.special import multigammaln, gammaln
87
+ >>> a = 23.5
88
+ >>> d = 10
89
+ >>> multigammaln(a, d)
90
+ 454.1488605074416
91
+
92
+ Verify that the result agrees with the logarithm of the equation
93
+ shown above:
94
+
95
+ >>> d*(d-1)/4*np.log(np.pi) + gammaln(a - 0.5*np.arange(0, d)).sum()
96
+ 454.1488605074416
97
+ """
98
+ a = np.asarray(a)
99
+ if not np.isscalar(d) or (np.floor(d) != d):
100
+ raise ValueError("d should be a positive integer (dimension)")
101
+ if np.any(a <= 0.5 * (d - 1)):
102
+ raise ValueError(f"condition a ({a:f}) > 0.5 * (d-1) ({0.5 * (d-1):f}) not met")
103
+
104
+ res = (d * (d-1) * 0.25) * np.log(np.pi)
105
+ res += np.sum(loggam([(a - (j - 1.)/2) for j in range(1, d+1)]), axis=0)
106
+ return res
.venv/Lib/site-packages/scipy/special/_spherical_bessel.py ADDED
@@ -0,0 +1,354 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ from ._ufuncs import (_spherical_jn, _spherical_yn, _spherical_in,
3
+ _spherical_kn, _spherical_jn_d, _spherical_yn_d,
4
+ _spherical_in_d, _spherical_kn_d)
5
+
6
+ def spherical_jn(n, z, derivative=False):
7
+ r"""Spherical Bessel function of the first kind or its derivative.
8
+
9
+ Defined as [1]_,
10
+
11
+ .. math:: j_n(z) = \sqrt{\frac{\pi}{2z}} J_{n + 1/2}(z),
12
+
13
+ where :math:`J_n` is the Bessel function of the first kind.
14
+
15
+ Parameters
16
+ ----------
17
+ n : int, array_like
18
+ Order of the Bessel function (n >= 0).
19
+ z : complex or float, array_like
20
+ Argument of the Bessel function.
21
+ derivative : bool, optional
22
+ If True, the value of the derivative (rather than the function
23
+ itself) is returned.
24
+
25
+ Returns
26
+ -------
27
+ jn : ndarray
28
+
29
+ Notes
30
+ -----
31
+ For real arguments greater than the order, the function is computed
32
+ using the ascending recurrence [2]_. For small real or complex
33
+ arguments, the definitional relation to the cylindrical Bessel function
34
+ of the first kind is used.
35
+
36
+ The derivative is computed using the relations [3]_,
37
+
38
+ .. math::
39
+ j_n'(z) = j_{n-1}(z) - \frac{n + 1}{z} j_n(z).
40
+
41
+ j_0'(z) = -j_1(z)
42
+
43
+
44
+ .. versionadded:: 0.18.0
45
+
46
+ References
47
+ ----------
48
+ .. [1] https://dlmf.nist.gov/10.47.E3
49
+ .. [2] https://dlmf.nist.gov/10.51.E1
50
+ .. [3] https://dlmf.nist.gov/10.51.E2
51
+ .. [AS] Milton Abramowitz and Irene A. Stegun, eds.
52
+ Handbook of Mathematical Functions with Formulas,
53
+ Graphs, and Mathematical Tables. New York: Dover, 1972.
54
+
55
+ Examples
56
+ --------
57
+ The spherical Bessel functions of the first kind :math:`j_n` accept
58
+ both real and complex second argument. They can return a complex type:
59
+
60
+ >>> from scipy.special import spherical_jn
61
+ >>> spherical_jn(0, 3+5j)
62
+ (-9.878987731663194-8.021894345786002j)
63
+ >>> type(spherical_jn(0, 3+5j))
64
+ <class 'numpy.complex128'>
65
+
66
+ We can verify the relation for the derivative from the Notes
67
+ for :math:`n=3` in the interval :math:`[1, 2]`:
68
+
69
+ >>> import numpy as np
70
+ >>> x = np.arange(1.0, 2.0, 0.01)
71
+ >>> np.allclose(spherical_jn(3, x, True),
72
+ ... spherical_jn(2, x) - 4/x * spherical_jn(3, x))
73
+ True
74
+
75
+ The first few :math:`j_n` with real argument:
76
+
77
+ >>> import matplotlib.pyplot as plt
78
+ >>> x = np.arange(0.0, 10.0, 0.01)
79
+ >>> fig, ax = plt.subplots()
80
+ >>> ax.set_ylim(-0.5, 1.5)
81
+ >>> ax.set_title(r'Spherical Bessel functions $j_n$')
82
+ >>> for n in np.arange(0, 4):
83
+ ... ax.plot(x, spherical_jn(n, x), label=rf'$j_{n}$')
84
+ >>> plt.legend(loc='best')
85
+ >>> plt.show()
86
+
87
+ """
88
+ n = np.asarray(n, dtype=np.dtype("long"))
89
+ if derivative:
90
+ return _spherical_jn_d(n, z)
91
+ else:
92
+ return _spherical_jn(n, z)
93
+
94
+
95
+ def spherical_yn(n, z, derivative=False):
96
+ r"""Spherical Bessel function of the second kind or its derivative.
97
+
98
+ Defined as [1]_,
99
+
100
+ .. math:: y_n(z) = \sqrt{\frac{\pi}{2z}} Y_{n + 1/2}(z),
101
+
102
+ where :math:`Y_n` is the Bessel function of the second kind.
103
+
104
+ Parameters
105
+ ----------
106
+ n : int, array_like
107
+ Order of the Bessel function (n >= 0).
108
+ z : complex or float, array_like
109
+ Argument of the Bessel function.
110
+ derivative : bool, optional
111
+ If True, the value of the derivative (rather than the function
112
+ itself) is returned.
113
+
114
+ Returns
115
+ -------
116
+ yn : ndarray
117
+
118
+ Notes
119
+ -----
120
+ For real arguments, the function is computed using the ascending
121
+ recurrence [2]_. For complex arguments, the definitional relation to
122
+ the cylindrical Bessel function of the second kind is used.
123
+
124
+ The derivative is computed using the relations [3]_,
125
+
126
+ .. math::
127
+ y_n' = y_{n-1} - \frac{n + 1}{z} y_n.
128
+
129
+ y_0' = -y_1
130
+
131
+
132
+ .. versionadded:: 0.18.0
133
+
134
+ References
135
+ ----------
136
+ .. [1] https://dlmf.nist.gov/10.47.E4
137
+ .. [2] https://dlmf.nist.gov/10.51.E1
138
+ .. [3] https://dlmf.nist.gov/10.51.E2
139
+ .. [AS] Milton Abramowitz and Irene A. Stegun, eds.
140
+ Handbook of Mathematical Functions with Formulas,
141
+ Graphs, and Mathematical Tables. New York: Dover, 1972.
142
+
143
+ Examples
144
+ --------
145
+ The spherical Bessel functions of the second kind :math:`y_n` accept
146
+ both real and complex second argument. They can return a complex type:
147
+
148
+ >>> from scipy.special import spherical_yn
149
+ >>> spherical_yn(0, 3+5j)
150
+ (8.022343088587197-9.880052589376795j)
151
+ >>> type(spherical_yn(0, 3+5j))
152
+ <class 'numpy.complex128'>
153
+
154
+ We can verify the relation for the derivative from the Notes
155
+ for :math:`n=3` in the interval :math:`[1, 2]`:
156
+
157
+ >>> import numpy as np
158
+ >>> x = np.arange(1.0, 2.0, 0.01)
159
+ >>> np.allclose(spherical_yn(3, x, True),
160
+ ... spherical_yn(2, x) - 4/x * spherical_yn(3, x))
161
+ True
162
+
163
+ The first few :math:`y_n` with real argument:
164
+
165
+ >>> import matplotlib.pyplot as plt
166
+ >>> x = np.arange(0.0, 10.0, 0.01)
167
+ >>> fig, ax = plt.subplots()
168
+ >>> ax.set_ylim(-2.0, 1.0)
169
+ >>> ax.set_title(r'Spherical Bessel functions $y_n$')
170
+ >>> for n in np.arange(0, 4):
171
+ ... ax.plot(x, spherical_yn(n, x), label=rf'$y_{n}$')
172
+ >>> plt.legend(loc='best')
173
+ >>> plt.show()
174
+
175
+ """
176
+ n = np.asarray(n, dtype=np.dtype("long"))
177
+ if derivative:
178
+ return _spherical_yn_d(n, z)
179
+ else:
180
+ return _spherical_yn(n, z)
181
+
182
+
183
+ def spherical_in(n, z, derivative=False):
184
+ r"""Modified spherical Bessel function of the first kind or its derivative.
185
+
186
+ Defined as [1]_,
187
+
188
+ .. math:: i_n(z) = \sqrt{\frac{\pi}{2z}} I_{n + 1/2}(z),
189
+
190
+ where :math:`I_n` is the modified Bessel function of the first kind.
191
+
192
+ Parameters
193
+ ----------
194
+ n : int, array_like
195
+ Order of the Bessel function (n >= 0).
196
+ z : complex or float, array_like
197
+ Argument of the Bessel function.
198
+ derivative : bool, optional
199
+ If True, the value of the derivative (rather than the function
200
+ itself) is returned.
201
+
202
+ Returns
203
+ -------
204
+ in : ndarray
205
+
206
+ Notes
207
+ -----
208
+ The function is computed using its definitional relation to the
209
+ modified cylindrical Bessel function of the first kind.
210
+
211
+ The derivative is computed using the relations [2]_,
212
+
213
+ .. math::
214
+ i_n' = i_{n-1} - \frac{n + 1}{z} i_n.
215
+
216
+ i_1' = i_0
217
+
218
+
219
+ .. versionadded:: 0.18.0
220
+
221
+ References
222
+ ----------
223
+ .. [1] https://dlmf.nist.gov/10.47.E7
224
+ .. [2] https://dlmf.nist.gov/10.51.E5
225
+ .. [AS] Milton Abramowitz and Irene A. Stegun, eds.
226
+ Handbook of Mathematical Functions with Formulas,
227
+ Graphs, and Mathematical Tables. New York: Dover, 1972.
228
+
229
+ Examples
230
+ --------
231
+ The modified spherical Bessel functions of the first kind :math:`i_n`
232
+ accept both real and complex second argument.
233
+ They can return a complex type:
234
+
235
+ >>> from scipy.special import spherical_in
236
+ >>> spherical_in(0, 3+5j)
237
+ (-1.1689867793369182-1.2697305267234222j)
238
+ >>> type(spherical_in(0, 3+5j))
239
+ <class 'numpy.complex128'>
240
+
241
+ We can verify the relation for the derivative from the Notes
242
+ for :math:`n=3` in the interval :math:`[1, 2]`:
243
+
244
+ >>> import numpy as np
245
+ >>> x = np.arange(1.0, 2.0, 0.01)
246
+ >>> np.allclose(spherical_in(3, x, True),
247
+ ... spherical_in(2, x) - 4/x * spherical_in(3, x))
248
+ True
249
+
250
+ The first few :math:`i_n` with real argument:
251
+
252
+ >>> import matplotlib.pyplot as plt
253
+ >>> x = np.arange(0.0, 6.0, 0.01)
254
+ >>> fig, ax = plt.subplots()
255
+ >>> ax.set_ylim(-0.5, 5.0)
256
+ >>> ax.set_title(r'Modified spherical Bessel functions $i_n$')
257
+ >>> for n in np.arange(0, 4):
258
+ ... ax.plot(x, spherical_in(n, x), label=rf'$i_{n}$')
259
+ >>> plt.legend(loc='best')
260
+ >>> plt.show()
261
+
262
+ """
263
+ n = np.asarray(n, dtype=np.dtype("long"))
264
+ if derivative:
265
+ return _spherical_in_d(n, z)
266
+ else:
267
+ return _spherical_in(n, z)
268
+
269
+
270
+ def spherical_kn(n, z, derivative=False):
271
+ r"""Modified spherical Bessel function of the second kind or its derivative.
272
+
273
+ Defined as [1]_,
274
+
275
+ .. math:: k_n(z) = \sqrt{\frac{\pi}{2z}} K_{n + 1/2}(z),
276
+
277
+ where :math:`K_n` is the modified Bessel function of the second kind.
278
+
279
+ Parameters
280
+ ----------
281
+ n : int, array_like
282
+ Order of the Bessel function (n >= 0).
283
+ z : complex or float, array_like
284
+ Argument of the Bessel function.
285
+ derivative : bool, optional
286
+ If True, the value of the derivative (rather than the function
287
+ itself) is returned.
288
+
289
+ Returns
290
+ -------
291
+ kn : ndarray
292
+
293
+ Notes
294
+ -----
295
+ The function is computed using its definitional relation to the
296
+ modified cylindrical Bessel function of the second kind.
297
+
298
+ The derivative is computed using the relations [2]_,
299
+
300
+ .. math::
301
+ k_n' = -k_{n-1} - \frac{n + 1}{z} k_n.
302
+
303
+ k_0' = -k_1
304
+
305
+
306
+ .. versionadded:: 0.18.0
307
+
308
+ References
309
+ ----------
310
+ .. [1] https://dlmf.nist.gov/10.47.E9
311
+ .. [2] https://dlmf.nist.gov/10.51.E5
312
+ .. [AS] Milton Abramowitz and Irene A. Stegun, eds.
313
+ Handbook of Mathematical Functions with Formulas,
314
+ Graphs, and Mathematical Tables. New York: Dover, 1972.
315
+
316
+ Examples
317
+ --------
318
+ The modified spherical Bessel functions of the second kind :math:`k_n`
319
+ accept both real and complex second argument.
320
+ They can return a complex type:
321
+
322
+ >>> from scipy.special import spherical_kn
323
+ >>> spherical_kn(0, 3+5j)
324
+ (0.012985785614001561+0.003354691603137546j)
325
+ >>> type(spherical_kn(0, 3+5j))
326
+ <class 'numpy.complex128'>
327
+
328
+ We can verify the relation for the derivative from the Notes
329
+ for :math:`n=3` in the interval :math:`[1, 2]`:
330
+
331
+ >>> import numpy as np
332
+ >>> x = np.arange(1.0, 2.0, 0.01)
333
+ >>> np.allclose(spherical_kn(3, x, True),
334
+ ... - 4/x * spherical_kn(3, x) - spherical_kn(2, x))
335
+ True
336
+
337
+ The first few :math:`k_n` with real argument:
338
+
339
+ >>> import matplotlib.pyplot as plt
340
+ >>> x = np.arange(0.0, 4.0, 0.01)
341
+ >>> fig, ax = plt.subplots()
342
+ >>> ax.set_ylim(0.0, 5.0)
343
+ >>> ax.set_title(r'Modified spherical Bessel functions $k_n$')
344
+ >>> for n in np.arange(0, 4):
345
+ ... ax.plot(x, spherical_kn(n, x), label=rf'$k_{n}$')
346
+ >>> plt.legend(loc='best')
347
+ >>> plt.show()
348
+
349
+ """
350
+ n = np.asarray(n, dtype=np.dtype("long"))
351
+ if derivative:
352
+ return _spherical_kn_d(n, z)
353
+ else:
354
+ return _spherical_kn(n, z)
.venv/Lib/site-packages/scipy/special/_support_alternative_backends.py ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import functools
4
+
5
+ import numpy as np
6
+ from scipy._lib._array_api import array_namespace, is_cupy, is_torch, is_numpy
7
+ from . import _ufuncs
8
+ # These don't really need to be imported, but otherwise IDEs might not realize
9
+ # that these are defined in this file / report an error in __init__.py
10
+ from ._ufuncs import (
11
+ log_ndtr, ndtr, ndtri, erf, erfc, i0, i0e, i1, i1e, # noqa: F401
12
+ gammaln, gammainc, gammaincc, logit, expit) # noqa: F401
13
+
14
+ _SCIPY_ARRAY_API = os.environ.get("SCIPY_ARRAY_API", False)
15
+ array_api_compat_prefix = "scipy._lib.array_api_compat"
16
+
17
+
18
+ def get_array_special_func(f_name, xp, n_array_args):
19
+ if is_numpy(xp):
20
+ f = getattr(_ufuncs, f_name, None)
21
+ elif is_torch(xp):
22
+ f = getattr(xp.special, f_name, None)
23
+ elif is_cupy(xp):
24
+ import cupyx # type: ignore[import]
25
+ f = getattr(cupyx.scipy.special, f_name, None)
26
+ elif xp.__name__ == f"{array_api_compat_prefix}.jax":
27
+ f = getattr(xp.scipy.special, f_name, None)
28
+ else:
29
+ f_scipy = getattr(_ufuncs, f_name, None)
30
+ def f(*args, **kwargs):
31
+ array_args = args[:n_array_args]
32
+ other_args = args[n_array_args:]
33
+ array_args = [np.asarray(arg) for arg in array_args]
34
+ out = f_scipy(*array_args, *other_args, **kwargs)
35
+ return xp.asarray(out)
36
+
37
+ return f
38
+
39
+ # functools.wraps doesn't work because:
40
+ # 'numpy.ufunc' object has no attribute '__module__'
41
+ def support_alternative_backends(f_name, n_array_args):
42
+ func = getattr(_ufuncs, f_name)
43
+
44
+ @functools.wraps(func)
45
+ def wrapped(*args, **kwargs):
46
+ xp = array_namespace(*args[:n_array_args])
47
+ f = get_array_special_func(f_name, xp, n_array_args)
48
+ return f(*args, **kwargs)
49
+
50
+ return wrapped
51
+
52
+
53
+ array_special_func_map = {
54
+ 'log_ndtr': 1,
55
+ 'ndtr': 1,
56
+ 'ndtri': 1,
57
+ 'erf': 1,
58
+ 'erfc': 1,
59
+ 'i0': 1,
60
+ 'i0e': 1,
61
+ 'i1': 1,
62
+ 'i1e': 1,
63
+ 'gammaln': 1,
64
+ 'gammainc': 2,
65
+ 'gammaincc': 2,
66
+ 'logit': 1,
67
+ 'expit': 1,
68
+ }
69
+
70
+ for f_name, n_array_args in array_special_func_map.items():
71
+ f = (support_alternative_backends(f_name, n_array_args) if _SCIPY_ARRAY_API
72
+ else getattr(_ufuncs, f_name))
73
+ sys.modules[__name__].__dict__[f_name] = f
74
+
75
+ __all__ = list(array_special_func_map)
.venv/Lib/site-packages/scipy/special/_test_internal.cp39-win_amd64.dll.a ADDED
Binary file (1.62 kB). View file
 
.venv/Lib/site-packages/scipy/special/_test_internal.cp39-win_amd64.pyd ADDED
Binary file (271 kB). View file
 
.venv/Lib/site-packages/scipy/special/_test_internal.pyi ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+
3
+ def have_fenv() -> bool: ...
4
+ def random_double(size: int) -> np.float64: ...
5
+ def test_add_round(size: int, mode: str): ...
6
+
7
+ def _dd_exp(xhi: float, xlo: float) -> tuple[float, float]: ...
8
+ def _dd_log(xhi: float, xlo: float) -> tuple[float, float]: ...
9
+ def _dd_expm1(xhi: float, xlo: float) -> tuple[float, float]: ...
.venv/Lib/site-packages/scipy/special/_testutils.py ADDED
@@ -0,0 +1,321 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import functools
3
+ import operator
4
+ from scipy._lib import _pep440
5
+
6
+ import numpy as np
7
+ from numpy.testing import assert_
8
+ import pytest
9
+
10
+ import scipy.special as sc
11
+
12
+ __all__ = ['with_special_errors', 'assert_func_equal', 'FuncData']
13
+
14
+
15
+ #------------------------------------------------------------------------------
16
+ # Check if a module is present to be used in tests
17
+ #------------------------------------------------------------------------------
18
+
19
+ class MissingModule:
20
+ def __init__(self, name):
21
+ self.name = name
22
+
23
+
24
+ def check_version(module, min_ver):
25
+ if type(module) == MissingModule:
26
+ return pytest.mark.skip(reason=f"{module.name} is not installed")
27
+ return pytest.mark.skipif(
28
+ _pep440.parse(module.__version__) < _pep440.Version(min_ver),
29
+ reason=f"{module.__name__} version >= {min_ver} required"
30
+ )
31
+
32
+
33
+ #------------------------------------------------------------------------------
34
+ # Enable convergence and loss of precision warnings -- turn off one by one
35
+ #------------------------------------------------------------------------------
36
+
37
+ def with_special_errors(func):
38
+ """
39
+ Enable special function errors (such as underflow, overflow,
40
+ loss of precision, etc.)
41
+ """
42
+ @functools.wraps(func)
43
+ def wrapper(*a, **kw):
44
+ with sc.errstate(all='raise'):
45
+ res = func(*a, **kw)
46
+ return res
47
+ return wrapper
48
+
49
+
50
+ #------------------------------------------------------------------------------
51
+ # Comparing function values at many data points at once, with helpful
52
+ # error reports
53
+ #------------------------------------------------------------------------------
54
+
55
+ def assert_func_equal(func, results, points, rtol=None, atol=None,
56
+ param_filter=None, knownfailure=None,
57
+ vectorized=True, dtype=None, nan_ok=False,
58
+ ignore_inf_sign=False, distinguish_nan_and_inf=True):
59
+ if hasattr(points, 'next'):
60
+ # it's a generator
61
+ points = list(points)
62
+
63
+ points = np.asarray(points)
64
+ if points.ndim == 1:
65
+ points = points[:,None]
66
+ nparams = points.shape[1]
67
+
68
+ if hasattr(results, '__name__'):
69
+ # function
70
+ data = points
71
+ result_columns = None
72
+ result_func = results
73
+ else:
74
+ # dataset
75
+ data = np.c_[points, results]
76
+ result_columns = list(range(nparams, data.shape[1]))
77
+ result_func = None
78
+
79
+ fdata = FuncData(func, data, list(range(nparams)),
80
+ result_columns=result_columns, result_func=result_func,
81
+ rtol=rtol, atol=atol, param_filter=param_filter,
82
+ knownfailure=knownfailure, nan_ok=nan_ok, vectorized=vectorized,
83
+ ignore_inf_sign=ignore_inf_sign,
84
+ distinguish_nan_and_inf=distinguish_nan_and_inf)
85
+ fdata.check()
86
+
87
+
88
+ class FuncData:
89
+ """
90
+ Data set for checking a special function.
91
+
92
+ Parameters
93
+ ----------
94
+ func : function
95
+ Function to test
96
+ data : numpy array
97
+ columnar data to use for testing
98
+ param_columns : int or tuple of ints
99
+ Columns indices in which the parameters to `func` lie.
100
+ Can be imaginary integers to indicate that the parameter
101
+ should be cast to complex.
102
+ result_columns : int or tuple of ints, optional
103
+ Column indices for expected results from `func`.
104
+ result_func : callable, optional
105
+ Function to call to obtain results.
106
+ rtol : float, optional
107
+ Required relative tolerance. Default is 5*eps.
108
+ atol : float, optional
109
+ Required absolute tolerance. Default is 5*tiny.
110
+ param_filter : function, or tuple of functions/Nones, optional
111
+ Filter functions to exclude some parameter ranges.
112
+ If omitted, no filtering is done.
113
+ knownfailure : str, optional
114
+ Known failure error message to raise when the test is run.
115
+ If omitted, no exception is raised.
116
+ nan_ok : bool, optional
117
+ If nan is always an accepted result.
118
+ vectorized : bool, optional
119
+ Whether all functions passed in are vectorized.
120
+ ignore_inf_sign : bool, optional
121
+ Whether to ignore signs of infinities.
122
+ (Doesn't matter for complex-valued functions.)
123
+ distinguish_nan_and_inf : bool, optional
124
+ If True, treat numbers which contain nans or infs as
125
+ equal. Sets ignore_inf_sign to be True.
126
+
127
+ """
128
+
129
+ def __init__(self, func, data, param_columns, result_columns=None,
130
+ result_func=None, rtol=None, atol=None, param_filter=None,
131
+ knownfailure=None, dataname=None, nan_ok=False, vectorized=True,
132
+ ignore_inf_sign=False, distinguish_nan_and_inf=True):
133
+ self.func = func
134
+ self.data = data
135
+ self.dataname = dataname
136
+ if not hasattr(param_columns, '__len__'):
137
+ param_columns = (param_columns,)
138
+ self.param_columns = tuple(param_columns)
139
+ if result_columns is not None:
140
+ if not hasattr(result_columns, '__len__'):
141
+ result_columns = (result_columns,)
142
+ self.result_columns = tuple(result_columns)
143
+ if result_func is not None:
144
+ message = "Only result_func or result_columns should be provided"
145
+ raise ValueError(message)
146
+ elif result_func is not None:
147
+ self.result_columns = None
148
+ else:
149
+ raise ValueError("Either result_func or result_columns should be provided")
150
+ self.result_func = result_func
151
+ self.rtol = rtol
152
+ self.atol = atol
153
+ if not hasattr(param_filter, '__len__'):
154
+ param_filter = (param_filter,)
155
+ self.param_filter = param_filter
156
+ self.knownfailure = knownfailure
157
+ self.nan_ok = nan_ok
158
+ self.vectorized = vectorized
159
+ self.ignore_inf_sign = ignore_inf_sign
160
+ self.distinguish_nan_and_inf = distinguish_nan_and_inf
161
+ if not self.distinguish_nan_and_inf:
162
+ self.ignore_inf_sign = True
163
+
164
+ def get_tolerances(self, dtype):
165
+ if not np.issubdtype(dtype, np.inexact):
166
+ dtype = np.dtype(float)
167
+ info = np.finfo(dtype)
168
+ rtol, atol = self.rtol, self.atol
169
+ if rtol is None:
170
+ rtol = 5*info.eps
171
+ if atol is None:
172
+ atol = 5*info.tiny
173
+ return rtol, atol
174
+
175
+ def check(self, data=None, dtype=None, dtypes=None):
176
+ """Check the special function against the data."""
177
+ __tracebackhide__ = operator.methodcaller(
178
+ 'errisinstance', AssertionError
179
+ )
180
+
181
+ if self.knownfailure:
182
+ pytest.xfail(reason=self.knownfailure)
183
+
184
+ if data is None:
185
+ data = self.data
186
+
187
+ if dtype is None:
188
+ dtype = data.dtype
189
+ else:
190
+ data = data.astype(dtype)
191
+
192
+ rtol, atol = self.get_tolerances(dtype)
193
+
194
+ # Apply given filter functions
195
+ if self.param_filter:
196
+ param_mask = np.ones((data.shape[0],), np.bool_)
197
+ for j, filter in zip(self.param_columns, self.param_filter):
198
+ if filter:
199
+ param_mask &= list(filter(data[:,j]))
200
+ data = data[param_mask]
201
+
202
+ # Pick parameters from the correct columns
203
+ params = []
204
+ for idx, j in enumerate(self.param_columns):
205
+ if np.iscomplexobj(j):
206
+ j = int(j.imag)
207
+ params.append(data[:,j].astype(complex))
208
+ elif dtypes and idx < len(dtypes):
209
+ params.append(data[:, j].astype(dtypes[idx]))
210
+ else:
211
+ params.append(data[:,j])
212
+
213
+ # Helper for evaluating results
214
+ def eval_func_at_params(func, skip_mask=None):
215
+ if self.vectorized:
216
+ got = func(*params)
217
+ else:
218
+ got = []
219
+ for j in range(len(params[0])):
220
+ if skip_mask is not None and skip_mask[j]:
221
+ got.append(np.nan)
222
+ continue
223
+ got.append(func(*tuple([params[i][j] for i in range(len(params))])))
224
+ got = np.asarray(got)
225
+ if not isinstance(got, tuple):
226
+ got = (got,)
227
+ return got
228
+
229
+ # Evaluate function to be tested
230
+ got = eval_func_at_params(self.func)
231
+
232
+ # Grab the correct results
233
+ if self.result_columns is not None:
234
+ # Correct results passed in with the data
235
+ wanted = tuple([data[:,icol] for icol in self.result_columns])
236
+ else:
237
+ # Function producing correct results passed in
238
+ skip_mask = None
239
+ if self.nan_ok and len(got) == 1:
240
+ # Don't spend time evaluating what doesn't need to be evaluated
241
+ skip_mask = np.isnan(got[0])
242
+ wanted = eval_func_at_params(self.result_func, skip_mask=skip_mask)
243
+
244
+ # Check the validity of each output returned
245
+ assert_(len(got) == len(wanted))
246
+
247
+ for output_num, (x, y) in enumerate(zip(got, wanted)):
248
+ if np.issubdtype(x.dtype, np.complexfloating) or self.ignore_inf_sign:
249
+ pinf_x = np.isinf(x)
250
+ pinf_y = np.isinf(y)
251
+ minf_x = np.isinf(x)
252
+ minf_y = np.isinf(y)
253
+ else:
254
+ pinf_x = np.isposinf(x)
255
+ pinf_y = np.isposinf(y)
256
+ minf_x = np.isneginf(x)
257
+ minf_y = np.isneginf(y)
258
+ nan_x = np.isnan(x)
259
+ nan_y = np.isnan(y)
260
+
261
+ with np.errstate(all='ignore'):
262
+ abs_y = np.absolute(y)
263
+ abs_y[~np.isfinite(abs_y)] = 0
264
+ diff = np.absolute(x - y)
265
+ diff[~np.isfinite(diff)] = 0
266
+
267
+ rdiff = diff / np.absolute(y)
268
+ rdiff[~np.isfinite(rdiff)] = 0
269
+
270
+ tol_mask = (diff <= atol + rtol*abs_y)
271
+ pinf_mask = (pinf_x == pinf_y)
272
+ minf_mask = (minf_x == minf_y)
273
+
274
+ nan_mask = (nan_x == nan_y)
275
+
276
+ bad_j = ~(tol_mask & pinf_mask & minf_mask & nan_mask)
277
+
278
+ point_count = bad_j.size
279
+ if self.nan_ok:
280
+ bad_j &= ~nan_x
281
+ bad_j &= ~nan_y
282
+ point_count -= (nan_x | nan_y).sum()
283
+
284
+ if not self.distinguish_nan_and_inf and not self.nan_ok:
285
+ # If nan's are okay we've already covered all these cases
286
+ inf_x = np.isinf(x)
287
+ inf_y = np.isinf(y)
288
+ both_nonfinite = (inf_x & nan_y) | (nan_x & inf_y)
289
+ bad_j &= ~both_nonfinite
290
+ point_count -= both_nonfinite.sum()
291
+
292
+ if np.any(bad_j):
293
+ # Some bad results: inform what, where, and how bad
294
+ msg = [""]
295
+ msg.append("Max |adiff|: %g" % diff[bad_j].max())
296
+ msg.append("Max |rdiff|: %g" % rdiff[bad_j].max())
297
+ msg.append("Bad results (%d out of %d) for the following points "
298
+ "(in output %d):"
299
+ % (np.sum(bad_j), point_count, output_num,))
300
+ for j in np.nonzero(bad_j)[0]:
301
+ j = int(j)
302
+ def fmt(x):
303
+ return '%30s' % np.array2string(x[j], precision=18)
304
+ a = " ".join(map(fmt, params))
305
+ b = " ".join(map(fmt, got))
306
+ c = " ".join(map(fmt, wanted))
307
+ d = fmt(rdiff)
308
+ msg.append(f"{a} => {b} != {c} (rdiff {d})")
309
+ assert_(False, "\n".join(msg))
310
+
311
+ def __repr__(self):
312
+ """Pretty-printing, esp. for Nose output"""
313
+ if np.any(list(map(np.iscomplexobj, self.param_columns))):
314
+ is_complex = " (complex)"
315
+ else:
316
+ is_complex = ""
317
+ if self.dataname:
318
+ return "<Data for {}{}: {}>".format(self.func.__name__, is_complex,
319
+ os.path.basename(self.dataname))
320
+ else:
321
+ return f"<Data for {self.func.__name__}{is_complex}>"
.venv/Lib/site-packages/scipy/special/_ufuncs.cp39-win_amd64.dll.a ADDED
Binary file (1.54 kB). View file
 
.venv/Lib/site-packages/scipy/special/_ufuncs.pyi ADDED
@@ -0,0 +1,526 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This file is automatically generated by _generate_pyx.py.
2
+ # Do not edit manually!
3
+
4
+ from typing import Any, Dict
5
+
6
+ import numpy as np
7
+
8
+ __all__ = [
9
+ 'geterr',
10
+ 'seterr',
11
+ 'errstate',
12
+ 'agm',
13
+ 'airy',
14
+ 'airye',
15
+ 'bdtr',
16
+ 'bdtrc',
17
+ 'bdtri',
18
+ 'bdtrik',
19
+ 'bdtrin',
20
+ 'bei',
21
+ 'beip',
22
+ 'ber',
23
+ 'berp',
24
+ 'besselpoly',
25
+ 'beta',
26
+ 'betainc',
27
+ 'betaincc',
28
+ 'betainccinv',
29
+ 'betaincinv',
30
+ 'betaln',
31
+ 'binom',
32
+ 'boxcox',
33
+ 'boxcox1p',
34
+ 'btdtr',
35
+ 'btdtri',
36
+ 'btdtria',
37
+ 'btdtrib',
38
+ 'cbrt',
39
+ 'chdtr',
40
+ 'chdtrc',
41
+ 'chdtri',
42
+ 'chdtriv',
43
+ 'chndtr',
44
+ 'chndtridf',
45
+ 'chndtrinc',
46
+ 'chndtrix',
47
+ 'cosdg',
48
+ 'cosm1',
49
+ 'cotdg',
50
+ 'dawsn',
51
+ 'ellipe',
52
+ 'ellipeinc',
53
+ 'ellipj',
54
+ 'ellipk',
55
+ 'ellipkinc',
56
+ 'ellipkm1',
57
+ 'elliprc',
58
+ 'elliprd',
59
+ 'elliprf',
60
+ 'elliprg',
61
+ 'elliprj',
62
+ 'entr',
63
+ 'erf',
64
+ 'erfc',
65
+ 'erfcinv',
66
+ 'erfcx',
67
+ 'erfi',
68
+ 'erfinv',
69
+ 'eval_chebyc',
70
+ 'eval_chebys',
71
+ 'eval_chebyt',
72
+ 'eval_chebyu',
73
+ 'eval_gegenbauer',
74
+ 'eval_genlaguerre',
75
+ 'eval_hermite',
76
+ 'eval_hermitenorm',
77
+ 'eval_jacobi',
78
+ 'eval_laguerre',
79
+ 'eval_legendre',
80
+ 'eval_sh_chebyt',
81
+ 'eval_sh_chebyu',
82
+ 'eval_sh_jacobi',
83
+ 'eval_sh_legendre',
84
+ 'exp1',
85
+ 'exp10',
86
+ 'exp2',
87
+ 'expi',
88
+ 'expit',
89
+ 'expm1',
90
+ 'expn',
91
+ 'exprel',
92
+ 'fdtr',
93
+ 'fdtrc',
94
+ 'fdtri',
95
+ 'fdtridfd',
96
+ 'fresnel',
97
+ 'gamma',
98
+ 'gammainc',
99
+ 'gammaincc',
100
+ 'gammainccinv',
101
+ 'gammaincinv',
102
+ 'gammaln',
103
+ 'gammasgn',
104
+ 'gdtr',
105
+ 'gdtrc',
106
+ 'gdtria',
107
+ 'gdtrib',
108
+ 'gdtrix',
109
+ 'hankel1',
110
+ 'hankel1e',
111
+ 'hankel2',
112
+ 'hankel2e',
113
+ 'huber',
114
+ 'hyp0f1',
115
+ 'hyp1f1',
116
+ 'hyp2f1',
117
+ 'hyperu',
118
+ 'i0',
119
+ 'i0e',
120
+ 'i1',
121
+ 'i1e',
122
+ 'inv_boxcox',
123
+ 'inv_boxcox1p',
124
+ 'it2i0k0',
125
+ 'it2j0y0',
126
+ 'it2struve0',
127
+ 'itairy',
128
+ 'iti0k0',
129
+ 'itj0y0',
130
+ 'itmodstruve0',
131
+ 'itstruve0',
132
+ 'iv',
133
+ 'ive',
134
+ 'j0',
135
+ 'j1',
136
+ 'jn',
137
+ 'jv',
138
+ 'jve',
139
+ 'k0',
140
+ 'k0e',
141
+ 'k1',
142
+ 'k1e',
143
+ 'kei',
144
+ 'keip',
145
+ 'kelvin',
146
+ 'ker',
147
+ 'kerp',
148
+ 'kl_div',
149
+ 'kn',
150
+ 'kolmogi',
151
+ 'kolmogorov',
152
+ 'kv',
153
+ 'kve',
154
+ 'log1p',
155
+ 'log_expit',
156
+ 'log_ndtr',
157
+ 'loggamma',
158
+ 'logit',
159
+ 'lpmv',
160
+ 'mathieu_a',
161
+ 'mathieu_b',
162
+ 'mathieu_cem',
163
+ 'mathieu_modcem1',
164
+ 'mathieu_modcem2',
165
+ 'mathieu_modsem1',
166
+ 'mathieu_modsem2',
167
+ 'mathieu_sem',
168
+ 'modfresnelm',
169
+ 'modfresnelp',
170
+ 'modstruve',
171
+ 'nbdtr',
172
+ 'nbdtrc',
173
+ 'nbdtri',
174
+ 'nbdtrik',
175
+ 'nbdtrin',
176
+ 'ncfdtr',
177
+ 'ncfdtri',
178
+ 'ncfdtridfd',
179
+ 'ncfdtridfn',
180
+ 'ncfdtrinc',
181
+ 'nctdtr',
182
+ 'nctdtridf',
183
+ 'nctdtrinc',
184
+ 'nctdtrit',
185
+ 'ndtr',
186
+ 'ndtri',
187
+ 'ndtri_exp',
188
+ 'nrdtrimn',
189
+ 'nrdtrisd',
190
+ 'obl_ang1',
191
+ 'obl_ang1_cv',
192
+ 'obl_cv',
193
+ 'obl_rad1',
194
+ 'obl_rad1_cv',
195
+ 'obl_rad2',
196
+ 'obl_rad2_cv',
197
+ 'owens_t',
198
+ 'pbdv',
199
+ 'pbvv',
200
+ 'pbwa',
201
+ 'pdtr',
202
+ 'pdtrc',
203
+ 'pdtri',
204
+ 'pdtrik',
205
+ 'poch',
206
+ 'powm1',
207
+ 'pro_ang1',
208
+ 'pro_ang1_cv',
209
+ 'pro_cv',
210
+ 'pro_rad1',
211
+ 'pro_rad1_cv',
212
+ 'pro_rad2',
213
+ 'pro_rad2_cv',
214
+ 'pseudo_huber',
215
+ 'psi',
216
+ 'radian',
217
+ 'rel_entr',
218
+ 'rgamma',
219
+ 'round',
220
+ 'shichi',
221
+ 'sici',
222
+ 'sindg',
223
+ 'smirnov',
224
+ 'smirnovi',
225
+ 'spence',
226
+ 'sph_harm',
227
+ 'stdtr',
228
+ 'stdtridf',
229
+ 'stdtrit',
230
+ 'struve',
231
+ 'tandg',
232
+ 'tklmbda',
233
+ 'voigt_profile',
234
+ 'wofz',
235
+ 'wright_bessel',
236
+ 'wrightomega',
237
+ 'xlog1py',
238
+ 'xlogy',
239
+ 'y0',
240
+ 'y1',
241
+ 'yn',
242
+ 'yv',
243
+ 'yve',
244
+ 'zetac'
245
+ ]
246
+
247
+ def geterr() -> Dict[str, str]: ...
248
+ def seterr(**kwargs: str) -> Dict[str, str]: ...
249
+
250
+ class errstate:
251
+ def __init__(self, **kargs: str) -> None: ...
252
+ def __enter__(self) -> None: ...
253
+ def __exit__(
254
+ self,
255
+ exc_type: Any, # Unused
256
+ exc_value: Any, # Unused
257
+ traceback: Any, # Unused
258
+ ) -> None: ...
259
+
260
+ _cosine_cdf: np.ufunc
261
+ _cosine_invcdf: np.ufunc
262
+ _cospi: np.ufunc
263
+ _ellip_harm: np.ufunc
264
+ _factorial: np.ufunc
265
+ _igam_fac: np.ufunc
266
+ _kolmogc: np.ufunc
267
+ _kolmogci: np.ufunc
268
+ _kolmogp: np.ufunc
269
+ _lambertw: np.ufunc
270
+ _lanczos_sum_expg_scaled: np.ufunc
271
+ _lgam1p: np.ufunc
272
+ _log1pmx: np.ufunc
273
+ _riemann_zeta: np.ufunc
274
+ _scaled_exp1: np.ufunc
275
+ _sf_error_test_function: np.ufunc
276
+ _sinpi: np.ufunc
277
+ _smirnovc: np.ufunc
278
+ _smirnovci: np.ufunc
279
+ _smirnovp: np.ufunc
280
+ _spherical_in: np.ufunc
281
+ _spherical_in_d: np.ufunc
282
+ _spherical_jn: np.ufunc
283
+ _spherical_jn_d: np.ufunc
284
+ _spherical_kn: np.ufunc
285
+ _spherical_kn_d: np.ufunc
286
+ _spherical_yn: np.ufunc
287
+ _spherical_yn_d: np.ufunc
288
+ _stirling2_inexact: np.ufunc
289
+ _struve_asymp_large_z: np.ufunc
290
+ _struve_bessel_series: np.ufunc
291
+ _struve_power_series: np.ufunc
292
+ _zeta: np.ufunc
293
+ agm: np.ufunc
294
+ airy: np.ufunc
295
+ airye: np.ufunc
296
+ bdtr: np.ufunc
297
+ bdtrc: np.ufunc
298
+ bdtri: np.ufunc
299
+ bdtrik: np.ufunc
300
+ bdtrin: np.ufunc
301
+ bei: np.ufunc
302
+ beip: np.ufunc
303
+ ber: np.ufunc
304
+ berp: np.ufunc
305
+ besselpoly: np.ufunc
306
+ beta: np.ufunc
307
+ betainc: np.ufunc
308
+ betaincc: np.ufunc
309
+ betainccinv: np.ufunc
310
+ betaincinv: np.ufunc
311
+ betaln: np.ufunc
312
+ binom: np.ufunc
313
+ boxcox1p: np.ufunc
314
+ boxcox: np.ufunc
315
+ btdtr: np.ufunc
316
+ btdtri: np.ufunc
317
+ btdtria: np.ufunc
318
+ btdtrib: np.ufunc
319
+ cbrt: np.ufunc
320
+ chdtr: np.ufunc
321
+ chdtrc: np.ufunc
322
+ chdtri: np.ufunc
323
+ chdtriv: np.ufunc
324
+ chndtr: np.ufunc
325
+ chndtridf: np.ufunc
326
+ chndtrinc: np.ufunc
327
+ chndtrix: np.ufunc
328
+ cosdg: np.ufunc
329
+ cosm1: np.ufunc
330
+ cotdg: np.ufunc
331
+ dawsn: np.ufunc
332
+ ellipe: np.ufunc
333
+ ellipeinc: np.ufunc
334
+ ellipj: np.ufunc
335
+ ellipk: np.ufunc
336
+ ellipkinc: np.ufunc
337
+ ellipkm1: np.ufunc
338
+ elliprc: np.ufunc
339
+ elliprd: np.ufunc
340
+ elliprf: np.ufunc
341
+ elliprg: np.ufunc
342
+ elliprj: np.ufunc
343
+ entr: np.ufunc
344
+ erf: np.ufunc
345
+ erfc: np.ufunc
346
+ erfcinv: np.ufunc
347
+ erfcx: np.ufunc
348
+ erfi: np.ufunc
349
+ erfinv: np.ufunc
350
+ eval_chebyc: np.ufunc
351
+ eval_chebys: np.ufunc
352
+ eval_chebyt: np.ufunc
353
+ eval_chebyu: np.ufunc
354
+ eval_gegenbauer: np.ufunc
355
+ eval_genlaguerre: np.ufunc
356
+ eval_hermite: np.ufunc
357
+ eval_hermitenorm: np.ufunc
358
+ eval_jacobi: np.ufunc
359
+ eval_laguerre: np.ufunc
360
+ eval_legendre: np.ufunc
361
+ eval_sh_chebyt: np.ufunc
362
+ eval_sh_chebyu: np.ufunc
363
+ eval_sh_jacobi: np.ufunc
364
+ eval_sh_legendre: np.ufunc
365
+ exp10: np.ufunc
366
+ exp1: np.ufunc
367
+ exp2: np.ufunc
368
+ expi: np.ufunc
369
+ expit: np.ufunc
370
+ expm1: np.ufunc
371
+ expn: np.ufunc
372
+ exprel: np.ufunc
373
+ fdtr: np.ufunc
374
+ fdtrc: np.ufunc
375
+ fdtri: np.ufunc
376
+ fdtridfd: np.ufunc
377
+ fresnel: np.ufunc
378
+ gamma: np.ufunc
379
+ gammainc: np.ufunc
380
+ gammaincc: np.ufunc
381
+ gammainccinv: np.ufunc
382
+ gammaincinv: np.ufunc
383
+ gammaln: np.ufunc
384
+ gammasgn: np.ufunc
385
+ gdtr: np.ufunc
386
+ gdtrc: np.ufunc
387
+ gdtria: np.ufunc
388
+ gdtrib: np.ufunc
389
+ gdtrix: np.ufunc
390
+ hankel1: np.ufunc
391
+ hankel1e: np.ufunc
392
+ hankel2: np.ufunc
393
+ hankel2e: np.ufunc
394
+ huber: np.ufunc
395
+ hyp0f1: np.ufunc
396
+ hyp1f1: np.ufunc
397
+ hyp2f1: np.ufunc
398
+ hyperu: np.ufunc
399
+ i0: np.ufunc
400
+ i0e: np.ufunc
401
+ i1: np.ufunc
402
+ i1e: np.ufunc
403
+ inv_boxcox1p: np.ufunc
404
+ inv_boxcox: np.ufunc
405
+ it2i0k0: np.ufunc
406
+ it2j0y0: np.ufunc
407
+ it2struve0: np.ufunc
408
+ itairy: np.ufunc
409
+ iti0k0: np.ufunc
410
+ itj0y0: np.ufunc
411
+ itmodstruve0: np.ufunc
412
+ itstruve0: np.ufunc
413
+ iv: np.ufunc
414
+ ive: np.ufunc
415
+ j0: np.ufunc
416
+ j1: np.ufunc
417
+ jn: np.ufunc
418
+ jv: np.ufunc
419
+ jve: np.ufunc
420
+ k0: np.ufunc
421
+ k0e: np.ufunc
422
+ k1: np.ufunc
423
+ k1e: np.ufunc
424
+ kei: np.ufunc
425
+ keip: np.ufunc
426
+ kelvin: np.ufunc
427
+ ker: np.ufunc
428
+ kerp: np.ufunc
429
+ kl_div: np.ufunc
430
+ kn: np.ufunc
431
+ kolmogi: np.ufunc
432
+ kolmogorov: np.ufunc
433
+ kv: np.ufunc
434
+ kve: np.ufunc
435
+ log1p: np.ufunc
436
+ log_expit: np.ufunc
437
+ log_ndtr: np.ufunc
438
+ loggamma: np.ufunc
439
+ logit: np.ufunc
440
+ lpmv: np.ufunc
441
+ mathieu_a: np.ufunc
442
+ mathieu_b: np.ufunc
443
+ mathieu_cem: np.ufunc
444
+ mathieu_modcem1: np.ufunc
445
+ mathieu_modcem2: np.ufunc
446
+ mathieu_modsem1: np.ufunc
447
+ mathieu_modsem2: np.ufunc
448
+ mathieu_sem: np.ufunc
449
+ modfresnelm: np.ufunc
450
+ modfresnelp: np.ufunc
451
+ modstruve: np.ufunc
452
+ nbdtr: np.ufunc
453
+ nbdtrc: np.ufunc
454
+ nbdtri: np.ufunc
455
+ nbdtrik: np.ufunc
456
+ nbdtrin: np.ufunc
457
+ ncfdtr: np.ufunc
458
+ ncfdtri: np.ufunc
459
+ ncfdtridfd: np.ufunc
460
+ ncfdtridfn: np.ufunc
461
+ ncfdtrinc: np.ufunc
462
+ nctdtr: np.ufunc
463
+ nctdtridf: np.ufunc
464
+ nctdtrinc: np.ufunc
465
+ nctdtrit: np.ufunc
466
+ ndtr: np.ufunc
467
+ ndtri: np.ufunc
468
+ ndtri_exp: np.ufunc
469
+ nrdtrimn: np.ufunc
470
+ nrdtrisd: np.ufunc
471
+ obl_ang1: np.ufunc
472
+ obl_ang1_cv: np.ufunc
473
+ obl_cv: np.ufunc
474
+ obl_rad1: np.ufunc
475
+ obl_rad1_cv: np.ufunc
476
+ obl_rad2: np.ufunc
477
+ obl_rad2_cv: np.ufunc
478
+ owens_t: np.ufunc
479
+ pbdv: np.ufunc
480
+ pbvv: np.ufunc
481
+ pbwa: np.ufunc
482
+ pdtr: np.ufunc
483
+ pdtrc: np.ufunc
484
+ pdtri: np.ufunc
485
+ pdtrik: np.ufunc
486
+ poch: np.ufunc
487
+ powm1: np.ufunc
488
+ pro_ang1: np.ufunc
489
+ pro_ang1_cv: np.ufunc
490
+ pro_cv: np.ufunc
491
+ pro_rad1: np.ufunc
492
+ pro_rad1_cv: np.ufunc
493
+ pro_rad2: np.ufunc
494
+ pro_rad2_cv: np.ufunc
495
+ pseudo_huber: np.ufunc
496
+ psi: np.ufunc
497
+ radian: np.ufunc
498
+ rel_entr: np.ufunc
499
+ rgamma: np.ufunc
500
+ round: np.ufunc
501
+ shichi: np.ufunc
502
+ sici: np.ufunc
503
+ sindg: np.ufunc
504
+ smirnov: np.ufunc
505
+ smirnovi: np.ufunc
506
+ spence: np.ufunc
507
+ sph_harm: np.ufunc
508
+ stdtr: np.ufunc
509
+ stdtridf: np.ufunc
510
+ stdtrit: np.ufunc
511
+ struve: np.ufunc
512
+ tandg: np.ufunc
513
+ tklmbda: np.ufunc
514
+ voigt_profile: np.ufunc
515
+ wofz: np.ufunc
516
+ wright_bessel: np.ufunc
517
+ wrightomega: np.ufunc
518
+ xlog1py: np.ufunc
519
+ xlogy: np.ufunc
520
+ y0: np.ufunc
521
+ y1: np.ufunc
522
+ yn: np.ufunc
523
+ yv: np.ufunc
524
+ yve: np.ufunc
525
+ zetac: np.ufunc
526
+