Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .gitattributes +2 -0
- .venv/Lib/site-packages/scipy/signal/_max_len_seq_inner.cp39-win_amd64.pyd +3 -0
- .venv/Lib/site-packages/scipy/sparse/_sparsetools.cp39-win_amd64.pyd +3 -0
- .venv/Lib/site-packages/scipy/spatial/__pycache__/__init__.cpython-39.pyc +0 -0
- .venv/Lib/site-packages/scipy/spatial/__pycache__/_geometric_slerp.cpython-39.pyc +0 -0
- .venv/Lib/site-packages/scipy/spatial/__pycache__/_kdtree.cpython-39.pyc +0 -0
- .venv/Lib/site-packages/scipy/spatial/__pycache__/_plotutils.cpython-39.pyc +0 -0
- .venv/Lib/site-packages/scipy/spatial/__pycache__/_procrustes.cpython-39.pyc +0 -0
- .venv/Lib/site-packages/scipy/spatial/__pycache__/_spherical_voronoi.cpython-39.pyc +0 -0
- .venv/Lib/site-packages/scipy/spatial/__pycache__/ckdtree.cpython-39.pyc +0 -0
- .venv/Lib/site-packages/scipy/spatial/__pycache__/distance.cpython-39.pyc +0 -0
- .venv/Lib/site-packages/scipy/spatial/__pycache__/kdtree.cpython-39.pyc +0 -0
- .venv/Lib/site-packages/scipy/spatial/__pycache__/qhull.cpython-39.pyc +0 -0
- .venv/Lib/site-packages/scipy/spatial/transform/__init__.py +29 -0
- .venv/Lib/site-packages/scipy/spatial/transform/__pycache__/__init__.cpython-39.pyc +0 -0
- .venv/Lib/site-packages/scipy/spatial/transform/__pycache__/_rotation_groups.cpython-39.pyc +0 -0
- .venv/Lib/site-packages/scipy/spatial/transform/__pycache__/_rotation_spline.cpython-39.pyc +0 -0
- .venv/Lib/site-packages/scipy/spatial/transform/__pycache__/rotation.cpython-39.pyc +0 -0
- .venv/Lib/site-packages/scipy/spatial/transform/_rotation_groups.py +140 -0
- .venv/Lib/site-packages/scipy/spatial/transform/_rotation_spline.py +460 -0
- .venv/Lib/site-packages/scipy/spatial/transform/tests/__init__.py +0 -0
- .venv/Lib/site-packages/scipy/spatial/transform/tests/test_rotation.py +1906 -0
- .venv/Lib/site-packages/scipy/spatial/transform/tests/test_rotation_groups.py +169 -0
- .venv/Lib/site-packages/scipy/spatial/transform/tests/test_rotation_spline.py +162 -0
- .venv/Lib/site-packages/scipy/special/_add_newdocs.py +0 -0
- .venv/Lib/site-packages/scipy/special/_basic.py +0 -0
- .venv/Lib/site-packages/scipy/special/_cdflib.cp39-win_amd64.dll.a +0 -0
- .venv/Lib/site-packages/scipy/special/_cdflib.cp39-win_amd64.pyd +0 -0
- .venv/Lib/site-packages/scipy/special/_comb.cp39-win_amd64.dll.a +0 -0
- .venv/Lib/site-packages/scipy/special/_comb.cp39-win_amd64.pyd +0 -0
- .venv/Lib/site-packages/scipy/special/_ellip_harm.py +214 -0
- .venv/Lib/site-packages/scipy/special/_ellip_harm_2.cp39-win_amd64.dll.a +0 -0
- .venv/Lib/site-packages/scipy/special/_ellip_harm_2.cp39-win_amd64.pyd +0 -0
- .venv/Lib/site-packages/scipy/special/_lambertw.py +149 -0
- .venv/Lib/site-packages/scipy/special/_logsumexp.py +307 -0
- .venv/Lib/site-packages/scipy/special/_mptestutils.py +453 -0
- .venv/Lib/site-packages/scipy/special/_orthogonal.py +2605 -0
- .venv/Lib/site-packages/scipy/special/_orthogonal.pyi +331 -0
- .venv/Lib/site-packages/scipy/special/_sf_error.py +15 -0
- .venv/Lib/site-packages/scipy/special/_specfun.cp39-win_amd64.dll.a +0 -0
- .venv/Lib/site-packages/scipy/special/_specfun.cp39-win_amd64.pyd +0 -0
- .venv/Lib/site-packages/scipy/special/_spfun_stats.py +106 -0
- .venv/Lib/site-packages/scipy/special/_spherical_bessel.py +354 -0
- .venv/Lib/site-packages/scipy/special/_support_alternative_backends.py +75 -0
- .venv/Lib/site-packages/scipy/special/_test_internal.cp39-win_amd64.dll.a +0 -0
- .venv/Lib/site-packages/scipy/special/_test_internal.cp39-win_amd64.pyd +0 -0
- .venv/Lib/site-packages/scipy/special/_test_internal.pyi +9 -0
- .venv/Lib/site-packages/scipy/special/_testutils.py +321 -0
- .venv/Lib/site-packages/scipy/special/_ufuncs.cp39-win_amd64.dll.a +0 -0
- .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 |
+
|