Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/scipy/spatial/_geometric_slerp.py: 17%
52 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-12 06:31 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-12 06:31 +0000
1from __future__ import annotations
3__all__ = ['geometric_slerp']
5import warnings
6from typing import TYPE_CHECKING
8import numpy as np
9from scipy.spatial.distance import euclidean
11if TYPE_CHECKING:
12 import numpy.typing as npt
15def _geometric_slerp(start, end, t):
16 # create an orthogonal basis using QR decomposition
17 basis = np.vstack([start, end])
18 Q, R = np.linalg.qr(basis.T)
19 signs = 2 * (np.diag(R) >= 0) - 1
20 Q = Q.T * signs.T[:, np.newaxis]
21 R = R.T * signs.T[:, np.newaxis]
23 # calculate the angle between `start` and `end`
24 c = np.dot(start, end)
25 s = np.linalg.det(R)
26 omega = np.arctan2(s, c)
28 # interpolate
29 start, end = Q
30 s = np.sin(t * omega)
31 c = np.cos(t * omega)
32 return start * c[:, np.newaxis] + end * s[:, np.newaxis]
35def geometric_slerp(
36 start: npt.ArrayLike,
37 end: npt.ArrayLike,
38 t: npt.ArrayLike,
39 tol: float = 1e-7,
40) -> np.ndarray:
41 """
42 Geometric spherical linear interpolation.
44 The interpolation occurs along a unit-radius
45 great circle arc in arbitrary dimensional space.
47 Parameters
48 ----------
49 start : (n_dimensions, ) array-like
50 Single n-dimensional input coordinate in a 1-D array-like
51 object. `n` must be greater than 1.
52 end : (n_dimensions, ) array-like
53 Single n-dimensional input coordinate in a 1-D array-like
54 object. `n` must be greater than 1.
55 t : float or (n_points,) 1D array-like
56 A float or 1D array-like of doubles representing interpolation
57 parameters, with values required in the inclusive interval
58 between 0 and 1. A common approach is to generate the array
59 with ``np.linspace(0, 1, n_pts)`` for linearly spaced points.
60 Ascending, descending, and scrambled orders are permitted.
61 tol : float
62 The absolute tolerance for determining if the start and end
63 coordinates are antipodes.
65 Returns
66 -------
67 result : (t.size, D)
68 An array of doubles containing the interpolated
69 spherical path and including start and
70 end when 0 and 1 t are used. The
71 interpolated values should correspond to the
72 same sort order provided in the t array. The result
73 may be 1-dimensional if ``t`` is a float.
75 Raises
76 ------
77 ValueError
78 If ``start`` and ``end`` are antipodes, not on the
79 unit n-sphere, or for a variety of degenerate conditions.
81 See Also
82 --------
83 scipy.spatial.transform.Slerp : 3-D Slerp that works with quaternions
85 Notes
86 -----
87 The implementation is based on the mathematical formula provided in [1]_,
88 and the first known presentation of this algorithm, derived from study of
89 4-D geometry, is credited to Glenn Davis in a footnote of the original
90 quaternion Slerp publication by Ken Shoemake [2]_.
92 .. versionadded:: 1.5.0
94 References
95 ----------
96 .. [1] https://en.wikipedia.org/wiki/Slerp#Geometric_Slerp
97 .. [2] Ken Shoemake (1985) Animating rotation with quaternion curves.
98 ACM SIGGRAPH Computer Graphics, 19(3): 245-254.
100 Examples
101 --------
102 Interpolate four linearly-spaced values on the circumference of
103 a circle spanning 90 degrees:
105 >>> import numpy as np
106 >>> from scipy.spatial import geometric_slerp
107 >>> import matplotlib.pyplot as plt
108 >>> fig = plt.figure()
109 >>> ax = fig.add_subplot(111)
110 >>> start = np.array([1, 0])
111 >>> end = np.array([0, 1])
112 >>> t_vals = np.linspace(0, 1, 4)
113 >>> result = geometric_slerp(start,
114 ... end,
115 ... t_vals)
117 The interpolated results should be at 30 degree intervals
118 recognizable on the unit circle:
120 >>> ax.scatter(result[...,0], result[...,1], c='k')
121 >>> circle = plt.Circle((0, 0), 1, color='grey')
122 >>> ax.add_artist(circle)
123 >>> ax.set_aspect('equal')
124 >>> plt.show()
126 Attempting to interpolate between antipodes on a circle is
127 ambiguous because there are two possible paths, and on a
128 sphere there are infinite possible paths on the geodesic surface.
129 Nonetheless, one of the ambiguous paths is returned along
130 with a warning:
132 >>> opposite_pole = np.array([-1, 0])
133 >>> with np.testing.suppress_warnings() as sup:
134 ... sup.filter(UserWarning)
135 ... geometric_slerp(start,
136 ... opposite_pole,
137 ... t_vals)
138 array([[ 1.00000000e+00, 0.00000000e+00],
139 [ 5.00000000e-01, 8.66025404e-01],
140 [-5.00000000e-01, 8.66025404e-01],
141 [-1.00000000e+00, 1.22464680e-16]])
143 Extend the original example to a sphere and plot interpolation
144 points in 3D:
146 >>> from mpl_toolkits.mplot3d import proj3d
147 >>> fig = plt.figure()
148 >>> ax = fig.add_subplot(111, projection='3d')
150 Plot the unit sphere for reference (optional):
152 >>> u = np.linspace(0, 2 * np.pi, 100)
153 >>> v = np.linspace(0, np.pi, 100)
154 >>> x = np.outer(np.cos(u), np.sin(v))
155 >>> y = np.outer(np.sin(u), np.sin(v))
156 >>> z = np.outer(np.ones(np.size(u)), np.cos(v))
157 >>> ax.plot_surface(x, y, z, color='y', alpha=0.1)
159 Interpolating over a larger number of points
160 may provide the appearance of a smooth curve on
161 the surface of the sphere, which is also useful
162 for discretized integration calculations on a
163 sphere surface:
165 >>> start = np.array([1, 0, 0])
166 >>> end = np.array([0, 0, 1])
167 >>> t_vals = np.linspace(0, 1, 200)
168 >>> result = geometric_slerp(start,
169 ... end,
170 ... t_vals)
171 >>> ax.plot(result[...,0],
172 ... result[...,1],
173 ... result[...,2],
174 ... c='k')
175 >>> plt.show()
176 """
178 start = np.asarray(start, dtype=np.float64)
179 end = np.asarray(end, dtype=np.float64)
180 t = np.asarray(t)
182 if t.ndim > 1:
183 raise ValueError("The interpolation parameter "
184 "value must be one dimensional.")
186 if start.ndim != 1 or end.ndim != 1:
187 raise ValueError("Start and end coordinates "
188 "must be one-dimensional")
190 if start.size != end.size:
191 raise ValueError("The dimensions of start and "
192 "end must match (have same size)")
194 if start.size < 2 or end.size < 2:
195 raise ValueError("The start and end coordinates must "
196 "both be in at least two-dimensional "
197 "space")
199 if np.array_equal(start, end):
200 return np.linspace(start, start, t.size)
202 # for points that violate equation for n-sphere
203 for coord in [start, end]:
204 if not np.allclose(np.linalg.norm(coord), 1.0,
205 rtol=1e-9,
206 atol=0):
207 raise ValueError("start and end are not"
208 " on a unit n-sphere")
210 if not isinstance(tol, float):
211 raise ValueError("tol must be a float")
212 else:
213 tol = np.fabs(tol)
215 coord_dist = euclidean(start, end)
217 # diameter of 2 within tolerance means antipodes, which is a problem
218 # for all unit n-spheres (even the 0-sphere would have an ambiguous path)
219 if np.allclose(coord_dist, 2.0, rtol=0, atol=tol):
220 warnings.warn("start and end are antipodes"
221 " using the specified tolerance;"
222 " this may cause ambiguous slerp paths")
224 t = np.asarray(t, dtype=np.float64)
226 if t.size == 0:
227 return np.empty((0, start.size))
229 if t.min() < 0 or t.max() > 1:
230 raise ValueError("interpolation parameter must be in [0, 1]")
232 if t.ndim == 0:
233 return _geometric_slerp(start,
234 end,
235 np.atleast_1d(t)).ravel()
236 else:
237 return _geometric_slerp(start,
238 end,
239 t)