scipy 1.15.3__cp312-cp312-win_amd64.whl → 1.16.0rc2__cp312-cp312-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (759) hide show
  1. scipy/__config__.py +8 -8
  2. scipy/__init__.py +3 -6
  3. scipy/_cyutility.cp312-win_amd64.dll.a +0 -0
  4. scipy/_cyutility.cp312-win_amd64.pyd +0 -0
  5. scipy/_lib/_array_api.py +486 -161
  6. scipy/_lib/_array_api_compat_vendor.py +9 -0
  7. scipy/_lib/_bunch.py +4 -0
  8. scipy/_lib/_ccallback_c.cp312-win_amd64.dll.a +0 -0
  9. scipy/_lib/_ccallback_c.cp312-win_amd64.pyd +0 -0
  10. scipy/_lib/_docscrape.py +1 -1
  11. scipy/_lib/_elementwise_iterative_method.py +15 -26
  12. scipy/_lib/_fpumode.cp312-win_amd64.dll.a +0 -0
  13. scipy/_lib/_fpumode.cp312-win_amd64.pyd +0 -0
  14. scipy/_lib/_sparse.py +41 -0
  15. scipy/_lib/_test_ccallback.cp312-win_amd64.dll.a +0 -0
  16. scipy/_lib/_test_ccallback.cp312-win_amd64.pyd +0 -0
  17. scipy/_lib/_test_deprecation_call.cp312-win_amd64.dll.a +0 -0
  18. scipy/_lib/_test_deprecation_call.cp312-win_amd64.pyd +0 -0
  19. scipy/_lib/_test_deprecation_def.cp312-win_amd64.dll.a +0 -0
  20. scipy/_lib/_test_deprecation_def.cp312-win_amd64.pyd +0 -0
  21. scipy/_lib/_testutils.py +6 -2
  22. scipy/_lib/_uarray/_uarray.cp312-win_amd64.dll.a +0 -0
  23. scipy/_lib/_uarray/_uarray.cp312-win_amd64.pyd +0 -0
  24. scipy/_lib/_util.py +222 -125
  25. scipy/_lib/array_api_compat/__init__.py +4 -4
  26. scipy/_lib/array_api_compat/_internal.py +19 -6
  27. scipy/_lib/array_api_compat/common/__init__.py +1 -1
  28. scipy/_lib/array_api_compat/common/_aliases.py +365 -193
  29. scipy/_lib/array_api_compat/common/_fft.py +94 -64
  30. scipy/_lib/array_api_compat/common/_helpers.py +413 -180
  31. scipy/_lib/array_api_compat/common/_linalg.py +116 -40
  32. scipy/_lib/array_api_compat/common/_typing.py +179 -10
  33. scipy/_lib/array_api_compat/cupy/__init__.py +1 -4
  34. scipy/_lib/array_api_compat/cupy/_aliases.py +61 -41
  35. scipy/_lib/array_api_compat/cupy/_info.py +16 -6
  36. scipy/_lib/array_api_compat/cupy/_typing.py +24 -39
  37. scipy/_lib/array_api_compat/dask/array/__init__.py +6 -3
  38. scipy/_lib/array_api_compat/dask/array/_aliases.py +267 -108
  39. scipy/_lib/array_api_compat/dask/array/_info.py +105 -34
  40. scipy/_lib/array_api_compat/dask/array/fft.py +5 -8
  41. scipy/_lib/array_api_compat/dask/array/linalg.py +21 -22
  42. scipy/_lib/array_api_compat/numpy/__init__.py +13 -15
  43. scipy/_lib/array_api_compat/numpy/_aliases.py +98 -49
  44. scipy/_lib/array_api_compat/numpy/_info.py +36 -16
  45. scipy/_lib/array_api_compat/numpy/_typing.py +27 -43
  46. scipy/_lib/array_api_compat/numpy/fft.py +11 -5
  47. scipy/_lib/array_api_compat/numpy/linalg.py +75 -22
  48. scipy/_lib/array_api_compat/torch/__init__.py +3 -5
  49. scipy/_lib/array_api_compat/torch/_aliases.py +262 -159
  50. scipy/_lib/array_api_compat/torch/_info.py +27 -16
  51. scipy/_lib/array_api_compat/torch/_typing.py +3 -0
  52. scipy/_lib/array_api_compat/torch/fft.py +17 -18
  53. scipy/_lib/array_api_compat/torch/linalg.py +16 -16
  54. scipy/_lib/array_api_extra/__init__.py +26 -3
  55. scipy/_lib/array_api_extra/_delegation.py +171 -0
  56. scipy/_lib/array_api_extra/_lib/__init__.py +1 -0
  57. scipy/_lib/array_api_extra/_lib/_at.py +463 -0
  58. scipy/_lib/array_api_extra/_lib/_backends.py +46 -0
  59. scipy/_lib/array_api_extra/_lib/_funcs.py +937 -0
  60. scipy/_lib/array_api_extra/_lib/_lazy.py +357 -0
  61. scipy/_lib/array_api_extra/_lib/_testing.py +278 -0
  62. scipy/_lib/array_api_extra/_lib/_utils/__init__.py +1 -0
  63. scipy/_lib/array_api_extra/_lib/_utils/_compat.py +74 -0
  64. scipy/_lib/array_api_extra/_lib/_utils/_compat.pyi +45 -0
  65. scipy/_lib/array_api_extra/_lib/_utils/_helpers.py +559 -0
  66. scipy/_lib/array_api_extra/_lib/_utils/_typing.py +10 -0
  67. scipy/_lib/array_api_extra/_lib/_utils/_typing.pyi +105 -0
  68. scipy/_lib/array_api_extra/testing.py +359 -0
  69. scipy/_lib/decorator.py +2 -2
  70. scipy/_lib/doccer.py +1 -7
  71. scipy/_lib/messagestream.cp312-win_amd64.dll.a +0 -0
  72. scipy/_lib/messagestream.cp312-win_amd64.pyd +0 -0
  73. scipy/_lib/pyprima/__init__.py +212 -0
  74. scipy/_lib/pyprima/cobyla/__init__.py +0 -0
  75. scipy/_lib/pyprima/cobyla/cobyla.py +559 -0
  76. scipy/_lib/pyprima/cobyla/cobylb.py +714 -0
  77. scipy/_lib/pyprima/cobyla/geometry.py +226 -0
  78. scipy/_lib/pyprima/cobyla/initialize.py +215 -0
  79. scipy/_lib/pyprima/cobyla/trustregion.py +492 -0
  80. scipy/_lib/pyprima/cobyla/update.py +289 -0
  81. scipy/_lib/pyprima/common/__init__.py +0 -0
  82. scipy/_lib/pyprima/common/_bounds.py +34 -0
  83. scipy/_lib/pyprima/common/_linear_constraints.py +46 -0
  84. scipy/_lib/pyprima/common/_nonlinear_constraints.py +54 -0
  85. scipy/_lib/pyprima/common/_project.py +173 -0
  86. scipy/_lib/pyprima/common/checkbreak.py +93 -0
  87. scipy/_lib/pyprima/common/consts.py +47 -0
  88. scipy/_lib/pyprima/common/evaluate.py +99 -0
  89. scipy/_lib/pyprima/common/history.py +38 -0
  90. scipy/_lib/pyprima/common/infos.py +30 -0
  91. scipy/_lib/pyprima/common/linalg.py +435 -0
  92. scipy/_lib/pyprima/common/message.py +290 -0
  93. scipy/_lib/pyprima/common/powalg.py +131 -0
  94. scipy/_lib/pyprima/common/preproc.py +277 -0
  95. scipy/_lib/pyprima/common/present.py +5 -0
  96. scipy/_lib/pyprima/common/ratio.py +54 -0
  97. scipy/_lib/pyprima/common/redrho.py +47 -0
  98. scipy/_lib/pyprima/common/selectx.py +296 -0
  99. scipy/_lib/tests/test__util.py +105 -121
  100. scipy/_lib/tests/test_array_api.py +166 -35
  101. scipy/_lib/tests/test_bunch.py +7 -0
  102. scipy/_lib/tests/test_ccallback.py +2 -10
  103. scipy/_lib/tests/test_public_api.py +13 -0
  104. scipy/cluster/_hierarchy.cp312-win_amd64.dll.a +0 -0
  105. scipy/cluster/_hierarchy.cp312-win_amd64.pyd +0 -0
  106. scipy/cluster/_optimal_leaf_ordering.cp312-win_amd64.dll.a +0 -0
  107. scipy/cluster/_optimal_leaf_ordering.cp312-win_amd64.pyd +0 -0
  108. scipy/cluster/_vq.cp312-win_amd64.dll.a +0 -0
  109. scipy/cluster/_vq.cp312-win_amd64.pyd +0 -0
  110. scipy/cluster/hierarchy.py +393 -223
  111. scipy/cluster/tests/test_hierarchy.py +273 -335
  112. scipy/cluster/tests/test_vq.py +45 -61
  113. scipy/cluster/vq.py +39 -35
  114. scipy/conftest.py +263 -157
  115. scipy/constants/_constants.py +4 -1
  116. scipy/constants/tests/test_codata.py +2 -2
  117. scipy/constants/tests/test_constants.py +11 -18
  118. scipy/datasets/_download_all.py +15 -1
  119. scipy/datasets/_fetchers.py +7 -1
  120. scipy/datasets/_utils.py +1 -1
  121. scipy/differentiate/_differentiate.py +25 -25
  122. scipy/differentiate/tests/test_differentiate.py +24 -25
  123. scipy/fft/_basic.py +20 -0
  124. scipy/fft/_helper.py +3 -34
  125. scipy/fft/_pocketfft/helper.py +29 -1
  126. scipy/fft/_pocketfft/pypocketfft.cp312-win_amd64.dll.a +0 -0
  127. scipy/fft/_pocketfft/pypocketfft.cp312-win_amd64.pyd +0 -0
  128. scipy/fft/_pocketfft/tests/test_basic.py +2 -4
  129. scipy/fft/_pocketfft/tests/test_real_transforms.py +4 -4
  130. scipy/fft/_realtransforms.py +13 -0
  131. scipy/fft/tests/test_basic.py +27 -25
  132. scipy/fft/tests/test_fftlog.py +16 -7
  133. scipy/fft/tests/test_helper.py +18 -34
  134. scipy/fft/tests/test_real_transforms.py +8 -10
  135. scipy/fftpack/convolve.cp312-win_amd64.dll.a +0 -0
  136. scipy/fftpack/convolve.cp312-win_amd64.pyd +0 -0
  137. scipy/fftpack/tests/test_basic.py +2 -4
  138. scipy/fftpack/tests/test_real_transforms.py +8 -9
  139. scipy/integrate/_bvp.py +9 -3
  140. scipy/integrate/_cubature.py +3 -2
  141. scipy/integrate/_dop.cp312-win_amd64.dll.a +0 -0
  142. scipy/integrate/_dop.cp312-win_amd64.pyd +0 -0
  143. scipy/integrate/_lsoda.cp312-win_amd64.dll.a +0 -0
  144. scipy/integrate/_lsoda.cp312-win_amd64.pyd +0 -0
  145. scipy/integrate/_ode.py +9 -2
  146. scipy/integrate/_odepack.cp312-win_amd64.dll.a +0 -0
  147. scipy/integrate/_odepack.cp312-win_amd64.pyd +0 -0
  148. scipy/integrate/_quad_vec.py +21 -29
  149. scipy/integrate/_quadpack.cp312-win_amd64.dll.a +0 -0
  150. scipy/integrate/_quadpack.cp312-win_amd64.pyd +0 -0
  151. scipy/integrate/_quadpack_py.py +11 -7
  152. scipy/integrate/_quadrature.py +3 -3
  153. scipy/integrate/_rules/_base.py +2 -2
  154. scipy/integrate/_tanhsinh.py +48 -47
  155. scipy/integrate/_test_multivariate.cp312-win_amd64.dll.a +0 -0
  156. scipy/integrate/_test_multivariate.cp312-win_amd64.pyd +0 -0
  157. scipy/integrate/_test_odeint_banded.cp312-win_amd64.dll.a +0 -0
  158. scipy/integrate/_test_odeint_banded.cp312-win_amd64.pyd +0 -0
  159. scipy/integrate/_vode.cp312-win_amd64.dll.a +0 -0
  160. scipy/integrate/_vode.cp312-win_amd64.pyd +0 -0
  161. scipy/integrate/tests/test__quad_vec.py +0 -6
  162. scipy/integrate/tests/test_banded_ode_solvers.py +85 -0
  163. scipy/integrate/tests/test_cubature.py +21 -35
  164. scipy/integrate/tests/test_quadrature.py +6 -8
  165. scipy/integrate/tests/test_tanhsinh.py +56 -48
  166. scipy/interpolate/__init__.py +70 -58
  167. scipy/interpolate/_bary_rational.py +22 -22
  168. scipy/interpolate/_bsplines.py +119 -66
  169. scipy/interpolate/_cubic.py +65 -50
  170. scipy/interpolate/_dfitpack.cp312-win_amd64.dll.a +0 -0
  171. scipy/interpolate/_dfitpack.cp312-win_amd64.pyd +0 -0
  172. scipy/interpolate/_dierckx.cp312-win_amd64.dll.a +0 -0
  173. scipy/interpolate/_dierckx.cp312-win_amd64.pyd +0 -0
  174. scipy/interpolate/_fitpack.cp312-win_amd64.dll.a +0 -0
  175. scipy/interpolate/_fitpack.cp312-win_amd64.pyd +0 -0
  176. scipy/interpolate/_fitpack2.py +9 -6
  177. scipy/interpolate/_fitpack_impl.py +32 -26
  178. scipy/interpolate/_fitpack_repro.py +23 -19
  179. scipy/interpolate/_interpnd.cp312-win_amd64.dll.a +0 -0
  180. scipy/interpolate/_interpnd.cp312-win_amd64.pyd +0 -0
  181. scipy/interpolate/_interpolate.py +30 -12
  182. scipy/interpolate/_ndbspline.py +13 -18
  183. scipy/interpolate/_ndgriddata.py +5 -8
  184. scipy/interpolate/_polyint.py +95 -31
  185. scipy/interpolate/_ppoly.cp312-win_amd64.dll.a +0 -0
  186. scipy/interpolate/_ppoly.cp312-win_amd64.pyd +0 -0
  187. scipy/interpolate/_rbf.py +2 -2
  188. scipy/interpolate/_rbfinterp.py +1 -1
  189. scipy/interpolate/_rbfinterp_pythran.cp312-win_amd64.dll.a +0 -0
  190. scipy/interpolate/_rbfinterp_pythran.cp312-win_amd64.pyd +0 -0
  191. scipy/interpolate/_rgi.py +31 -26
  192. scipy/interpolate/_rgi_cython.cp312-win_amd64.dll.a +0 -0
  193. scipy/interpolate/_rgi_cython.cp312-win_amd64.pyd +0 -0
  194. scipy/interpolate/dfitpack.py +0 -20
  195. scipy/interpolate/interpnd.py +1 -2
  196. scipy/interpolate/tests/test_bary_rational.py +2 -2
  197. scipy/interpolate/tests/test_bsplines.py +97 -1
  198. scipy/interpolate/tests/test_fitpack2.py +39 -1
  199. scipy/interpolate/tests/test_interpnd.py +32 -20
  200. scipy/interpolate/tests/test_interpolate.py +48 -4
  201. scipy/interpolate/tests/test_rgi.py +2 -1
  202. scipy/io/_fast_matrix_market/__init__.py +2 -0
  203. scipy/io/_fast_matrix_market/_fmm_core.cp312-win_amd64.dll.a +0 -0
  204. scipy/io/_fast_matrix_market/_fmm_core.cp312-win_amd64.pyd +0 -0
  205. scipy/io/_harwell_boeing/_fortran_format_parser.py +19 -16
  206. scipy/io/_harwell_boeing/hb.py +7 -11
  207. scipy/io/_idl.py +5 -7
  208. scipy/io/_netcdf.py +15 -5
  209. scipy/io/_test_fortran.cp312-win_amd64.dll.a +0 -0
  210. scipy/io/_test_fortran.cp312-win_amd64.pyd +0 -0
  211. scipy/io/arff/tests/test_arffread.py +3 -3
  212. scipy/io/matlab/__init__.py +5 -3
  213. scipy/io/matlab/_mio.py +4 -1
  214. scipy/io/matlab/_mio5.py +19 -13
  215. scipy/io/matlab/_mio5_utils.cp312-win_amd64.dll.a +0 -0
  216. scipy/io/matlab/_mio5_utils.cp312-win_amd64.pyd +0 -0
  217. scipy/io/matlab/_mio_utils.cp312-win_amd64.dll.a +0 -0
  218. scipy/io/matlab/_mio_utils.cp312-win_amd64.pyd +0 -0
  219. scipy/io/matlab/_miobase.py +4 -1
  220. scipy/io/matlab/_streams.cp312-win_amd64.dll.a +0 -0
  221. scipy/io/matlab/_streams.cp312-win_amd64.pyd +0 -0
  222. scipy/io/matlab/tests/test_mio.py +46 -18
  223. scipy/io/matlab/tests/test_mio_funcs.py +1 -1
  224. scipy/io/tests/test_mmio.py +7 -1
  225. scipy/io/tests/test_wavfile.py +41 -0
  226. scipy/io/wavfile.py +57 -10
  227. scipy/linalg/_basic.py +113 -86
  228. scipy/linalg/_cythonized_array_utils.cp312-win_amd64.dll.a +0 -0
  229. scipy/linalg/_cythonized_array_utils.cp312-win_amd64.pyd +0 -0
  230. scipy/linalg/_decomp.py +22 -9
  231. scipy/linalg/_decomp_cholesky.py +28 -13
  232. scipy/linalg/_decomp_cossin.py +45 -30
  233. scipy/linalg/_decomp_interpolative.cp312-win_amd64.dll.a +0 -0
  234. scipy/linalg/_decomp_interpolative.cp312-win_amd64.pyd +0 -0
  235. scipy/linalg/_decomp_ldl.py +4 -1
  236. scipy/linalg/_decomp_lu.py +18 -6
  237. scipy/linalg/_decomp_lu_cython.cp312-win_amd64.dll.a +0 -0
  238. scipy/linalg/_decomp_lu_cython.cp312-win_amd64.pyd +0 -0
  239. scipy/linalg/_decomp_polar.py +2 -0
  240. scipy/linalg/_decomp_qr.py +6 -2
  241. scipy/linalg/_decomp_qz.py +3 -0
  242. scipy/linalg/_decomp_schur.py +3 -1
  243. scipy/linalg/_decomp_svd.py +13 -2
  244. scipy/linalg/_decomp_update.cp312-win_amd64.dll.a +0 -0
  245. scipy/linalg/_decomp_update.cp312-win_amd64.pyd +0 -0
  246. scipy/linalg/_expm_frechet.py +4 -0
  247. scipy/linalg/_fblas.cp312-win_amd64.dll.a +0 -0
  248. scipy/linalg/_fblas.cp312-win_amd64.pyd +0 -0
  249. scipy/linalg/_flapack.cp312-win_amd64.dll.a +0 -0
  250. scipy/linalg/_flapack.cp312-win_amd64.pyd +0 -0
  251. scipy/linalg/_linalg_pythran.cp312-win_amd64.dll.a +0 -0
  252. scipy/linalg/_linalg_pythran.cp312-win_amd64.pyd +0 -0
  253. scipy/linalg/_matfuncs.py +187 -4
  254. scipy/linalg/_matfuncs_expm.cp312-win_amd64.dll.a +0 -0
  255. scipy/linalg/_matfuncs_expm.cp312-win_amd64.pyd +0 -0
  256. scipy/linalg/_matfuncs_schur_sqrtm.cp312-win_amd64.dll.a +0 -0
  257. scipy/linalg/_matfuncs_schur_sqrtm.cp312-win_amd64.pyd +0 -0
  258. scipy/linalg/_matfuncs_sqrtm.py +1 -99
  259. scipy/linalg/_matfuncs_sqrtm_triu.cp312-win_amd64.dll.a +0 -0
  260. scipy/linalg/_matfuncs_sqrtm_triu.cp312-win_amd64.pyd +0 -0
  261. scipy/linalg/_procrustes.py +2 -0
  262. scipy/linalg/_sketches.py +17 -6
  263. scipy/linalg/_solve_toeplitz.cp312-win_amd64.dll.a +0 -0
  264. scipy/linalg/_solve_toeplitz.cp312-win_amd64.pyd +0 -0
  265. scipy/linalg/_solvers.py +7 -2
  266. scipy/linalg/_special_matrices.py +26 -36
  267. scipy/linalg/cython_blas.cp312-win_amd64.dll.a +0 -0
  268. scipy/linalg/cython_blas.cp312-win_amd64.pyd +0 -0
  269. scipy/linalg/cython_lapack.cp312-win_amd64.dll.a +0 -0
  270. scipy/linalg/cython_lapack.cp312-win_amd64.pyd +0 -0
  271. scipy/linalg/lapack.py +22 -2
  272. scipy/linalg/tests/_cython_examples/meson.build +7 -0
  273. scipy/linalg/tests/test_basic.py +31 -16
  274. scipy/linalg/tests/test_batch.py +588 -0
  275. scipy/linalg/tests/test_cythonized_array_utils.py +0 -2
  276. scipy/linalg/tests/test_decomp.py +40 -3
  277. scipy/linalg/tests/test_decomp_cossin.py +14 -0
  278. scipy/linalg/tests/test_decomp_ldl.py +1 -1
  279. scipy/linalg/tests/test_lapack.py +115 -7
  280. scipy/linalg/tests/test_matfuncs.py +157 -102
  281. scipy/linalg/tests/test_procrustes.py +0 -7
  282. scipy/linalg/tests/test_solve_toeplitz.py +1 -1
  283. scipy/linalg/tests/test_special_matrices.py +1 -5
  284. scipy/ndimage/__init__.py +1 -0
  285. scipy/ndimage/_ctest.cp312-win_amd64.dll.a +0 -0
  286. scipy/ndimage/_ctest.cp312-win_amd64.pyd +0 -0
  287. scipy/ndimage/_cytest.cp312-win_amd64.dll.a +0 -0
  288. scipy/ndimage/_cytest.cp312-win_amd64.pyd +0 -0
  289. scipy/ndimage/_delegators.py +8 -2
  290. scipy/ndimage/_filters.py +453 -5
  291. scipy/ndimage/_interpolation.py +36 -6
  292. scipy/ndimage/_measurements.py +4 -2
  293. scipy/ndimage/_morphology.py +5 -0
  294. scipy/ndimage/_nd_image.cp312-win_amd64.dll.a +0 -0
  295. scipy/ndimage/_nd_image.cp312-win_amd64.pyd +0 -0
  296. scipy/ndimage/_ni_docstrings.py +5 -1
  297. scipy/ndimage/_ni_label.cp312-win_amd64.dll.a +0 -0
  298. scipy/ndimage/_ni_label.cp312-win_amd64.pyd +0 -0
  299. scipy/ndimage/_ni_support.py +1 -5
  300. scipy/ndimage/_rank_filter_1d.cp312-win_amd64.dll.a +0 -0
  301. scipy/ndimage/_rank_filter_1d.cp312-win_amd64.pyd +0 -0
  302. scipy/ndimage/_support_alternative_backends.py +18 -6
  303. scipy/ndimage/tests/test_filters.py +370 -259
  304. scipy/ndimage/tests/test_fourier.py +7 -9
  305. scipy/ndimage/tests/test_interpolation.py +68 -61
  306. scipy/ndimage/tests/test_measurements.py +18 -35
  307. scipy/ndimage/tests/test_morphology.py +143 -131
  308. scipy/ndimage/tests/test_splines.py +1 -3
  309. scipy/odr/__odrpack.cp312-win_amd64.dll.a +0 -0
  310. scipy/odr/__odrpack.cp312-win_amd64.pyd +0 -0
  311. scipy/optimize/_basinhopping.py +13 -7
  312. scipy/optimize/_bglu_dense.cp312-win_amd64.dll.a +0 -0
  313. scipy/optimize/_bglu_dense.cp312-win_amd64.pyd +0 -0
  314. scipy/optimize/_bracket.py +17 -24
  315. scipy/optimize/_chandrupatla.py +9 -10
  316. scipy/optimize/_cobyla_py.py +104 -123
  317. scipy/optimize/_constraints.py +14 -10
  318. scipy/optimize/_differentiable_functions.py +371 -230
  319. scipy/optimize/_differentialevolution.py +4 -3
  320. scipy/optimize/_direct.cp312-win_amd64.dll.a +0 -0
  321. scipy/optimize/_direct.cp312-win_amd64.pyd +0 -0
  322. scipy/optimize/_dual_annealing.py +1 -1
  323. scipy/optimize/_elementwise.py +1 -4
  324. scipy/optimize/_group_columns.cp312-win_amd64.dll.a +0 -0
  325. scipy/optimize/_group_columns.cp312-win_amd64.pyd +0 -0
  326. scipy/optimize/_highspy/_core.cp312-win_amd64.dll.a +0 -0
  327. scipy/optimize/_highspy/_core.cp312-win_amd64.pyd +0 -0
  328. scipy/optimize/_highspy/_highs_options.cp312-win_amd64.dll.a +0 -0
  329. scipy/optimize/_highspy/_highs_options.cp312-win_amd64.pyd +0 -0
  330. scipy/optimize/_lbfgsb.cp312-win_amd64.dll.a +0 -0
  331. scipy/optimize/_lbfgsb.cp312-win_amd64.pyd +0 -0
  332. scipy/optimize/_lbfgsb_py.py +57 -16
  333. scipy/optimize/_linprog_doc.py +2 -2
  334. scipy/optimize/_linprog_highs.py +2 -2
  335. scipy/optimize/_linprog_ip.py +25 -10
  336. scipy/optimize/_linprog_util.py +14 -16
  337. scipy/optimize/_lsap.cp312-win_amd64.dll.a +0 -0
  338. scipy/optimize/_lsap.cp312-win_amd64.pyd +0 -0
  339. scipy/optimize/_lsq/common.py +3 -3
  340. scipy/optimize/_lsq/dogbox.py +16 -2
  341. scipy/optimize/_lsq/givens_elimination.cp312-win_amd64.dll.a +0 -0
  342. scipy/optimize/_lsq/givens_elimination.cp312-win_amd64.pyd +0 -0
  343. scipy/optimize/_lsq/least_squares.py +198 -126
  344. scipy/optimize/_lsq/lsq_linear.py +6 -6
  345. scipy/optimize/_lsq/trf.py +35 -8
  346. scipy/optimize/_milp.py +3 -1
  347. scipy/optimize/_minimize.py +105 -36
  348. scipy/optimize/_minpack.cp312-win_amd64.dll.a +0 -0
  349. scipy/optimize/_minpack.cp312-win_amd64.pyd +0 -0
  350. scipy/optimize/_minpack_py.py +21 -14
  351. scipy/optimize/_moduleTNC.cp312-win_amd64.dll.a +0 -0
  352. scipy/optimize/_moduleTNC.cp312-win_amd64.pyd +0 -0
  353. scipy/optimize/_nnls.py +20 -21
  354. scipy/optimize/_nonlin.py +34 -3
  355. scipy/optimize/_numdiff.py +288 -110
  356. scipy/optimize/_optimize.py +86 -48
  357. scipy/optimize/_pava_pybind.cp312-win_amd64.dll.a +0 -0
  358. scipy/optimize/_pava_pybind.cp312-win_amd64.pyd +0 -0
  359. scipy/optimize/_remove_redundancy.py +5 -5
  360. scipy/optimize/_root_scalar.py +1 -1
  361. scipy/optimize/_shgo.py +6 -0
  362. scipy/optimize/_shgo_lib/_complex.py +1 -1
  363. scipy/optimize/_slsqp_py.py +216 -124
  364. scipy/optimize/_slsqplib.cp312-win_amd64.dll.a +0 -0
  365. scipy/optimize/_slsqplib.cp312-win_amd64.pyd +0 -0
  366. scipy/optimize/_spectral.py +1 -1
  367. scipy/optimize/_tnc.py +8 -1
  368. scipy/optimize/_trlib/_trlib.cp312-win_amd64.dll.a +0 -0
  369. scipy/optimize/_trlib/_trlib.cp312-win_amd64.pyd +0 -0
  370. scipy/optimize/_trustregion.py +20 -6
  371. scipy/optimize/_trustregion_constr/canonical_constraint.py +7 -7
  372. scipy/optimize/_trustregion_constr/equality_constrained_sqp.py +1 -1
  373. scipy/optimize/_trustregion_constr/minimize_trustregion_constr.py +11 -3
  374. scipy/optimize/_trustregion_constr/projections.py +12 -8
  375. scipy/optimize/_trustregion_constr/qp_subproblem.py +9 -9
  376. scipy/optimize/_trustregion_constr/tests/test_projections.py +7 -7
  377. scipy/optimize/_trustregion_constr/tests/test_qp_subproblem.py +77 -77
  378. scipy/optimize/_trustregion_constr/tr_interior_point.py +5 -5
  379. scipy/optimize/_trustregion_exact.py +0 -1
  380. scipy/optimize/_zeros.cp312-win_amd64.dll.a +0 -0
  381. scipy/optimize/_zeros.cp312-win_amd64.pyd +0 -0
  382. scipy/optimize/_zeros_py.py +97 -17
  383. scipy/optimize/cython_optimize/_zeros.cp312-win_amd64.dll.a +0 -0
  384. scipy/optimize/cython_optimize/_zeros.cp312-win_amd64.pyd +0 -0
  385. scipy/optimize/slsqp.py +0 -1
  386. scipy/optimize/tests/test__basinhopping.py +1 -1
  387. scipy/optimize/tests/test__differential_evolution.py +4 -4
  388. scipy/optimize/tests/test__linprog_clean_inputs.py +5 -3
  389. scipy/optimize/tests/test__numdiff.py +66 -22
  390. scipy/optimize/tests/test__remove_redundancy.py +2 -2
  391. scipy/optimize/tests/test__shgo.py +9 -1
  392. scipy/optimize/tests/test_bracket.py +36 -46
  393. scipy/optimize/tests/test_chandrupatla.py +133 -135
  394. scipy/optimize/tests/test_cobyla.py +74 -45
  395. scipy/optimize/tests/test_constraints.py +1 -1
  396. scipy/optimize/tests/test_differentiable_functions.py +226 -6
  397. scipy/optimize/tests/test_lbfgsb_hessinv.py +22 -0
  398. scipy/optimize/tests/test_least_squares.py +125 -13
  399. scipy/optimize/tests/test_linear_assignment.py +3 -3
  400. scipy/optimize/tests/test_linprog.py +3 -3
  401. scipy/optimize/tests/test_lsq_linear.py +6 -6
  402. scipy/optimize/tests/test_minimize_constrained.py +2 -2
  403. scipy/optimize/tests/test_minpack.py +4 -4
  404. scipy/optimize/tests/test_nnls.py +43 -3
  405. scipy/optimize/tests/test_nonlin.py +36 -0
  406. scipy/optimize/tests/test_optimize.py +95 -17
  407. scipy/optimize/tests/test_slsqp.py +36 -4
  408. scipy/optimize/tests/test_zeros.py +34 -1
  409. scipy/signal/__init__.py +12 -23
  410. scipy/signal/_delegators.py +568 -0
  411. scipy/signal/_filter_design.py +459 -241
  412. scipy/signal/_fir_filter_design.py +262 -90
  413. scipy/signal/_lti_conversion.py +3 -2
  414. scipy/signal/_ltisys.py +118 -91
  415. scipy/signal/_max_len_seq_inner.cp312-win_amd64.dll.a +0 -0
  416. scipy/signal/_max_len_seq_inner.cp312-win_amd64.pyd +0 -0
  417. scipy/signal/_peak_finding_utils.cp312-win_amd64.dll.a +0 -0
  418. scipy/signal/_peak_finding_utils.cp312-win_amd64.pyd +0 -0
  419. scipy/signal/_polyutils.py +172 -0
  420. scipy/signal/_short_time_fft.py +519 -70
  421. scipy/signal/_signal_api.py +30 -0
  422. scipy/signal/_signaltools.py +719 -399
  423. scipy/signal/_sigtools.cp312-win_amd64.dll.a +0 -0
  424. scipy/signal/_sigtools.cp312-win_amd64.pyd +0 -0
  425. scipy/signal/_sosfilt.cp312-win_amd64.dll.a +0 -0
  426. scipy/signal/_sosfilt.cp312-win_amd64.pyd +0 -0
  427. scipy/signal/_spectral_py.py +230 -50
  428. scipy/signal/_spline.cp312-win_amd64.dll.a +0 -0
  429. scipy/signal/_spline.cp312-win_amd64.pyd +0 -0
  430. scipy/signal/_spline_filters.py +108 -68
  431. scipy/signal/_support_alternative_backends.py +73 -0
  432. scipy/signal/_upfirdn.py +4 -1
  433. scipy/signal/_upfirdn_apply.cp312-win_amd64.dll.a +0 -0
  434. scipy/signal/_upfirdn_apply.cp312-win_amd64.pyd +0 -0
  435. scipy/signal/_waveforms.py +2 -11
  436. scipy/signal/_wavelets.py +1 -1
  437. scipy/signal/fir_filter_design.py +1 -0
  438. scipy/signal/spline.py +4 -11
  439. scipy/signal/tests/_scipy_spectral_test_shim.py +2 -171
  440. scipy/signal/tests/test_bsplines.py +114 -79
  441. scipy/signal/tests/test_cont2discrete.py +9 -2
  442. scipy/signal/tests/test_filter_design.py +721 -481
  443. scipy/signal/tests/test_fir_filter_design.py +332 -140
  444. scipy/signal/tests/test_savitzky_golay.py +4 -3
  445. scipy/signal/tests/test_short_time_fft.py +221 -3
  446. scipy/signal/tests/test_signaltools.py +2144 -1348
  447. scipy/signal/tests/test_spectral.py +50 -6
  448. scipy/signal/tests/test_splines.py +161 -96
  449. scipy/signal/tests/test_upfirdn.py +84 -50
  450. scipy/signal/tests/test_waveforms.py +20 -0
  451. scipy/signal/tests/test_windows.py +607 -466
  452. scipy/signal/windows/_windows.py +287 -148
  453. scipy/sparse/__init__.py +23 -4
  454. scipy/sparse/_base.py +270 -108
  455. scipy/sparse/_bsr.py +7 -4
  456. scipy/sparse/_compressed.py +59 -231
  457. scipy/sparse/_construct.py +90 -38
  458. scipy/sparse/_coo.py +115 -181
  459. scipy/sparse/_csc.py +4 -4
  460. scipy/sparse/_csparsetools.cp312-win_amd64.dll.a +0 -0
  461. scipy/sparse/_csparsetools.cp312-win_amd64.pyd +0 -0
  462. scipy/sparse/_csr.py +2 -2
  463. scipy/sparse/_data.py +48 -48
  464. scipy/sparse/_dia.py +105 -18
  465. scipy/sparse/_dok.py +0 -23
  466. scipy/sparse/_index.py +4 -4
  467. scipy/sparse/_matrix.py +23 -0
  468. scipy/sparse/_sparsetools.cp312-win_amd64.dll.a +0 -0
  469. scipy/sparse/_sparsetools.cp312-win_amd64.pyd +0 -0
  470. scipy/sparse/_sputils.py +37 -22
  471. scipy/sparse/base.py +0 -9
  472. scipy/sparse/bsr.py +0 -14
  473. scipy/sparse/compressed.py +0 -23
  474. scipy/sparse/construct.py +0 -6
  475. scipy/sparse/coo.py +0 -14
  476. scipy/sparse/csc.py +0 -3
  477. scipy/sparse/csgraph/_flow.cp312-win_amd64.dll.a +0 -0
  478. scipy/sparse/csgraph/_flow.cp312-win_amd64.pyd +0 -0
  479. scipy/sparse/csgraph/_matching.cp312-win_amd64.dll.a +0 -0
  480. scipy/sparse/csgraph/_matching.cp312-win_amd64.pyd +0 -0
  481. scipy/sparse/csgraph/_min_spanning_tree.cp312-win_amd64.dll.a +0 -0
  482. scipy/sparse/csgraph/_min_spanning_tree.cp312-win_amd64.pyd +0 -0
  483. scipy/sparse/csgraph/_reordering.cp312-win_amd64.dll.a +0 -0
  484. scipy/sparse/csgraph/_reordering.cp312-win_amd64.pyd +0 -0
  485. scipy/sparse/csgraph/_shortest_path.cp312-win_amd64.dll.a +0 -0
  486. scipy/sparse/csgraph/_shortest_path.cp312-win_amd64.pyd +0 -0
  487. scipy/sparse/csgraph/_tools.cp312-win_amd64.dll.a +0 -0
  488. scipy/sparse/csgraph/_tools.cp312-win_amd64.pyd +0 -0
  489. scipy/sparse/csgraph/_traversal.cp312-win_amd64.dll.a +0 -0
  490. scipy/sparse/csgraph/_traversal.cp312-win_amd64.pyd +0 -0
  491. scipy/sparse/csgraph/tests/test_matching.py +14 -2
  492. scipy/sparse/csgraph/tests/test_pydata_sparse.py +4 -1
  493. scipy/sparse/csgraph/tests/test_shortest_path.py +83 -27
  494. scipy/sparse/csr.py +0 -5
  495. scipy/sparse/data.py +1 -6
  496. scipy/sparse/dia.py +0 -7
  497. scipy/sparse/dok.py +0 -10
  498. scipy/sparse/linalg/_dsolve/_superlu.cp312-win_amd64.dll.a +0 -0
  499. scipy/sparse/linalg/_dsolve/_superlu.cp312-win_amd64.pyd +0 -0
  500. scipy/sparse/linalg/_dsolve/linsolve.py +9 -0
  501. scipy/sparse/linalg/_dsolve/tests/test_linsolve.py +35 -28
  502. scipy/sparse/linalg/_eigen/arpack/_arpack.cp312-win_amd64.dll.a +0 -0
  503. scipy/sparse/linalg/_eigen/arpack/_arpack.cp312-win_amd64.pyd +0 -0
  504. scipy/sparse/linalg/_eigen/arpack/arpack.py +23 -17
  505. scipy/sparse/linalg/_eigen/lobpcg/lobpcg.py +6 -6
  506. scipy/sparse/linalg/_interface.py +17 -18
  507. scipy/sparse/linalg/_isolve/_gcrotmk.py +4 -4
  508. scipy/sparse/linalg/_isolve/iterative.py +51 -45
  509. scipy/sparse/linalg/_isolve/lgmres.py +6 -6
  510. scipy/sparse/linalg/_isolve/minres.py +5 -5
  511. scipy/sparse/linalg/_isolve/tfqmr.py +7 -7
  512. scipy/sparse/linalg/_isolve/utils.py +2 -8
  513. scipy/sparse/linalg/_matfuncs.py +1 -1
  514. scipy/sparse/linalg/_norm.py +1 -1
  515. scipy/sparse/linalg/_propack/_cpropack.cp312-win_amd64.dll.a +0 -0
  516. scipy/sparse/linalg/_propack/_cpropack.cp312-win_amd64.pyd +0 -0
  517. scipy/sparse/linalg/_propack/_dpropack.cp312-win_amd64.dll.a +0 -0
  518. scipy/sparse/linalg/_propack/_dpropack.cp312-win_amd64.pyd +0 -0
  519. scipy/sparse/linalg/_propack/_spropack.cp312-win_amd64.dll.a +0 -0
  520. scipy/sparse/linalg/_propack/_spropack.cp312-win_amd64.pyd +0 -0
  521. scipy/sparse/linalg/_propack/_zpropack.cp312-win_amd64.dll.a +0 -0
  522. scipy/sparse/linalg/_propack/_zpropack.cp312-win_amd64.pyd +0 -0
  523. scipy/sparse/linalg/_special_sparse_arrays.py +39 -38
  524. scipy/sparse/linalg/tests/test_pydata_sparse.py +14 -0
  525. scipy/sparse/tests/test_arithmetic1d.py +5 -2
  526. scipy/sparse/tests/test_base.py +214 -42
  527. scipy/sparse/tests/test_common1d.py +7 -7
  528. scipy/sparse/tests/test_construct.py +1 -1
  529. scipy/sparse/tests/test_coo.py +272 -4
  530. scipy/sparse/tests/test_sparsetools.py +5 -0
  531. scipy/sparse/tests/test_sputils.py +36 -7
  532. scipy/spatial/_ckdtree.cp312-win_amd64.dll.a +0 -0
  533. scipy/spatial/_ckdtree.cp312-win_amd64.pyd +0 -0
  534. scipy/spatial/_distance_pybind.cp312-win_amd64.dll.a +0 -0
  535. scipy/spatial/_distance_pybind.cp312-win_amd64.pyd +0 -0
  536. scipy/spatial/_distance_wrap.cp312-win_amd64.dll.a +0 -0
  537. scipy/spatial/_distance_wrap.cp312-win_amd64.pyd +0 -0
  538. scipy/spatial/_hausdorff.cp312-win_amd64.dll.a +0 -0
  539. scipy/spatial/_hausdorff.cp312-win_amd64.pyd +0 -0
  540. scipy/spatial/_qhull.cp312-win_amd64.dll.a +0 -0
  541. scipy/spatial/_qhull.cp312-win_amd64.pyd +0 -0
  542. scipy/spatial/_voronoi.cp312-win_amd64.dll.a +0 -0
  543. scipy/spatial/_voronoi.cp312-win_amd64.pyd +0 -0
  544. scipy/spatial/distance.py +49 -42
  545. scipy/spatial/tests/test_distance.py +15 -1
  546. scipy/spatial/tests/test_kdtree.py +1 -0
  547. scipy/spatial/tests/test_qhull.py +7 -2
  548. scipy/spatial/transform/__init__.py +5 -3
  549. scipy/spatial/transform/_rigid_transform.cp312-win_amd64.dll.a +0 -0
  550. scipy/spatial/transform/_rigid_transform.cp312-win_amd64.pyd +0 -0
  551. scipy/spatial/transform/_rotation.cp312-win_amd64.dll.a +0 -0
  552. scipy/spatial/transform/_rotation.cp312-win_amd64.pyd +0 -0
  553. scipy/spatial/transform/tests/test_rigid_transform.py +1221 -0
  554. scipy/spatial/transform/tests/test_rotation.py +1213 -832
  555. scipy/spatial/transform/tests/test_rotation_groups.py +3 -3
  556. scipy/spatial/transform/tests/test_rotation_spline.py +29 -8
  557. scipy/special/__init__.py +1 -47
  558. scipy/special/_add_newdocs.py +34 -772
  559. scipy/special/_basic.py +22 -25
  560. scipy/special/_comb.cp312-win_amd64.dll.a +0 -0
  561. scipy/special/_comb.cp312-win_amd64.pyd +0 -0
  562. scipy/special/_ellip_harm_2.cp312-win_amd64.dll.a +0 -0
  563. scipy/special/_ellip_harm_2.cp312-win_amd64.pyd +0 -0
  564. scipy/special/_gufuncs.cp312-win_amd64.dll.a +0 -0
  565. scipy/special/_gufuncs.cp312-win_amd64.pyd +0 -0
  566. scipy/special/_logsumexp.py +67 -58
  567. scipy/special/_orthogonal.pyi +1 -1
  568. scipy/special/_specfun.cp312-win_amd64.dll.a +0 -0
  569. scipy/special/_specfun.cp312-win_amd64.pyd +0 -0
  570. scipy/special/_special_ufuncs.cp312-win_amd64.dll.a +0 -0
  571. scipy/special/_special_ufuncs.cp312-win_amd64.pyd +0 -0
  572. scipy/special/_spherical_bessel.py +4 -4
  573. scipy/special/_support_alternative_backends.py +212 -119
  574. scipy/special/_test_internal.cp312-win_amd64.dll.a +0 -0
  575. scipy/special/_test_internal.cp312-win_amd64.pyd +0 -0
  576. scipy/special/_testutils.py +4 -4
  577. scipy/special/_ufuncs.cp312-win_amd64.dll.a +0 -0
  578. scipy/special/_ufuncs.cp312-win_amd64.pyd +0 -0
  579. scipy/special/_ufuncs.pyi +1 -0
  580. scipy/special/_ufuncs.pyx +215 -1400
  581. scipy/special/_ufuncs_cxx.cp312-win_amd64.dll.a +0 -0
  582. scipy/special/_ufuncs_cxx.cp312-win_amd64.pyd +0 -0
  583. scipy/special/_ufuncs_cxx.pxd +2 -15
  584. scipy/special/_ufuncs_cxx.pyx +5 -44
  585. scipy/special/_ufuncs_cxx_defs.h +2 -16
  586. scipy/special/_ufuncs_defs.h +0 -8
  587. scipy/special/cython_special.cp312-win_amd64.dll.a +0 -0
  588. scipy/special/cython_special.cp312-win_amd64.pyd +0 -0
  589. scipy/special/cython_special.pxd +1 -1
  590. scipy/special/tests/_cython_examples/meson.build +10 -1
  591. scipy/special/tests/test_basic.py +153 -20
  592. scipy/special/tests/test_boost_ufuncs.py +3 -0
  593. scipy/special/tests/test_cdflib.py +35 -11
  594. scipy/special/tests/test_gammainc.py +16 -0
  595. scipy/special/tests/test_hyp2f1.py +2 -2
  596. scipy/special/tests/test_log1mexp.py +85 -0
  597. scipy/special/tests/test_logsumexp.py +206 -64
  598. scipy/special/tests/test_mpmath.py +1 -0
  599. scipy/special/tests/test_nan_inputs.py +1 -1
  600. scipy/special/tests/test_orthogonal.py +17 -18
  601. scipy/special/tests/test_sf_error.py +3 -2
  602. scipy/special/tests/test_sph_harm.py +6 -7
  603. scipy/special/tests/test_support_alternative_backends.py +211 -76
  604. scipy/stats/__init__.py +4 -1
  605. scipy/stats/_ansari_swilk_statistics.cp312-win_amd64.dll.a +0 -0
  606. scipy/stats/_ansari_swilk_statistics.cp312-win_amd64.pyd +0 -0
  607. scipy/stats/_axis_nan_policy.py +5 -12
  608. scipy/stats/_biasedurn.cp312-win_amd64.dll.a +0 -0
  609. scipy/stats/_biasedurn.cp312-win_amd64.pyd +0 -0
  610. scipy/stats/_continued_fraction.py +387 -0
  611. scipy/stats/_continuous_distns.py +277 -310
  612. scipy/stats/_correlation.py +1 -1
  613. scipy/stats/_covariance.py +6 -3
  614. scipy/stats/_discrete_distns.py +39 -32
  615. scipy/stats/_distn_infrastructure.py +39 -12
  616. scipy/stats/_distribution_infrastructure.py +900 -238
  617. scipy/stats/_entropy.py +9 -10
  618. scipy/{_lib → stats}/_finite_differences.py +1 -1
  619. scipy/stats/_hypotests.py +83 -50
  620. scipy/stats/_kde.py +53 -49
  621. scipy/stats/_ksstats.py +1 -1
  622. scipy/stats/_levy_stable/__init__.py +7 -15
  623. scipy/stats/_levy_stable/levyst.cp312-win_amd64.dll.a +0 -0
  624. scipy/stats/_levy_stable/levyst.cp312-win_amd64.pyd +0 -0
  625. scipy/stats/_morestats.py +118 -73
  626. scipy/stats/_mstats_basic.py +13 -17
  627. scipy/stats/_mstats_extras.py +8 -8
  628. scipy/stats/_multivariate.py +89 -113
  629. scipy/stats/_new_distributions.py +97 -20
  630. scipy/stats/_page_trend_test.py +12 -5
  631. scipy/stats/_probability_distribution.py +265 -43
  632. scipy/stats/_qmc.py +14 -9
  633. scipy/stats/_qmc_cy.cp312-win_amd64.dll.a +0 -0
  634. scipy/stats/_qmc_cy.cp312-win_amd64.pyd +0 -0
  635. scipy/stats/_qmvnt.py +16 -95
  636. scipy/stats/_qmvnt_cy.cp312-win_amd64.dll.a +0 -0
  637. scipy/stats/_qmvnt_cy.cp312-win_amd64.pyd +0 -0
  638. scipy/stats/_quantile.py +335 -0
  639. scipy/stats/_rcont/rcont.cp312-win_amd64.dll.a +0 -0
  640. scipy/stats/_rcont/rcont.cp312-win_amd64.pyd +0 -0
  641. scipy/stats/_resampling.py +4 -29
  642. scipy/stats/_sampling.py +1 -1
  643. scipy/stats/_sobol.cp312-win_amd64.dll.a +0 -0
  644. scipy/stats/_sobol.cp312-win_amd64.pyd +0 -0
  645. scipy/stats/_stats.cp312-win_amd64.dll.a +0 -0
  646. scipy/stats/_stats.cp312-win_amd64.pyd +0 -0
  647. scipy/stats/_stats_mstats_common.py +21 -2
  648. scipy/stats/_stats_py.py +550 -476
  649. scipy/stats/_stats_pythran.cp312-win_amd64.dll.a +0 -0
  650. scipy/stats/_stats_pythran.cp312-win_amd64.pyd +0 -0
  651. scipy/stats/_unuran/unuran_wrapper.cp312-win_amd64.dll.a +0 -0
  652. scipy/stats/_unuran/unuran_wrapper.cp312-win_amd64.pyd +0 -0
  653. scipy/stats/_unuran/unuran_wrapper.pyi +2 -1
  654. scipy/stats/_variation.py +6 -8
  655. scipy/stats/_wilcoxon.py +13 -7
  656. scipy/stats/tests/common_tests.py +6 -4
  657. scipy/stats/tests/test_axis_nan_policy.py +62 -24
  658. scipy/stats/tests/test_continued_fraction.py +173 -0
  659. scipy/stats/tests/test_continuous.py +379 -60
  660. scipy/stats/tests/test_continuous_basic.py +18 -12
  661. scipy/stats/tests/test_discrete_basic.py +14 -8
  662. scipy/stats/tests/test_discrete_distns.py +16 -16
  663. scipy/stats/tests/test_distributions.py +95 -75
  664. scipy/stats/tests/test_entropy.py +40 -48
  665. scipy/stats/tests/test_fit.py +4 -3
  666. scipy/stats/tests/test_hypotests.py +153 -24
  667. scipy/stats/tests/test_kdeoth.py +109 -41
  668. scipy/stats/tests/test_marray.py +289 -0
  669. scipy/stats/tests/test_morestats.py +79 -47
  670. scipy/stats/tests/test_mstats_basic.py +3 -3
  671. scipy/stats/tests/test_multivariate.py +434 -83
  672. scipy/stats/tests/test_qmc.py +13 -10
  673. scipy/stats/tests/test_quantile.py +199 -0
  674. scipy/stats/tests/test_rank.py +119 -112
  675. scipy/stats/tests/test_resampling.py +47 -56
  676. scipy/stats/tests/test_sampling.py +9 -4
  677. scipy/stats/tests/test_stats.py +799 -939
  678. scipy/stats/tests/test_variation.py +8 -6
  679. scipy/version.py +2 -2
  680. scipy-1.16.0rc2.dist-info/DELVEWHEEL +2 -0
  681. {scipy-1.15.3.dist-info → scipy-1.16.0rc2.dist-info}/LICENSE.txt +4 -4
  682. {scipy-1.15.3.dist-info → scipy-1.16.0rc2.dist-info}/METADATA +11 -11
  683. {scipy-1.15.3.dist-info → scipy-1.16.0rc2.dist-info}/RECORD +685 -693
  684. scipy/_lib/array_api_extra/_funcs.py +0 -484
  685. scipy/_lib/array_api_extra/_typing.py +0 -8
  686. scipy/interpolate/_bspl.cp312-win_amd64.dll.a +0 -0
  687. scipy/interpolate/_bspl.cp312-win_amd64.pyd +0 -0
  688. scipy/optimize/_cobyla.cp312-win_amd64.dll.a +0 -0
  689. scipy/optimize/_cobyla.cp312-win_amd64.pyd +0 -0
  690. scipy/optimize/_cython_nnls.cp312-win_amd64.dll.a +0 -0
  691. scipy/optimize/_cython_nnls.cp312-win_amd64.pyd +0 -0
  692. scipy/optimize/_slsqp.cp312-win_amd64.dll.a +0 -0
  693. scipy/optimize/_slsqp.cp312-win_amd64.pyd +0 -0
  694. scipy/spatial/qhull_src/COPYING.txt +0 -38
  695. scipy/special/libsf_error_state.dll +0 -0
  696. scipy/special/libsf_error_state.dll.a +0 -0
  697. scipy/special/tests/test_log_softmax.py +0 -109
  698. scipy/special/tests/test_xsf_cuda.py +0 -114
  699. scipy/special/xsf/binom.h +0 -89
  700. scipy/special/xsf/cdflib.h +0 -100
  701. scipy/special/xsf/cephes/airy.h +0 -307
  702. scipy/special/xsf/cephes/besselpoly.h +0 -51
  703. scipy/special/xsf/cephes/beta.h +0 -257
  704. scipy/special/xsf/cephes/cbrt.h +0 -131
  705. scipy/special/xsf/cephes/chbevl.h +0 -85
  706. scipy/special/xsf/cephes/chdtr.h +0 -193
  707. scipy/special/xsf/cephes/const.h +0 -87
  708. scipy/special/xsf/cephes/ellie.h +0 -293
  709. scipy/special/xsf/cephes/ellik.h +0 -251
  710. scipy/special/xsf/cephes/ellpe.h +0 -107
  711. scipy/special/xsf/cephes/ellpk.h +0 -117
  712. scipy/special/xsf/cephes/expn.h +0 -260
  713. scipy/special/xsf/cephes/gamma.h +0 -398
  714. scipy/special/xsf/cephes/hyp2f1.h +0 -596
  715. scipy/special/xsf/cephes/hyperg.h +0 -361
  716. scipy/special/xsf/cephes/i0.h +0 -149
  717. scipy/special/xsf/cephes/i1.h +0 -158
  718. scipy/special/xsf/cephes/igam.h +0 -421
  719. scipy/special/xsf/cephes/igam_asymp_coeff.h +0 -195
  720. scipy/special/xsf/cephes/igami.h +0 -313
  721. scipy/special/xsf/cephes/j0.h +0 -225
  722. scipy/special/xsf/cephes/j1.h +0 -198
  723. scipy/special/xsf/cephes/jv.h +0 -715
  724. scipy/special/xsf/cephes/k0.h +0 -164
  725. scipy/special/xsf/cephes/k1.h +0 -163
  726. scipy/special/xsf/cephes/kn.h +0 -243
  727. scipy/special/xsf/cephes/lanczos.h +0 -112
  728. scipy/special/xsf/cephes/ndtr.h +0 -275
  729. scipy/special/xsf/cephes/poch.h +0 -85
  730. scipy/special/xsf/cephes/polevl.h +0 -167
  731. scipy/special/xsf/cephes/psi.h +0 -194
  732. scipy/special/xsf/cephes/rgamma.h +0 -111
  733. scipy/special/xsf/cephes/scipy_iv.h +0 -811
  734. scipy/special/xsf/cephes/shichi.h +0 -248
  735. scipy/special/xsf/cephes/sici.h +0 -224
  736. scipy/special/xsf/cephes/sindg.h +0 -221
  737. scipy/special/xsf/cephes/tandg.h +0 -139
  738. scipy/special/xsf/cephes/trig.h +0 -58
  739. scipy/special/xsf/cephes/unity.h +0 -186
  740. scipy/special/xsf/cephes/zeta.h +0 -172
  741. scipy/special/xsf/config.h +0 -304
  742. scipy/special/xsf/digamma.h +0 -205
  743. scipy/special/xsf/error.h +0 -57
  744. scipy/special/xsf/evalpoly.h +0 -47
  745. scipy/special/xsf/expint.h +0 -266
  746. scipy/special/xsf/hyp2f1.h +0 -694
  747. scipy/special/xsf/iv_ratio.h +0 -173
  748. scipy/special/xsf/lambertw.h +0 -150
  749. scipy/special/xsf/loggamma.h +0 -163
  750. scipy/special/xsf/sici.h +0 -200
  751. scipy/special/xsf/tools.h +0 -427
  752. scipy/special/xsf/trig.h +0 -164
  753. scipy/special/xsf/wright_bessel.h +0 -843
  754. scipy/special/xsf/zlog1.h +0 -35
  755. scipy/stats/_mvn.cp312-win_amd64.dll.a +0 -0
  756. scipy/stats/_mvn.cp312-win_amd64.pyd +0 -0
  757. scipy-1.15.3.dist-info/DELVEWHEEL +0 -2
  758. /scipy-1.15.3-cp312-cp312-win_amd64.whl → /scipy-1.16.0rc2-cp312-cp312-win_amd64.whl +0 -0
  759. {scipy-1.15.3.dist-info → scipy-1.16.0rc2.dist-info}/WHEEL +0 -0
@@ -1,15 +1,30 @@
1
+ import math
2
+
1
3
  import pytest
2
4
 
3
5
  import numpy as np
4
- from numpy.testing import assert_equal, assert_array_almost_equal
5
- from numpy.testing import assert_allclose
6
+ from numpy.testing import assert_equal
6
7
  from scipy.spatial.transform import Rotation, Slerp
7
8
  from scipy.stats import special_ortho_group
8
- from itertools import permutations
9
+ from itertools import permutations, product
10
+ from scipy._lib._array_api import (
11
+ xp_assert_equal,
12
+ is_numpy,
13
+ is_lazy_array,
14
+ xp_vector_norm,
15
+ xp_assert_close,
16
+ eager_warns,
17
+ xp_default_dtype
18
+ )
19
+ import scipy._lib.array_api_extra as xpx
9
20
 
10
21
  import pickle
11
22
  import copy
12
23
 
24
+
25
+ pytestmark = pytest.mark.skip_xp_backends(np_only=True)
26
+
27
+
13
28
  def basis_vec(axis):
14
29
  if axis == 'x':
15
30
  return [1, 0, 0]
@@ -18,81 +33,114 @@ def basis_vec(axis):
18
33
  elif axis == 'z':
19
34
  return [0, 0, 1]
20
35
 
21
- def test_generic_quat_matrix():
22
- x = np.array([[3, 4, 0, 0], [5, 12, 0, 0]])
36
+
37
+ def rotation_to_xp(r: Rotation, xp):
38
+ return Rotation.from_quat(xp.asarray(r.as_quat()))
39
+
40
+
41
+ def test_init_non_array():
42
+ Rotation((0, 0, 0, 1))
43
+ Rotation([0, 0, 0, 1])
44
+
45
+
46
+ def test_generic_quat_matrix(xp):
47
+ x = xp.asarray([[3.0, 4, 0, 0], [5, 12, 0, 0]])
23
48
  r = Rotation.from_quat(x)
24
- expected_quat = x / np.array([[5], [13]])
25
- assert_array_almost_equal(r.as_quat(), expected_quat)
49
+ expected_quat = x / xp.asarray([[5.0], [13.0]])
50
+ xp_assert_close(r.as_quat(), expected_quat)
26
51
 
27
52
 
28
- def test_from_single_1d_quaternion():
29
- x = np.array([3, 4, 0, 0])
53
+ def test_from_single_1d_quaternion(xp):
54
+ x = xp.asarray([3.0, 4, 0, 0])
30
55
  r = Rotation.from_quat(x)
31
56
  expected_quat = x / 5
32
- assert_array_almost_equal(r.as_quat(), expected_quat)
57
+ xp_assert_close(r.as_quat(), expected_quat)
33
58
 
34
59
 
35
- def test_from_single_2d_quaternion():
36
- x = np.array([[3, 4, 0, 0]])
60
+ def test_from_single_2d_quaternion(xp):
61
+ x = xp.asarray([[3.0, 4, 0, 0]])
37
62
  r = Rotation.from_quat(x)
38
63
  expected_quat = x / 5
39
- assert_array_almost_equal(r.as_quat(), expected_quat)
64
+ xp_assert_close(r.as_quat(), expected_quat)
40
65
 
41
66
 
42
- def test_from_quat_scalar_first():
67
+ def test_from_quat_scalar_first(xp):
43
68
  rng = np.random.RandomState(0)
44
69
 
45
- r = Rotation.from_quat([1, 0, 0, 0], scalar_first=True)
46
- assert_allclose(r.as_matrix(), np.eye(3), rtol=1e-15, atol=1e-16)
70
+ r = Rotation.from_quat(xp.asarray([1, 0, 0, 0]), scalar_first=True)
71
+ xp_assert_close(r.as_matrix(), xp.eye(3), rtol=1e-15, atol=1e-16)
47
72
 
48
- r = Rotation.from_quat(np.tile([1, 0, 0, 0], (10, 1)), scalar_first=True)
49
- assert_allclose(r.as_matrix(), np.tile(np.eye(3), (10, 1, 1)),
50
- rtol=1e-15, atol=1e-16)
73
+ q = xp.tile(xp.asarray([1, 0, 0, 0]), (10, 1))
74
+ r = Rotation.from_quat(q, scalar_first=True)
75
+ xp_assert_close(
76
+ r.as_matrix(), xp.tile(xp.eye(3), (10, 1, 1)), rtol=1e-15, atol=1e-16
77
+ )
51
78
 
52
- q = rng.randn(100, 4)
53
- q /= np.linalg.norm(q, axis=1)[:, None]
54
- for qi in q:
79
+ q = xp.asarray(rng.randn(100, 4))
80
+ q /= xp_vector_norm(q, axis=1)[:, None]
81
+ for i in range(q.shape[0]): # Array API conforming loop
82
+ qi = q[i, ...]
55
83
  r = Rotation.from_quat(qi, scalar_first=True)
56
- assert_allclose(np.roll(r.as_quat(), 1), qi, rtol=1e-15)
84
+ xp_assert_close(xp.roll(r.as_quat(), 1), qi, rtol=1e-15)
57
85
 
58
86
  r = Rotation.from_quat(q, scalar_first=True)
59
- assert_allclose(np.roll(r.as_quat(), 1, axis=1), q, rtol=1e-15)
87
+ xp_assert_close(xp.roll(r.as_quat(), 1, axis=1), q, rtol=1e-15)
88
+
89
+
90
+ def test_from_quat_array_like():
91
+ rng = np.random.default_rng(123)
92
+ # Single rotation
93
+ r_expected = Rotation.random(rng=rng)
94
+ r = Rotation.from_quat(r_expected.as_quat().tolist())
95
+ assert r_expected.approx_equal(r, atol=1e-12)
96
+
97
+ # Multiple rotations
98
+ r_expected = Rotation.random(3, rng=rng)
99
+ r = Rotation.from_quat(r_expected.as_quat().tolist())
100
+ assert np.all(r_expected.approx_equal(r, atol=1e-12))
101
+
102
+
103
+ def test_from_quat_int_dtype(xp):
104
+ r = Rotation.from_quat(xp.asarray([1, 0, 0, 0]))
105
+ assert r.as_quat().dtype == xp_default_dtype(xp)
60
106
 
61
107
 
62
- def test_as_quat_scalar_first():
108
+ def test_as_quat_scalar_first(xp):
63
109
  rng = np.random.RandomState(0)
64
110
 
65
- r = Rotation.from_euler('xyz', np.zeros(3))
66
- assert_allclose(r.as_quat(scalar_first=True), [1, 0, 0, 0],
111
+ r = Rotation.from_euler('xyz', xp.zeros(3))
112
+ xp_assert_close(r.as_quat(scalar_first=True), xp.asarray([1.0, 0, 0, 0]),
67
113
  rtol=1e-15, atol=1e-16)
68
114
 
69
- r = Rotation.from_euler('xyz', np.zeros((10, 3)))
70
- assert_allclose(r.as_quat(scalar_first=True),
71
- np.tile([1, 0, 0, 0], (10, 1)), rtol=1e-15, atol=1e-16)
115
+ r = Rotation.from_euler('xyz', xp.zeros((10, 3)))
116
+ xp_assert_close(r.as_quat(scalar_first=True),
117
+ xp.tile(xp.asarray([1.0, 0, 0, 0]), (10, 1)),
118
+ rtol=1e-15, atol=1e-16)
72
119
 
73
- q = rng.randn(100, 4)
74
- q /= np.linalg.norm(q, axis=1)[:, None]
75
- for qi in q:
120
+ q = xp.asarray(rng.randn(100, 4))
121
+ q /= xp_vector_norm(q, axis=1)[:, None]
122
+ for i in range(q.shape[0]): # Array API conforming loop
123
+ qi = q[i, ...]
76
124
  r = Rotation.from_quat(qi)
77
- assert_allclose(r.as_quat(scalar_first=True), np.roll(qi, 1),
125
+ xp_assert_close(r.as_quat(scalar_first=True), xp.roll(qi, 1),
78
126
  rtol=1e-15)
79
127
 
80
- assert_allclose(r.as_quat(canonical=True, scalar_first=True),
81
- np.roll(r.as_quat(canonical=True), 1),
128
+ xp_assert_close(r.as_quat(canonical=True, scalar_first=True),
129
+ xp.roll(r.as_quat(canonical=True), 1),
82
130
  rtol=1e-15)
83
131
 
84
132
  r = Rotation.from_quat(q)
85
- assert_allclose(r.as_quat(scalar_first=True), np.roll(q, 1, axis=1),
133
+ xp_assert_close(r.as_quat(scalar_first=True), xp.roll(q, 1, axis=1),
86
134
  rtol=1e-15)
87
135
 
88
- assert_allclose(r.as_quat(canonical=True, scalar_first=True),
89
- np.roll(r.as_quat(canonical=True), 1, axis=1), rtol=1e-15)
136
+ xp_assert_close(r.as_quat(canonical=True, scalar_first=True),
137
+ xp.roll(r.as_quat(canonical=True), 1, axis=1), rtol=1e-15)
90
138
 
91
139
 
92
- def test_from_square_quat_matrix():
140
+ def test_from_square_quat_matrix(xp):
93
141
  # Ensure proper norm array broadcasting
94
- x = np.array([
95
- [3, 0, 0, 4],
142
+ x = xp.asarray([
143
+ [3.0, 0, 0, 4],
96
144
  [5, 0, 12, 0],
97
145
  [0, 0, 0, 1],
98
146
  [-1, -1, -1, 1],
@@ -100,643 +148,724 @@ def test_from_square_quat_matrix():
100
148
  [-1, -1, -1, -1] # Check double cover
101
149
  ])
102
150
  r = Rotation.from_quat(x)
103
- expected_quat = x / np.array([[5], [13], [1], [2], [1], [2]])
104
- assert_array_almost_equal(r.as_quat(), expected_quat)
151
+ expected_quat = x / xp.asarray([[5.0], [13], [1], [2], [1], [2]])
152
+ xp_assert_close(r.as_quat(), expected_quat)
105
153
 
106
154
 
107
- def test_quat_double_to_canonical_single_cover():
108
- x = np.array([
109
- [-1, 0, 0, 0],
155
+ def test_quat_double_to_canonical_single_cover(xp):
156
+ x = xp.asarray([
157
+ [-1.0, 0, 0, 0],
110
158
  [0, -1, 0, 0],
111
159
  [0, 0, -1, 0],
112
160
  [0, 0, 0, -1],
113
161
  [-1, -1, -1, -1]
114
162
  ])
115
163
  r = Rotation.from_quat(x)
116
- expected_quat = np.abs(x) / np.linalg.norm(x, axis=1)[:, None]
117
- assert_allclose(r.as_quat(canonical=True), expected_quat)
164
+ expected_quat = xp.abs(x) / xp_vector_norm(x, axis=1)[:, None]
165
+ xp_assert_close(r.as_quat(canonical=True), expected_quat)
118
166
 
119
167
 
120
- def test_quat_double_cover():
168
+ def test_quat_double_cover(xp):
121
169
  # See the Rotation.from_quat() docstring for scope of the quaternion
122
170
  # double cover property.
123
171
  # Check from_quat and as_quat(canonical=False)
124
- q = np.array([0, 0, 0, -1])
172
+ q = xp.asarray([0.0, 0, 0, -1])
125
173
  r = Rotation.from_quat(q)
126
- assert_equal(q, r.as_quat(canonical=False))
127
-
174
+ xp_assert_equal(q, r.as_quat(canonical=False))
128
175
  # Check composition and inverse
129
- q = np.array([1, 0, 0, 1])/np.sqrt(2) # 90 deg rotation about x
176
+ q = xp.asarray([1.0, 0, 0, 1])/math.sqrt(2) # 90 deg rotation about x
130
177
  r = Rotation.from_quat(q)
131
178
  r3 = r*r*r
132
- assert_allclose(r.as_quat(canonical=False)*np.sqrt(2),
133
- [1, 0, 0, 1])
134
- assert_allclose(r.inv().as_quat(canonical=False)*np.sqrt(2),
135
- [-1, 0, 0, 1])
136
- assert_allclose(r3.as_quat(canonical=False)*np.sqrt(2),
137
- [1, 0, 0, -1])
138
- assert_allclose(r3.inv().as_quat(canonical=False)*np.sqrt(2),
139
- [-1, 0, 0, -1])
179
+ xp_assert_close(r.as_quat(canonical=False)*math.sqrt(2),
180
+ xp.asarray([1.0, 0, 0, 1]))
181
+ xp_assert_close(r.inv().as_quat(canonical=False)*math.sqrt(2),
182
+ xp.asarray([-1.0, 0, 0, 1]))
183
+ xp_assert_close(r3.as_quat(canonical=False)*math.sqrt(2),
184
+ xp.asarray([1.0, 0, 0, -1]))
185
+ xp_assert_close(r3.inv().as_quat(canonical=False)*math.sqrt(2),
186
+ xp.asarray([-1.0, 0, 0, -1]))
140
187
 
141
188
  # More sanity checks
142
- assert_allclose((r*r.inv()).as_quat(canonical=False),
143
- [0, 0, 0, 1], atol=2e-16)
144
- assert_allclose((r3*r3.inv()).as_quat(canonical=False),
145
- [0, 0, 0, 1], atol=2e-16)
146
- assert_allclose((r*r3).as_quat(canonical=False),
147
- [0, 0, 0, -1], atol=2e-16)
148
- assert_allclose((r.inv()*r3.inv()).as_quat(canonical=False),
149
- [0, 0, 0, -1], atol=2e-16)
189
+ xp_assert_close((r*r.inv()).as_quat(canonical=False),
190
+ xp.asarray([0.0, 0, 0, 1]), atol=2e-16)
191
+ xp_assert_close((r3*r3.inv()).as_quat(canonical=False),
192
+ xp.asarray([0.0, 0, 0, 1]), atol=2e-16)
193
+ xp_assert_close((r*r3).as_quat(canonical=False),
194
+ xp.asarray([0.0, 0, 0, -1]), atol=2e-16)
195
+ xp_assert_close((r.inv() * r3.inv()).as_quat(canonical=False),
196
+ xp.asarray([0.0, 0, 0, -1]), atol=2e-16)
150
197
 
151
198
 
152
- def test_from_quat_wrong_shape():
199
+ def test_from_quat_wrong_shape(xp):
153
200
  # Wrong shape 1d array
154
201
  with pytest.raises(ValueError, match='Expected `quat` to have shape'):
155
- Rotation.from_quat(np.array([1, 2, 3]))
202
+ Rotation.from_quat(xp.asarray([1, 2, 3]))
156
203
 
157
204
  # Wrong shape 2d array
158
205
  with pytest.raises(ValueError, match='Expected `quat` to have shape'):
159
- Rotation.from_quat(np.array([
206
+ Rotation.from_quat(xp.asarray([
160
207
  [1, 2, 3, 4, 5],
161
208
  [4, 5, 6, 7, 8]
162
209
  ]))
163
210
 
164
211
  # 3d array
165
212
  with pytest.raises(ValueError, match='Expected `quat` to have shape'):
166
- Rotation.from_quat(np.array([
213
+ Rotation.from_quat(xp.asarray([
167
214
  [[1, 2, 3, 4]],
168
215
  [[4, 5, 6, 7]]
169
216
  ]))
170
217
 
171
218
 
172
- def test_zero_norms_from_quat():
173
- x = np.array([
219
+ def test_zero_norms_from_quat(xp):
220
+ x = xp.asarray([
174
221
  [3, 4, 0, 0],
175
222
  [0, 0, 0, 0],
176
223
  [5, 0, 12, 0]
177
224
  ])
178
- with pytest.raises(ValueError):
179
- Rotation.from_quat(x)
225
+ if is_lazy_array(x):
226
+ assert xp.all(xp.isnan(Rotation.from_quat(x).as_quat()[1, ...]))
227
+ else:
228
+ with pytest.raises(ValueError):
229
+ Rotation.from_quat(x)
180
230
 
181
231
 
182
- def test_as_matrix_single_1d_quaternion():
183
- quat = [0, 0, 0, 1]
232
+ def test_as_matrix_single_1d_quaternion(xp):
233
+ quat = xp.asarray([0, 0, 0, 1])
184
234
  mat = Rotation.from_quat(quat).as_matrix()
185
235
  # mat.shape == (3,3) due to 1d input
186
- assert_array_almost_equal(mat, np.eye(3))
236
+ xp_assert_close(mat, xp.eye(3))
187
237
 
188
238
 
189
- def test_as_matrix_single_2d_quaternion():
190
- quat = [[0, 0, 1, 1]]
239
+ def test_as_matrix_single_2d_quaternion(xp):
240
+ quat = xp.asarray([[0, 0, 1, 1]])
191
241
  mat = Rotation.from_quat(quat).as_matrix()
192
242
  assert_equal(mat.shape, (1, 3, 3))
193
- expected_mat = np.array([
194
- [0, -1, 0],
243
+ expected_mat = xp.asarray([
244
+ [0.0, -1, 0],
195
245
  [1, 0, 0],
196
246
  [0, 0, 1]
197
247
  ])
198
- assert_array_almost_equal(mat[0], expected_mat)
248
+ xp_assert_close(mat[0, ...], expected_mat)
199
249
 
200
250
 
201
- def test_as_matrix_from_square_input():
202
- quats = [
251
+ def test_as_matrix_from_square_input(xp):
252
+ quats = xp.asarray([
203
253
  [0, 0, 1, 1],
204
254
  [0, 1, 0, 1],
205
255
  [0, 0, 0, 1],
206
256
  [0, 0, 0, -1]
207
- ]
257
+ ])
208
258
  mat = Rotation.from_quat(quats).as_matrix()
209
259
  assert_equal(mat.shape, (4, 3, 3))
210
260
 
211
- expected0 = np.array([
212
- [0, -1, 0],
261
+ expected0 = xp.asarray([
262
+ [0.0, -1, 0],
213
263
  [1, 0, 0],
214
264
  [0, 0, 1]
215
265
  ])
216
- assert_array_almost_equal(mat[0], expected0)
266
+ xp_assert_close(mat[0, ...], expected0)
217
267
 
218
- expected1 = np.array([
219
- [0, 0, 1],
268
+ expected1 = xp.asarray([
269
+ [0.0, 0, 1],
220
270
  [0, 1, 0],
221
271
  [-1, 0, 0]
222
272
  ])
223
- assert_array_almost_equal(mat[1], expected1)
273
+ xp_assert_close(mat[1, ...], expected1)
224
274
 
225
- assert_array_almost_equal(mat[2], np.eye(3))
226
- assert_array_almost_equal(mat[3], np.eye(3))
275
+ xp_assert_close(mat[2, ...], xp.eye(3))
276
+ xp_assert_close(mat[3, ...], xp.eye(3))
227
277
 
228
278
 
229
- def test_as_matrix_from_generic_input():
230
- quats = [
279
+ def test_as_matrix_from_generic_input(xp):
280
+ quats = xp.asarray([
231
281
  [0, 0, 1, 1],
232
282
  [0, 1, 0, 1],
233
283
  [1, 2, 3, 4]
234
- ]
284
+ ])
235
285
  mat = Rotation.from_quat(quats).as_matrix()
236
286
  assert_equal(mat.shape, (3, 3, 3))
237
287
 
238
- expected0 = np.array([
239
- [0, -1, 0],
288
+ expected0 = xp.asarray([
289
+ [0.0, -1, 0],
240
290
  [1, 0, 0],
241
291
  [0, 0, 1]
242
292
  ])
243
- assert_array_almost_equal(mat[0], expected0)
293
+ xp_assert_close(mat[0, ...], expected0)
244
294
 
245
- expected1 = np.array([
246
- [0, 0, 1],
295
+ expected1 = xp.asarray([
296
+ [0.0, 0, 1],
247
297
  [0, 1, 0],
248
298
  [-1, 0, 0]
249
299
  ])
250
- assert_array_almost_equal(mat[1], expected1)
300
+ xp_assert_close(mat[1, ...], expected1)
251
301
 
252
- expected2 = np.array([
302
+ expected2 = xp.asarray([
253
303
  [0.4, -2, 2.2],
254
304
  [2.8, 1, 0.4],
255
305
  [-1, 2, 2]
256
306
  ]) / 3
257
- assert_array_almost_equal(mat[2], expected2)
307
+ xp_assert_close(mat[2, ...], expected2)
258
308
 
259
309
 
260
- def test_from_single_2d_matrix():
261
- mat = [
310
+ def test_from_single_2d_matrix(xp):
311
+ mat = xp.asarray([
262
312
  [0, 0, 1],
263
313
  [1, 0, 0],
264
314
  [0, 1, 0]
265
- ]
266
- expected_quat = [0.5, 0.5, 0.5, 0.5]
267
- assert_array_almost_equal(
268
- Rotation.from_matrix(mat).as_quat(),
269
- expected_quat)
315
+ ])
316
+ expected_quat = xp.asarray([0.5, 0.5, 0.5, 0.5])
317
+ xp_assert_close(Rotation.from_matrix(mat).as_quat(), expected_quat)
270
318
 
271
319
 
272
- def test_from_single_3d_matrix():
273
- mat = np.array([
320
+ def test_from_single_3d_matrix(xp):
321
+ mat = xp.asarray([[
274
322
  [0, 0, 1],
275
323
  [1, 0, 0],
276
- [0, 1, 0]
277
- ]).reshape((1, 3, 3))
278
- expected_quat = np.array([0.5, 0.5, 0.5, 0.5]).reshape((1, 4))
279
- assert_array_almost_equal(
280
- Rotation.from_matrix(mat).as_quat(),
281
- expected_quat)
324
+ [0, 1, 0],
325
+ ]])
326
+ expected_quat = xp.asarray([[0.5, 0.5, 0.5, 0.5]])
327
+ xp_assert_close(Rotation.from_matrix(mat).as_quat(), expected_quat)
282
328
 
283
329
 
284
- def test_from_matrix_calculation():
285
- expected_quat = np.array([1, 1, 6, 1]) / np.sqrt(39)
286
- mat = np.array([
330
+ def test_from_matrix_calculation(xp):
331
+ atol = 1e-8
332
+ expected_quat = xp.asarray([1.0, 1, 6, 1]) / math.sqrt(39)
333
+ mat = xp.asarray([
287
334
  [-0.8974359, -0.2564103, 0.3589744],
288
335
  [0.3589744, -0.8974359, 0.2564103],
289
336
  [0.2564103, 0.3589744, 0.8974359]
290
337
  ])
291
- assert_array_almost_equal(
292
- Rotation.from_matrix(mat).as_quat(),
293
- expected_quat)
294
- assert_array_almost_equal(
295
- Rotation.from_matrix(mat.reshape((1, 3, 3))).as_quat(),
296
- expected_quat.reshape((1, 4)))
338
+ xp_assert_close(Rotation.from_matrix(mat).as_quat(), expected_quat, atol=atol)
339
+ xp_assert_close(Rotation.from_matrix(xp.reshape(mat, (1, 3, 3))).as_quat(),
340
+ xp.reshape(expected_quat, (1, 4)),
341
+ atol=atol)
297
342
 
298
343
 
299
- def test_matrix_calculation_pipeline():
300
- mat = special_ortho_group.rvs(3, size=10, random_state=0)
301
- assert_array_almost_equal(Rotation.from_matrix(mat).as_matrix(), mat)
344
+ def test_matrix_calculation_pipeline(xp):
345
+ mat = xp.asarray(special_ortho_group.rvs(3, size=10, random_state=0))
346
+ xp_assert_close(Rotation.from_matrix(mat).as_matrix(), mat)
302
347
 
303
348
 
304
- def test_from_matrix_ortho_output():
349
+ def test_from_matrix_ortho_output(xp):
350
+ atol = 1e-12
305
351
  rnd = np.random.RandomState(0)
306
- mat = rnd.random_sample((100, 3, 3))
307
- dets = np.linalg.det(mat)
308
- for i in range(len(dets)):
352
+ mat = xp.asarray(rnd.random_sample((100, 3, 3)))
353
+ dets = xp.linalg.det(mat)
354
+ for i in range(dets.shape[0]):
309
355
  # Make sure we have a right-handed rotation matrix
310
356
  if dets[i] < 0:
311
- mat[i] = -mat[i]
357
+ mat = xpx.at(mat)[i, ...].set(-mat[i, ...])
312
358
  ortho_mat = Rotation.from_matrix(mat).as_matrix()
313
359
 
314
- mult_result = np.einsum('...ij,...jk->...ik', ortho_mat,
315
- ortho_mat.transpose((0, 2, 1)))
316
-
317
- eye3d = np.zeros((100, 3, 3))
318
- for i in range(3):
319
- eye3d[:, i, i] = 1.0
360
+ mult_result = xp.matmul(ortho_mat, xp.matrix_transpose(ortho_mat))
320
361
 
321
- assert_array_almost_equal(mult_result, eye3d)
362
+ eye3d = xp.zeros((100, 3, 3)) + xp.eye(3)
363
+ xp_assert_close(mult_result, eye3d, atol=atol)
322
364
 
323
365
 
324
- def test_from_matrix_normalize():
325
- mat = np.array([
366
+ def test_from_matrix_normalize(xp):
367
+ mat = xp.asarray([
326
368
  [1, 1, 0],
327
369
  [0, 1, 0],
328
370
  [0, 0, 1]])
329
- expected = np.array([[ 0.894427, 0.447214, 0.0],
330
- [-0.447214, 0.894427, 0.0],
331
- [ 0.0, 0.0, 1.0]])
332
- assert_allclose(Rotation.from_matrix(mat).as_matrix(), expected, atol=1e-6)
371
+ expected = xp.asarray([[ 0.894427, 0.447214, 0.0],
372
+ [-0.447214, 0.894427, 0.0],
373
+ [ 0.0, 0.0, 1.0]])
374
+ xp_assert_close(Rotation.from_matrix(mat).as_matrix(), expected, atol=1e-6)
333
375
 
334
- mat = np.array([
376
+ mat = xp.asarray([
335
377
  [0, -0.5, 0 ],
336
378
  [0.5, 0 , 0 ],
337
379
  [0, 0 , 0.5]])
338
- expected = np.array([[ 0, -1, 0],
339
- [ 1, 0, 0],
340
- [ 0, 0, 1]])
341
- assert_allclose(Rotation.from_matrix(mat).as_matrix(), expected, atol=1e-6)
342
-
343
-
344
- def test_from_matrix_non_positive_determinant():
345
- mat = np.eye(3)
346
- mat[0, 0] = 0
347
- with pytest.raises(ValueError, match='Non-positive determinant'):
348
- Rotation.from_matrix(mat)
349
-
350
- mat[0, 0] = -1
351
- with pytest.raises(ValueError, match='Non-positive determinant'):
352
- Rotation.from_matrix(mat)
380
+ expected = xp.asarray([[0.0, -1, 0],
381
+ [ 1, 0, 0],
382
+ [ 0, 0, 1]])
383
+ xp_assert_close(Rotation.from_matrix(mat).as_matrix(), expected, atol=1e-6)
384
+
385
+
386
+ def test_from_matrix_non_positive_determinant(xp):
387
+ mat = xp.eye(3)
388
+ mat = xpx.at(mat)[0, 0].set(0)
389
+ if is_lazy_array(mat):
390
+ assert xp.all(xp.isnan(Rotation.from_matrix(mat).as_matrix()))
391
+ else:
392
+ with pytest.raises(ValueError, match="Non-positive determinant"):
393
+ Rotation.from_matrix(mat)
394
+
395
+ mat = xpx.at(mat)[0, 0].set(-1)
396
+ if is_lazy_array(mat):
397
+ assert xp.all(xp.isnan(Rotation.from_matrix(mat).as_matrix()))
398
+ else:
399
+ with pytest.raises(ValueError, match="Non-positive determinant"):
400
+ Rotation.from_matrix(mat)
401
+
402
+
403
+ def test_from_matrix_array_like():
404
+ rng = np.random.default_rng(123)
405
+ # Single rotation
406
+ r_expected = Rotation.random(rng=rng)
407
+ r = Rotation.from_matrix(r_expected.as_matrix().tolist())
408
+ assert r_expected.approx_equal(r, atol=1e-12)
409
+
410
+ # Multiple rotations
411
+ r_expected = Rotation.random(3, rng=rng)
412
+ r = Rotation.from_matrix(r_expected.as_matrix().tolist())
413
+ assert np.all(r_expected.approx_equal(r, atol=1e-12))
414
+
415
+
416
+ def test_from_matrix_int_dtype(xp):
417
+ mat = xp.asarray([[1, 0, 0], [0, 1, 0], [0, 0, 1]])
418
+ r = Rotation.from_matrix(mat)
419
+ assert r.as_quat().dtype == xp_default_dtype(xp)
353
420
 
354
421
 
355
- def test_from_1d_single_rotvec():
356
- rotvec = [1, 0, 0]
357
- expected_quat = np.array([0.4794255, 0, 0, 0.8775826])
422
+ def test_from_1d_single_rotvec(xp):
423
+ atol = 1e-7
424
+ rotvec = xp.asarray([1, 0, 0])
425
+ expected_quat = xp.asarray([0.4794255, 0, 0, 0.8775826])
358
426
  result = Rotation.from_rotvec(rotvec)
359
- assert_array_almost_equal(result.as_quat(), expected_quat)
427
+ xp_assert_close(result.as_quat(), expected_quat, atol=atol)
360
428
 
361
429
 
362
- def test_from_2d_single_rotvec():
363
- rotvec = [[1, 0, 0]]
364
- expected_quat = np.array([[0.4794255, 0, 0, 0.8775826]])
430
+ def test_from_2d_single_rotvec(xp):
431
+ atol = 1e-7
432
+ rotvec = xp.asarray([[1, 0, 0]])
433
+ expected_quat = xp.asarray([[0.4794255, 0, 0, 0.8775826]])
365
434
  result = Rotation.from_rotvec(rotvec)
366
- assert_array_almost_equal(result.as_quat(), expected_quat)
435
+ xp_assert_close(result.as_quat(), expected_quat, atol=atol)
367
436
 
368
437
 
369
- def test_from_generic_rotvec():
370
- rotvec = [
438
+ def test_from_generic_rotvec(xp):
439
+ atol = 1e-7
440
+ rotvec = xp.asarray([
371
441
  [1, 2, 2],
372
442
  [1, -1, 0.5],
373
- [0, 0, 0]
374
- ]
375
- expected_quat = np.array([
443
+ [0, 0, 0]])
444
+ expected_quat = xp.asarray([
376
445
  [0.3324983, 0.6649967, 0.6649967, 0.0707372],
377
446
  [0.4544258, -0.4544258, 0.2272129, 0.7316889],
378
447
  [0, 0, 0, 1]
379
448
  ])
380
- assert_array_almost_equal(
381
- Rotation.from_rotvec(rotvec).as_quat(),
382
- expected_quat)
449
+ xp_assert_close(Rotation.from_rotvec(rotvec).as_quat(), expected_quat, atol=atol)
383
450
 
384
451
 
385
- def test_from_rotvec_small_angle():
386
- rotvec = np.array([
387
- [5e-4 / np.sqrt(3), -5e-4 / np.sqrt(3), 5e-4 / np.sqrt(3)],
452
+ def test_from_rotvec_small_angle(xp):
453
+ rotvec = xp.asarray([
454
+ [5e-4 / math.sqrt(3), -5e-4 / math.sqrt(3), 5e-4 / math.sqrt(3)],
388
455
  [0.2, 0.3, 0.4],
389
456
  [0, 0, 0]
390
457
  ])
391
458
 
392
459
  quat = Rotation.from_rotvec(rotvec).as_quat()
393
460
  # cos(theta/2) ~~ 1 for small theta
394
- assert_allclose(quat[0, 3], 1)
461
+ xp_assert_close(quat[0, 3], xp.asarray(1.0)[()])
395
462
  # sin(theta/2) / theta ~~ 0.5 for small theta
396
- assert_allclose(quat[0, :3], rotvec[0] * 0.5)
463
+ xp_assert_close(quat[0, :3], rotvec[0, ...] * 0.5)
397
464
 
398
- assert_allclose(quat[1, 3], 0.9639685)
399
- assert_allclose(
400
- quat[1, :3],
401
- np.array([
465
+ xp_assert_close(quat[1, 3], xp.asarray(0.9639685)[()])
466
+ xp_assert_close(quat[1, :3],
467
+ xp.asarray([
402
468
  0.09879603932153465,
403
469
  0.14819405898230198,
404
- 0.19759207864306931
405
- ]))
470
+ 0.19759207864306931]))
471
+
472
+ xp_assert_equal(quat[2, ...], xp.asarray([0.0, 0, 0, 1]))
473
+
406
474
 
407
- assert_equal(quat[2], np.array([0, 0, 0, 1]))
475
+ def test_from_rotvec_array_like():
476
+ rng = np.random.default_rng(123)
477
+ # Single rotation
478
+ r_expected = Rotation.random(rng=rng)
479
+ r = Rotation.from_rotvec(r_expected.as_rotvec().tolist())
480
+ assert r_expected.approx_equal(r, atol=1e-12)
408
481
 
482
+ # Multiple rotations
483
+ r_expected = Rotation.random(3, rng=rng)
484
+ r = Rotation.from_rotvec(r_expected.as_rotvec().tolist())
485
+ assert np.all(r_expected.approx_equal(r, atol=1e-12))
409
486
 
410
- def test_degrees_from_rotvec():
411
- rotvec1 = [1.0 / np.cbrt(3), 1.0 / np.cbrt(3), 1.0 / np.cbrt(3)]
487
+
488
+ def test_from_rotvec_int_dtype(xp):
489
+ rotvec = xp.asarray([1, 0, 0])
490
+ r = Rotation.from_rotvec(rotvec)
491
+ assert r.as_quat().dtype == xp_default_dtype(xp)
492
+
493
+
494
+ def test_degrees_from_rotvec(xp):
495
+ rotvec1 = xp.asarray([1 / 3 ** (1/3)] * 3)
412
496
  rot1 = Rotation.from_rotvec(rotvec1, degrees=True)
413
497
  quat1 = rot1.as_quat()
414
498
 
415
- rotvec2 = np.deg2rad(rotvec1)
499
+ # deg2rad is not implemented in Array API -> / 180 * xp.pi
500
+ rotvec2 = xp.asarray(rotvec1 / 180 * xp.pi)
416
501
  rot2 = Rotation.from_rotvec(rotvec2)
417
502
  quat2 = rot2.as_quat()
418
503
 
419
- assert_allclose(quat1, quat2)
504
+ xp_assert_close(quat1, quat2)
420
505
 
421
506
 
422
- def test_malformed_1d_from_rotvec():
507
+ def test_malformed_1d_from_rotvec(xp):
423
508
  with pytest.raises(ValueError, match='Expected `rot_vec` to have shape'):
424
- Rotation.from_rotvec([1, 2])
509
+ Rotation.from_rotvec(xp.asarray([1, 2]))
425
510
 
426
511
 
427
- def test_malformed_2d_from_rotvec():
512
+ def test_malformed_2d_from_rotvec(xp):
428
513
  with pytest.raises(ValueError, match='Expected `rot_vec` to have shape'):
429
- Rotation.from_rotvec([
514
+ Rotation.from_rotvec(xp.asarray([
430
515
  [1, 2, 3, 4],
431
516
  [5, 6, 7, 8]
432
- ])
517
+ ]))
433
518
 
434
519
 
435
- def test_as_generic_rotvec():
436
- quat = np.array([
520
+ def test_as_generic_rotvec(xp):
521
+ quat = xp.asarray([
437
522
  [1, 2, -1, 0.5],
438
523
  [1, -1, 1, 0.0003],
439
524
  [0, 0, 0, 1]
440
525
  ])
441
- quat /= np.linalg.norm(quat, axis=1)[:, None]
526
+ quat /= xp_vector_norm(quat, axis=-1, keepdims=True)
442
527
 
443
528
  rotvec = Rotation.from_quat(quat).as_rotvec()
444
- angle = np.linalg.norm(rotvec, axis=1)
529
+ angle = xp_vector_norm(rotvec, axis=-1)
445
530
 
446
- assert_allclose(quat[:, 3], np.cos(angle/2))
447
- assert_allclose(np.cross(rotvec, quat[:, :3]), np.zeros((3, 3)))
531
+ xp_assert_close(quat[:, 3], xp.cos(angle / 2))
532
+ xp_assert_close(xp.linalg.cross(rotvec, quat[:, :3]), xp.zeros((3, 3)), atol=1e-15)
448
533
 
449
534
 
450
- def test_as_rotvec_single_1d_input():
451
- quat = np.array([1, 2, -3, 2])
452
- expected_rotvec = np.array([0.5772381, 1.1544763, -1.7317144])
535
+ def test_as_rotvec_single_1d_input(xp):
536
+ quat = xp.asarray([1, 2, -3, 2])
537
+ expected_rotvec = xp.asarray([0.5772381, 1.1544763, -1.7317144])
453
538
 
454
539
  actual_rotvec = Rotation.from_quat(quat).as_rotvec()
455
540
 
456
541
  assert_equal(actual_rotvec.shape, (3,))
457
- assert_allclose(actual_rotvec, expected_rotvec)
542
+ xp_assert_close(actual_rotvec, expected_rotvec)
458
543
 
459
544
 
460
- def test_as_rotvec_single_2d_input():
461
- quat = np.array([[1, 2, -3, 2]])
462
- expected_rotvec = np.array([[0.5772381, 1.1544763, -1.7317144]])
545
+ def test_as_rotvec_single_2d_input(xp):
546
+ quat = xp.asarray([[1, 2, -3, 2]])
547
+ expected_rotvec = xp.asarray([[0.5772381, 1.1544763, -1.7317144]])
463
548
 
464
549
  actual_rotvec = Rotation.from_quat(quat).as_rotvec()
465
550
 
466
551
  assert_equal(actual_rotvec.shape, (1, 3))
467
- assert_allclose(actual_rotvec, expected_rotvec)
552
+ xp_assert_close(actual_rotvec, expected_rotvec)
468
553
 
469
554
 
470
- def test_as_rotvec_degrees():
555
+ def test_as_rotvec_degrees(xp):
471
556
  # x->y, y->z, z->x
472
- mat = [[0, 0, 1], [1, 0, 0], [0, 1, 0]]
557
+ mat = xp.asarray([[0, 0, 1], [1, 0, 0], [0, 1, 0]])
473
558
  rot = Rotation.from_matrix(mat)
474
559
  rotvec = rot.as_rotvec(degrees=True)
475
- angle = np.linalg.norm(rotvec)
476
- assert_allclose(angle, 120.0)
477
- assert_allclose(rotvec[0], rotvec[1])
478
- assert_allclose(rotvec[1], rotvec[2])
560
+ angle = xp_vector_norm(rotvec, axis=-1)
561
+ xp_assert_close(angle, xp.asarray(120.0)[()])
562
+ xp_assert_close(rotvec[0], rotvec[1])
563
+ xp_assert_close(rotvec[1], rotvec[2])
479
564
 
480
565
 
481
- def test_rotvec_calc_pipeline():
566
+ def test_rotvec_calc_pipeline(xp):
482
567
  # Include small angles
483
- rotvec = np.array([
568
+ rotvec = xp.asarray([
484
569
  [0, 0, 0],
485
570
  [1, -1, 2],
486
571
  [-3e-4, 3.5e-4, 7.5e-5]
487
572
  ])
488
- assert_allclose(Rotation.from_rotvec(rotvec).as_rotvec(), rotvec)
489
- assert_allclose(Rotation.from_rotvec(rotvec, degrees=True).as_rotvec(degrees=True),
573
+ xp_assert_close(Rotation.from_rotvec(rotvec).as_rotvec(), rotvec)
574
+ xp_assert_close(Rotation.from_rotvec(rotvec, degrees=True).as_rotvec(degrees=True),
490
575
  rotvec)
491
576
 
492
577
 
493
- def test_from_1d_single_mrp():
494
- mrp = [0, 0, 1.0]
495
- expected_quat = np.array([0, 0, 1, 0])
578
+ def test_from_1d_single_mrp(xp):
579
+ mrp = xp.asarray([0, 0, 1.0])
580
+ expected_quat = xp.asarray([0.0, 0, 1, 0])
496
581
  result = Rotation.from_mrp(mrp)
497
- assert_array_almost_equal(result.as_quat(), expected_quat)
582
+ xp_assert_close(result.as_quat(), expected_quat, atol=1e-12)
498
583
 
499
584
 
500
- def test_from_2d_single_mrp():
501
- mrp = [[0, 0, 1.0]]
502
- expected_quat = np.array([[0, 0, 1, 0]])
585
+ def test_from_2d_single_mrp(xp):
586
+ mrp = xp.asarray([[0, 0, 1.0]])
587
+ expected_quat = xp.asarray([[0.0, 0, 1, 0]])
503
588
  result = Rotation.from_mrp(mrp)
504
- assert_array_almost_equal(result.as_quat(), expected_quat)
589
+ xp_assert_close(result.as_quat(), expected_quat)
590
+
591
+
592
+ def test_from_mrp_array_like():
593
+ rng = np.random.default_rng(123)
594
+ # Single rotation
595
+ r_expected = Rotation.random(rng=rng)
596
+ r = Rotation.from_mrp(r_expected.as_mrp().tolist())
597
+ assert r_expected.approx_equal(r, atol=1e-12)
598
+
599
+ # Multiple rotations
600
+ r_expected = Rotation.random(3, rng=rng)
601
+ r = Rotation.from_mrp(r_expected.as_mrp().tolist())
602
+ assert np.all(r_expected.approx_equal(r, atol=1e-12))
505
603
 
506
604
 
507
- def test_from_generic_mrp():
508
- mrp = np.array([
605
+ def test_from_mrp_int_dtype(xp):
606
+ mrp = xp.asarray([0, 0, 1])
607
+ r = Rotation.from_mrp(mrp)
608
+ assert r.as_quat().dtype == xp_default_dtype(xp)
609
+
610
+
611
+ def test_from_generic_mrp(xp):
612
+ mrp = xp.asarray([
509
613
  [1, 2, 2],
510
614
  [1, -1, 0.5],
511
615
  [0, 0, 0]])
512
- expected_quat = np.array([
616
+ expected_quat = xp.asarray([
513
617
  [0.2, 0.4, 0.4, -0.8],
514
618
  [0.61538462, -0.61538462, 0.30769231, -0.38461538],
515
619
  [0, 0, 0, 1]])
516
- assert_array_almost_equal(Rotation.from_mrp(mrp).as_quat(), expected_quat)
620
+ xp_assert_close(Rotation.from_mrp(mrp).as_quat(), expected_quat)
517
621
 
518
622
 
519
- def test_malformed_1d_from_mrp():
623
+ def test_malformed_1d_from_mrp(xp):
520
624
  with pytest.raises(ValueError, match='Expected `mrp` to have shape'):
521
- Rotation.from_mrp([1, 2])
625
+ Rotation.from_mrp(xp.asarray([1, 2]))
522
626
 
523
627
 
524
- def test_malformed_2d_from_mrp():
628
+ def test_malformed_2d_from_mrp(xp):
525
629
  with pytest.raises(ValueError, match='Expected `mrp` to have shape'):
526
- Rotation.from_mrp([
630
+ Rotation.from_mrp(xp.asarray([
527
631
  [1, 2, 3, 4],
528
632
  [5, 6, 7, 8]
529
- ])
633
+ ]))
530
634
 
531
635
 
532
- def test_as_generic_mrp():
533
- quat = np.array([
636
+ def test_as_generic_mrp(xp):
637
+ quat = xp.asarray([
534
638
  [1, 2, -1, 0.5],
535
639
  [1, -1, 1, 0.0003],
536
640
  [0, 0, 0, 1]])
537
- quat /= np.linalg.norm(quat, axis=1)[:, None]
641
+ quat /= xp_vector_norm(quat, axis=1)[:, None]
538
642
 
539
- expected_mrp = np.array([
643
+ expected_mrp = xp.asarray([
540
644
  [0.33333333, 0.66666667, -0.33333333],
541
645
  [0.57725028, -0.57725028, 0.57725028],
542
646
  [0, 0, 0]])
543
- assert_array_almost_equal(Rotation.from_quat(quat).as_mrp(), expected_mrp)
647
+ xp_assert_close(Rotation.from_quat(quat).as_mrp(), expected_mrp)
544
648
 
545
- def test_past_180_degree_rotation():
649
+
650
+ def test_past_180_degree_rotation(xp):
546
651
  # ensure that a > 180 degree rotation is returned as a <180 rotation in MRPs
547
652
  # in this case 270 should be returned as -90
548
- expected_mrp = np.array([-np.tan(np.pi/2/4), 0.0, 0])
549
- assert_array_almost_equal(
550
- Rotation.from_euler('xyz', [270, 0, 0], degrees=True).as_mrp(),
551
- expected_mrp
653
+ expected_mrp = xp.asarray([-math.tan(xp.pi / 2 / 4), 0.0, 0])
654
+ xp_assert_close(
655
+ Rotation.from_euler('xyz', xp.asarray([270, 0, 0]), degrees=True).as_mrp(),
656
+ expected_mrp,
552
657
  )
553
658
 
554
659
 
555
- def test_as_mrp_single_1d_input():
556
- quat = np.array([1, 2, -3, 2])
557
- expected_mrp = np.array([0.16018862, 0.32037724, -0.48056586])
660
+ def test_as_mrp_single_1d_input(xp):
661
+ quat = xp.asarray([1, 2, -3, 2])
662
+ expected_mrp = xp.asarray([0.16018862, 0.32037724, -0.48056586])
558
663
 
559
664
  actual_mrp = Rotation.from_quat(quat).as_mrp()
560
665
 
561
666
  assert_equal(actual_mrp.shape, (3,))
562
- assert_allclose(actual_mrp, expected_mrp)
667
+ xp_assert_close(actual_mrp, expected_mrp)
563
668
 
564
669
 
565
- def test_as_mrp_single_2d_input():
566
- quat = np.array([[1, 2, -3, 2]])
567
- expected_mrp = np.array([[0.16018862, 0.32037724, -0.48056586]])
670
+ def test_as_mrp_single_2d_input(xp):
671
+ quat = xp.asarray([[1, 2, -3, 2]])
672
+ expected_mrp = xp.asarray([[0.16018862, 0.32037724, -0.48056586]])
568
673
 
569
674
  actual_mrp = Rotation.from_quat(quat).as_mrp()
570
675
 
571
676
  assert_equal(actual_mrp.shape, (1, 3))
572
- assert_allclose(actual_mrp, expected_mrp)
677
+ xp_assert_close(actual_mrp, expected_mrp)
573
678
 
574
679
 
575
- def test_mrp_calc_pipeline():
576
- actual_mrp = np.array([
680
+ def test_mrp_calc_pipeline(xp):
681
+ actual_mrp = xp.asarray([
577
682
  [0, 0, 0],
578
683
  [1, -1, 2],
579
684
  [0.41421356, 0, 0],
580
685
  [0.1, 0.2, 0.1]])
581
- expected_mrp = np.array([
686
+ expected_mrp = xp.asarray([
582
687
  [0, 0, 0],
583
688
  [-0.16666667, 0.16666667, -0.33333333],
584
689
  [0.41421356, 0, 0],
585
690
  [0.1, 0.2, 0.1]])
586
- assert_allclose(Rotation.from_mrp(actual_mrp).as_mrp(), expected_mrp)
691
+ xp_assert_close(Rotation.from_mrp(actual_mrp).as_mrp(), expected_mrp)
587
692
 
588
693
 
589
- def test_from_euler_single_rotation():
590
- quat = Rotation.from_euler('z', 90, degrees=True).as_quat()
591
- expected_quat = np.array([0, 0, 1, 1]) / np.sqrt(2)
592
- assert_allclose(quat, expected_quat)
694
+ def test_from_euler_single_rotation(xp):
695
+ quat = Rotation.from_euler("z", xp.asarray(90), degrees=True).as_quat()
696
+ expected_quat = xp.asarray([0.0, 0, 1, 1]) / math.sqrt(2)
697
+ xp_assert_close(quat, expected_quat)
593
698
 
594
699
 
595
- def test_single_intrinsic_extrinsic_rotation():
596
- extrinsic = Rotation.from_euler('z', 90, degrees=True).as_matrix()
597
- intrinsic = Rotation.from_euler('Z', 90, degrees=True).as_matrix()
598
- assert_allclose(extrinsic, intrinsic)
700
+ def test_single_intrinsic_extrinsic_rotation(xp):
701
+ extrinsic = Rotation.from_euler('z', xp.asarray(90), degrees=True).as_matrix()
702
+ intrinsic = Rotation.from_euler('Z', xp.asarray(90), degrees=True).as_matrix()
703
+ xp_assert_close(extrinsic, intrinsic)
599
704
 
600
705
 
601
- def test_from_euler_rotation_order():
706
+ def test_from_euler_rotation_order(xp):
602
707
  # Intrinsic rotation is same as extrinsic with order reversed
603
708
  rnd = np.random.RandomState(0)
604
- a = rnd.randint(low=0, high=180, size=(6, 3))
605
- b = a[:, ::-1]
709
+ a = xp.asarray(rnd.randint(low=0, high=180, size=(6, 3)))
710
+ b = xp.flip(a, axis=-1)
606
711
  x = Rotation.from_euler('xyz', a, degrees=True).as_quat()
607
712
  y = Rotation.from_euler('ZYX', b, degrees=True).as_quat()
608
- assert_allclose(x, y)
713
+ xp_assert_close(x, y)
609
714
 
610
715
 
611
- def test_from_euler_elementary_extrinsic_rotation():
716
+ def test_from_euler_elementary_extrinsic_rotation(xp):
717
+ atol = 1e-12
612
718
  # Simple test to check if extrinsic rotations are implemented correctly
613
- mat = Rotation.from_euler('zx', [90, 90], degrees=True).as_matrix()
614
- expected_mat = np.array([
615
- [0, -1, 0],
719
+ mat = Rotation.from_euler('zx', xp.asarray([90, 90]), degrees=True).as_matrix()
720
+ expected_mat = xp.asarray([
721
+ [0.0, -1, 0],
616
722
  [0, 0, -1],
617
723
  [1, 0, 0]
618
724
  ])
619
- assert_array_almost_equal(mat, expected_mat)
725
+ xp_assert_close(mat, expected_mat, atol=atol)
620
726
 
621
727
 
622
- def test_from_euler_intrinsic_rotation_312():
623
- angles = [
728
+ def test_from_euler_intrinsic_rotation_312(xp):
729
+ atol = 1e-7
730
+ angles = xp.asarray([
624
731
  [30, 60, 45],
625
732
  [30, 60, 30],
626
733
  [45, 30, 60]
627
- ]
734
+ ])
628
735
  mat = Rotation.from_euler('ZXY', angles, degrees=True).as_matrix()
629
736
 
630
- assert_array_almost_equal(mat[0], np.array([
737
+ xp_assert_close(mat[0, ...], xp.asarray([
631
738
  [0.3061862, -0.2500000, 0.9185587],
632
739
  [0.8838835, 0.4330127, -0.1767767],
633
740
  [-0.3535534, 0.8660254, 0.3535534]
634
- ]))
741
+ ]), atol=atol)
635
742
 
636
- assert_array_almost_equal(mat[1], np.array([
743
+ xp_assert_close(mat[1, ...], xp.asarray([
637
744
  [0.5334936, -0.2500000, 0.8080127],
638
745
  [0.8080127, 0.4330127, -0.3995191],
639
746
  [-0.2500000, 0.8660254, 0.4330127]
640
- ]))
747
+ ]), atol=atol)
641
748
 
642
- assert_array_almost_equal(mat[2], np.array([
749
+ xp_assert_close(mat[2, ...], xp.asarray([
643
750
  [0.0473672, -0.6123725, 0.7891491],
644
751
  [0.6597396, 0.6123725, 0.4355958],
645
752
  [-0.7500000, 0.5000000, 0.4330127]
646
- ]))
753
+ ]), atol=atol)
647
754
 
648
755
 
649
- def test_from_euler_intrinsic_rotation_313():
650
- angles = [
756
+ def test_from_euler_intrinsic_rotation_313(xp):
757
+ angles = xp.asarray([
651
758
  [30, 60, 45],
652
759
  [30, 60, 30],
653
760
  [45, 30, 60]
654
- ]
761
+ ])
655
762
  mat = Rotation.from_euler('ZXZ', angles, degrees=True).as_matrix()
656
763
 
657
- assert_array_almost_equal(mat[0], np.array([
764
+ xp_assert_close(mat[0, ...], xp.asarray([
658
765
  [0.43559574, -0.78914913, 0.4330127],
659
766
  [0.65973961, -0.04736717, -0.750000],
660
767
  [0.61237244, 0.61237244, 0.500000]
661
768
  ]))
662
769
 
663
- assert_array_almost_equal(mat[1], np.array([
770
+ xp_assert_close(mat[1, ...], xp.asarray([
664
771
  [0.6250000, -0.64951905, 0.4330127],
665
772
  [0.64951905, 0.1250000, -0.750000],
666
773
  [0.4330127, 0.750000, 0.500000]
667
774
  ]))
668
775
 
669
- assert_array_almost_equal(mat[2], np.array([
776
+ xp_assert_close(mat[2, ...], xp.asarray([
670
777
  [-0.1767767, -0.91855865, 0.35355339],
671
778
  [0.88388348, -0.30618622, -0.35355339],
672
779
  [0.4330127, 0.25000000, 0.8660254]
673
780
  ]))
674
781
 
675
782
 
676
- def test_from_euler_extrinsic_rotation_312():
677
- angles = [
783
+ def test_from_euler_extrinsic_rotation_312(xp):
784
+ angles = xp.asarray([
678
785
  [30, 60, 45],
679
786
  [30, 60, 30],
680
787
  [45, 30, 60]
681
- ]
788
+ ])
682
789
  mat = Rotation.from_euler('zxy', angles, degrees=True).as_matrix()
683
790
 
684
- assert_array_almost_equal(mat[0], np.array([
791
+ xp_assert_close(mat[0, ...], xp.asarray([
685
792
  [0.91855865, 0.1767767, 0.35355339],
686
793
  [0.25000000, 0.4330127, -0.8660254],
687
794
  [-0.30618622, 0.88388348, 0.35355339]
688
795
  ]))
689
796
 
690
- assert_array_almost_equal(mat[1], np.array([
797
+ xp_assert_close(mat[1, ...], xp.asarray([
691
798
  [0.96650635, -0.0580127, 0.2500000],
692
799
  [0.25000000, 0.4330127, -0.8660254],
693
800
  [-0.0580127, 0.89951905, 0.4330127]
694
801
  ]))
695
802
 
696
- assert_array_almost_equal(mat[2], np.array([
803
+ xp_assert_close(mat[2, ...], xp.asarray([
697
804
  [0.65973961, -0.04736717, 0.7500000],
698
805
  [0.61237244, 0.61237244, -0.5000000],
699
806
  [-0.43559574, 0.78914913, 0.4330127]
700
807
  ]))
701
808
 
702
809
 
703
- def test_from_euler_extrinsic_rotation_313():
704
- angles = [
810
+ def test_from_euler_extrinsic_rotation_313(xp):
811
+ angles = xp.asarray([
705
812
  [30, 60, 45],
706
813
  [30, 60, 30],
707
814
  [45, 30, 60]
708
- ]
815
+ ])
709
816
  mat = Rotation.from_euler('zxz', angles, degrees=True).as_matrix()
710
817
 
711
- assert_array_almost_equal(mat[0], np.array([
818
+ xp_assert_close(mat[0, ...], xp.asarray([
712
819
  [0.43559574, -0.65973961, 0.61237244],
713
820
  [0.78914913, -0.04736717, -0.61237244],
714
821
  [0.4330127, 0.75000000, 0.500000]
715
822
  ]))
716
823
 
717
- assert_array_almost_equal(mat[1], np.array([
824
+ xp_assert_close(mat[1, ...], xp.asarray([
718
825
  [0.62500000, -0.64951905, 0.4330127],
719
826
  [0.64951905, 0.12500000, -0.750000],
720
827
  [0.4330127, 0.75000000, 0.500000]
721
828
  ]))
722
829
 
723
- assert_array_almost_equal(mat[2], np.array([
830
+ xp_assert_close(mat[2, ...], xp.asarray([
724
831
  [-0.1767767, -0.88388348, 0.4330127],
725
832
  [0.91855865, -0.30618622, -0.250000],
726
833
  [0.35355339, 0.35355339, 0.8660254]
727
834
  ]))
728
835
 
729
836
 
837
+ def test_from_euler_array_like():
838
+ rng = np.random.default_rng(123)
839
+ order = "xyz"
840
+ # Single rotation
841
+ r_expected = Rotation.random(rng=rng)
842
+ r = Rotation.from_euler(order, r_expected.as_euler(order).tolist())
843
+ assert r_expected.approx_equal(r, atol=1e-12)
844
+
845
+ # Multiple rotations
846
+ r_expected = Rotation.random(3, rng=rng)
847
+ r = Rotation.from_euler(order, r_expected.as_euler(order).tolist())
848
+ assert np.all(r_expected.approx_equal(r, atol=1e-12))
849
+
850
+
851
+ def test_from_euler_scalar():
852
+ rng = np.random.default_rng(123)
853
+ deg = rng.uniform(low=-180, high=180)
854
+ r_expected = Rotation.from_euler("x", deg, degrees=True)
855
+ r = Rotation.from_euler("x", float(deg), degrees=True)
856
+ assert r_expected.approx_equal(r, atol=1e-12)
857
+
858
+
730
859
  @pytest.mark.parametrize("seq_tuple", permutations("xyz"))
731
860
  @pytest.mark.parametrize("intrinsic", (False, True))
732
- def test_as_euler_asymmetric_axes(seq_tuple, intrinsic):
861
+ def test_as_euler_asymmetric_axes(xp, seq_tuple, intrinsic):
733
862
  # helper function for mean error tests
734
863
  def test_stats(error, mean_max, rms_max):
735
- mean = np.mean(error, axis=0)
736
- std = np.std(error, axis=0)
737
- rms = np.hypot(mean, std)
738
- assert np.all(np.abs(mean) < mean_max)
739
- assert np.all(rms < rms_max)
864
+ mean = xp.mean(error, axis=0)
865
+ std = xp.std(error, axis=0)
866
+ rms = xp.hypot(mean, std)
867
+ assert xp.all(xp.abs(mean) < mean_max)
868
+ assert xp.all(rms < rms_max)
740
869
 
741
870
  rnd = np.random.RandomState(0)
742
871
  n = 1000
@@ -744,6 +873,7 @@ def test_as_euler_asymmetric_axes(seq_tuple, intrinsic):
744
873
  angles[:, 0] = rnd.uniform(low=-np.pi, high=np.pi, size=(n,))
745
874
  angles[:, 1] = rnd.uniform(low=-np.pi / 2, high=np.pi / 2, size=(n,))
746
875
  angles[:, 2] = rnd.uniform(low=-np.pi, high=np.pi, size=(n,))
876
+ angles = xp.asarray(angles)
747
877
 
748
878
  seq = "".join(seq_tuple)
749
879
  if intrinsic:
@@ -752,9 +882,11 @@ def test_as_euler_asymmetric_axes(seq_tuple, intrinsic):
752
882
  seq = seq.upper()
753
883
  rotation = Rotation.from_euler(seq, angles)
754
884
  angles_quat = rotation.as_euler(seq)
885
+ # TODO: Why are we using _as_euler_from_matrix here? As a sanity check? It is not
886
+ # part of the public API and should not be used anywhere else
755
887
  angles_mat = rotation._as_euler_from_matrix(seq)
756
- assert_allclose(angles, angles_quat, atol=0, rtol=1e-12)
757
- assert_allclose(angles, angles_mat, atol=0, rtol=1e-12)
888
+ xp_assert_close(angles, angles_quat, atol=0, rtol=1e-12)
889
+ xp_assert_close(angles, angles_mat, atol=0, rtol=1e-12)
758
890
  test_stats(angles_quat - angles, 1e-15, 1e-14)
759
891
  test_stats(angles_mat - angles, 1e-15, 1e-14)
760
892
 
@@ -762,14 +894,14 @@ def test_as_euler_asymmetric_axes(seq_tuple, intrinsic):
762
894
 
763
895
  @pytest.mark.parametrize("seq_tuple", permutations("xyz"))
764
896
  @pytest.mark.parametrize("intrinsic", (False, True))
765
- def test_as_euler_symmetric_axes(seq_tuple, intrinsic):
897
+ def test_as_euler_symmetric_axes(xp, seq_tuple, intrinsic):
766
898
  # helper function for mean error tests
767
899
  def test_stats(error, mean_max, rms_max):
768
- mean = np.mean(error, axis=0)
769
- std = np.std(error, axis=0)
770
- rms = np.hypot(mean, std)
771
- assert np.all(np.abs(mean) < mean_max)
772
- assert np.all(rms < rms_max)
900
+ mean = xp.mean(error, axis=0)
901
+ std = xp.std(error, axis=0)
902
+ rms = xp.hypot(mean, std)
903
+ assert xp.all(xp.abs(mean) < mean_max)
904
+ assert xp.all(rms < rms_max)
773
905
 
774
906
  rnd = np.random.RandomState(0)
775
907
  n = 1000
@@ -777,6 +909,7 @@ def test_as_euler_symmetric_axes(seq_tuple, intrinsic):
777
909
  angles[:, 0] = rnd.uniform(low=-np.pi, high=np.pi, size=(n,))
778
910
  angles[:, 1] = rnd.uniform(low=0, high=np.pi, size=(n,))
779
911
  angles[:, 2] = rnd.uniform(low=-np.pi, high=np.pi, size=(n,))
912
+ angles = xp.asarray(angles)
780
913
 
781
914
  # Rotation of the form A/B/A are rotation around symmetric axes
782
915
  seq = "".join([seq_tuple[0], seq_tuple[1], seq_tuple[0]])
@@ -784,9 +917,10 @@ def test_as_euler_symmetric_axes(seq_tuple, intrinsic):
784
917
  seq = seq.upper()
785
918
  rotation = Rotation.from_euler(seq, angles)
786
919
  angles_quat = rotation.as_euler(seq)
920
+ # TODO: Same as before: Remove _as_euler_from_matrix?
787
921
  angles_mat = rotation._as_euler_from_matrix(seq)
788
- assert_allclose(angles, angles_quat, atol=0, rtol=1e-13)
789
- assert_allclose(angles, angles_mat, atol=0, rtol=1e-9)
922
+ xp_assert_close(angles, angles_quat, atol=0, rtol=1e-13)
923
+ xp_assert_close(angles, angles_mat, atol=0, rtol=1e-9)
790
924
  test_stats(angles_quat - angles, 1e-16, 1e-14)
791
925
  test_stats(angles_mat - angles, 1e-15, 1e-13)
792
926
 
@@ -794,10 +928,11 @@ def test_as_euler_symmetric_axes(seq_tuple, intrinsic):
794
928
  @pytest.mark.thread_unsafe
795
929
  @pytest.mark.parametrize("seq_tuple", permutations("xyz"))
796
930
  @pytest.mark.parametrize("intrinsic", (False, True))
797
- def test_as_euler_degenerate_asymmetric_axes(seq_tuple, intrinsic):
931
+ def test_as_euler_degenerate_asymmetric_axes(xp, seq_tuple, intrinsic):
932
+ atol = 1e-12
798
933
  # Since we cannot check for angle equality, we check for rotation matrix
799
934
  # equality
800
- angles = np.array([
935
+ angles = xp.asarray([
801
936
  [45, 90, 35],
802
937
  [35, -90, 20],
803
938
  [35, 90, 25],
@@ -811,20 +946,23 @@ def test_as_euler_degenerate_asymmetric_axes(seq_tuple, intrinsic):
811
946
  rotation = Rotation.from_euler(seq, angles, degrees=True)
812
947
  mat_expected = rotation.as_matrix()
813
948
 
814
- with pytest.warns(UserWarning, match="Gimbal lock"):
949
+ # We can only warn on non-lazy backends because we'd need to condition on traced
950
+ # booleans
951
+ with eager_warns(mat_expected, UserWarning, match="Gimbal lock"):
815
952
  angle_estimates = rotation.as_euler(seq, degrees=True)
816
953
  mat_estimated = Rotation.from_euler(seq, angle_estimates, degrees=True).as_matrix()
817
954
 
818
- assert_array_almost_equal(mat_expected, mat_estimated)
955
+ xp_assert_close(mat_expected, mat_estimated, atol=atol)
819
956
 
820
957
 
821
958
  @pytest.mark.thread_unsafe
822
959
  @pytest.mark.parametrize("seq_tuple", permutations("xyz"))
823
960
  @pytest.mark.parametrize("intrinsic", (False, True))
824
- def test_as_euler_degenerate_symmetric_axes(seq_tuple, intrinsic):
961
+ def test_as_euler_degenerate_symmetric_axes(xp, seq_tuple, intrinsic):
962
+ atol = 1e-12
825
963
  # Since we cannot check for angle equality, we check for rotation matrix
826
964
  # equality
827
- angles = np.array([
965
+ angles = xp.asarray([
828
966
  [15, 0, 60],
829
967
  [35, 0, 75],
830
968
  [60, 180, 35],
@@ -839,22 +977,23 @@ def test_as_euler_degenerate_symmetric_axes(seq_tuple, intrinsic):
839
977
  rotation = Rotation.from_euler(seq, angles, degrees=True)
840
978
  mat_expected = rotation.as_matrix()
841
979
 
842
- with pytest.warns(UserWarning, match="Gimbal lock"):
980
+ # We can only warn on non-lazy backends
981
+ with eager_warns(mat_expected, UserWarning, match="Gimbal lock"):
843
982
  angle_estimates = rotation.as_euler(seq, degrees=True)
844
983
  mat_estimated = Rotation.from_euler(seq, angle_estimates, degrees=True).as_matrix()
845
984
 
846
- assert_array_almost_equal(mat_expected, mat_estimated)
985
+ xp_assert_close(mat_expected, mat_estimated, atol=atol)
847
986
 
848
987
 
849
988
  @pytest.mark.thread_unsafe
850
989
  @pytest.mark.parametrize("seq_tuple", permutations("xyz"))
851
990
  @pytest.mark.parametrize("intrinsic", (False, True))
852
- def test_as_euler_degenerate_compare_algorithms(seq_tuple, intrinsic):
991
+ def test_as_euler_degenerate_compare_algorithms(xp, seq_tuple, intrinsic):
853
992
  # this test makes sure that both algorithms are doing the same choices
854
993
  # in degenerate cases
855
994
 
856
995
  # asymmetric axes
857
- angles = np.array([
996
+ angles = xp.asarray([
858
997
  [45, 90, 35],
859
998
  [35, -90, 20],
860
999
  [35, 90, 25],
@@ -867,21 +1006,20 @@ def test_as_euler_degenerate_compare_algorithms(seq_tuple, intrinsic):
867
1006
  seq = seq.upper()
868
1007
 
869
1008
  rot = Rotation.from_euler(seq, angles, degrees=True)
870
- with pytest.warns(UserWarning, match="Gimbal lock"):
1009
+ with eager_warns(rot, UserWarning, match="Gimbal lock"):
871
1010
  estimates_matrix = rot._as_euler_from_matrix(seq, degrees=True)
872
- with pytest.warns(UserWarning, match="Gimbal lock"):
873
1011
  estimates_quat = rot.as_euler(seq, degrees=True)
874
- assert_allclose(
1012
+ xp_assert_close(
875
1013
  estimates_matrix[:, [0, 2]], estimates_quat[:, [0, 2]], atol=0, rtol=1e-12
876
1014
  )
877
- assert_allclose(estimates_matrix[:, 1], estimates_quat[:, 1], atol=0, rtol=1e-7)
1015
+ xp_assert_close(estimates_matrix[:, 1], estimates_quat[:, 1], atol=0, rtol=1e-7)
878
1016
 
879
1017
  # symmetric axes
880
1018
  # Absolute error tolerance must be looser to directly compare the results
881
1019
  # from both algorithms, because of numerical loss of precision for the
882
1020
  # method _as_euler_from_matrix near a zero angle value
883
1021
 
884
- angles = np.array([
1022
+ angles = xp.asarray([
885
1023
  [15, 0, 60],
886
1024
  [35, 0, 75],
887
1025
  [60, 180, 35],
@@ -897,45 +1035,49 @@ def test_as_euler_degenerate_compare_algorithms(seq_tuple, intrinsic):
897
1035
  seq = seq.upper()
898
1036
 
899
1037
  rot = Rotation.from_euler(seq, angles, degrees=True)
900
- with pytest.warns(UserWarning, match="Gimbal lock"):
1038
+ with eager_warns(rot, UserWarning, match="Gimbal lock"):
901
1039
  estimates_matrix = rot._as_euler_from_matrix(seq, degrees=True)
902
- with pytest.warns(UserWarning, match="Gimbal lock"):
1040
+ with eager_warns(rot, UserWarning, match="Gimbal lock"):
903
1041
  estimates_quat = rot.as_euler(seq, degrees=True)
904
- assert_allclose(
1042
+ xp_assert_close(
905
1043
  estimates_matrix[:, [0, 2]], estimates_quat[:, [0, 2]], atol=0, rtol=1e-12
906
1044
  )
907
1045
 
908
- assert_allclose(
1046
+ xp_assert_close(
909
1047
  estimates_matrix[~idx, 1], estimates_quat[~idx, 1], atol=0, rtol=1e-7
910
1048
  )
911
1049
 
912
- assert_allclose(
1050
+ xp_assert_close(
913
1051
  estimates_matrix[idx, 1], estimates_quat[idx, 1], atol=1e-6
914
1052
  ) # problematic, angles[1] = 0
915
1053
 
916
1054
 
917
- def test_inv():
1055
+ def test_inv(xp):
1056
+ atol = 1e-12
918
1057
  rnd = np.random.RandomState(0)
919
1058
  n = 10
920
1059
  # preserve use of old random_state during SPEC 7 transition
921
1060
  p = Rotation.random(num=n, random_state=rnd)
1061
+ p = Rotation.from_quat(xp.asarray(p.as_quat()))
922
1062
  q = p.inv()
923
1063
 
924
1064
  p_mat = p.as_matrix()
925
1065
  q_mat = q.as_matrix()
926
- result1 = np.einsum('...ij,...jk->...ik', p_mat, q_mat)
927
- result2 = np.einsum('...ij,...jk->...ik', q_mat, p_mat)
1066
+ result1 = xp.asarray(np.einsum("...ij,...jk->...ik", p_mat, q_mat))
1067
+ result2 = xp.asarray(np.einsum("...ij,...jk->...ik", q_mat, p_mat))
928
1068
 
929
- eye3d = np.empty((n, 3, 3))
930
- eye3d[:] = np.eye(3)
1069
+ eye3d = xp.empty((n, 3, 3))
1070
+ eye3d = xpx.at(eye3d)[..., :3, :3].set(xp.eye(3))
931
1071
 
932
- assert_array_almost_equal(result1, eye3d)
933
- assert_array_almost_equal(result2, eye3d)
1072
+ xp_assert_close(result1, eye3d, atol=atol)
1073
+ xp_assert_close(result2, eye3d, atol=atol)
934
1074
 
935
1075
 
936
- def test_inv_single_rotation():
1076
+ def test_inv_single_rotation(xp):
1077
+ atol = 1e-12
937
1078
  rng = np.random.default_rng(146972845698875399755764481408308808739)
938
1079
  p = Rotation.random(rng=rng)
1080
+ p = Rotation.from_quat(xp.asarray(p.as_quat()))
939
1081
  q = p.inv()
940
1082
 
941
1083
  p_mat = p.as_matrix()
@@ -943,93 +1085,105 @@ def test_inv_single_rotation():
943
1085
  res1 = np.dot(p_mat, q_mat)
944
1086
  res2 = np.dot(q_mat, p_mat)
945
1087
 
946
- eye = np.eye(3)
1088
+ eye = xp.eye(3)
947
1089
 
948
- assert_array_almost_equal(res1, eye)
949
- assert_array_almost_equal(res2, eye)
1090
+ xp_assert_close(res1, eye, atol=atol)
1091
+ xp_assert_close(res2, eye, atol=atol)
950
1092
 
951
1093
  x = Rotation.random(num=1, rng=rng)
1094
+ x = Rotation.from_quat(xp.asarray(x.as_quat()))
952
1095
  y = x.inv()
953
1096
 
954
1097
  x_matrix = x.as_matrix()
955
1098
  y_matrix = y.as_matrix()
956
- result1 = np.einsum('...ij,...jk->...ik', x_matrix, y_matrix)
957
- result2 = np.einsum('...ij,...jk->...ik', y_matrix, x_matrix)
1099
+ result1 = xp.linalg.matmul(x_matrix, y_matrix)
1100
+ result2 = xp.linalg.matmul(y_matrix, x_matrix)
958
1101
 
959
- eye3d = np.empty((1, 3, 3))
960
- eye3d[:] = np.eye(3)
1102
+ eye3d = xp.empty((1, 3, 3))
1103
+ eye3d = xpx.at(eye3d)[..., :3, :3].set(xp.eye(3))
961
1104
 
962
- assert_array_almost_equal(result1, eye3d)
963
- assert_array_almost_equal(result2, eye3d)
1105
+ xp_assert_close(result1, eye3d, atol=atol)
1106
+ xp_assert_close(result2, eye3d, atol=atol)
964
1107
 
965
1108
 
966
- def test_identity_magnitude():
1109
+ def test_identity_magnitude(xp):
967
1110
  n = 10
968
- assert_allclose(Rotation.identity(n).magnitude(), 0)
969
- assert_allclose(Rotation.identity(n).inv().magnitude(), 0)
1111
+ r = Rotation.identity(n)
1112
+ r = Rotation.from_quat(xp.asarray(r.as_quat()))
1113
+ expected = xp.zeros(n)
1114
+ xp_assert_close(r.magnitude(), expected)
1115
+ xp_assert_close(r.inv().magnitude(), expected)
970
1116
 
971
1117
 
972
- def test_single_identity_magnitude():
973
- assert Rotation.identity().magnitude() == 0
974
- assert Rotation.identity().inv().magnitude() == 0
1118
+ def test_single_identity_magnitude(xp):
1119
+ r = Rotation.from_quat(xp.asarray(Rotation.identity().as_quat()))
1120
+ assert r.magnitude() == 0
1121
+ assert r.inv().magnitude() == 0
975
1122
 
976
1123
 
977
- def test_identity_invariance():
1124
+ def test_identity_invariance(xp):
1125
+ atol = 1e-12
978
1126
  n = 10
979
1127
  p = Rotation.random(n, rng=0)
980
-
981
- result = p * Rotation.identity(n)
982
- assert_array_almost_equal(p.as_quat(), result.as_quat())
1128
+ p = Rotation.from_quat(xp.asarray(p.as_quat()))
1129
+ q = Rotation.from_quat(xp.asarray(Rotation.identity(n).as_quat()))
1130
+ result = p * q
1131
+ xp_assert_close(p.as_quat(), result.as_quat())
983
1132
 
984
1133
  result = result * p.inv()
985
- assert_array_almost_equal(result.magnitude(), np.zeros(n))
1134
+ xp_assert_close(result.magnitude(), xp.zeros(n), atol=atol)
986
1135
 
987
1136
 
988
- def test_single_identity_invariance():
1137
+ def test_single_identity_invariance(xp):
1138
+ atol = 1e-12
989
1139
  n = 10
990
1140
  p = Rotation.random(n, rng=0)
1141
+ p = Rotation.from_quat(xp.asarray(p.as_quat()))
991
1142
 
992
- result = p * Rotation.identity()
993
- assert_array_almost_equal(p.as_quat(), result.as_quat())
1143
+ q = Rotation.from_quat(xp.asarray(Rotation.identity().as_quat()))
1144
+ result = p * q
1145
+ xp_assert_close(p.as_quat(), result.as_quat())
994
1146
 
995
1147
  result = result * p.inv()
996
- assert_array_almost_equal(result.magnitude(), np.zeros(n))
1148
+ xp_assert_close(result.magnitude(), xp.zeros(n), atol=atol)
997
1149
 
998
1150
 
999
- def test_magnitude():
1000
- r = Rotation.from_quat(np.eye(4))
1151
+ def test_magnitude(xp):
1152
+ r = Rotation.from_quat(xp.eye(4))
1001
1153
  result = r.magnitude()
1002
- assert_array_almost_equal(result, [np.pi, np.pi, np.pi, 0])
1154
+ xp_assert_close(result, xp.asarray([xp.pi, xp.pi, xp.pi, 0]))
1003
1155
 
1004
- r = Rotation.from_quat(-np.eye(4))
1156
+ r = Rotation.from_quat(-xp.eye(4))
1005
1157
  result = r.magnitude()
1006
- assert_array_almost_equal(result, [np.pi, np.pi, np.pi, 0])
1158
+ xp_assert_close(result, xp.asarray([xp.pi, xp.pi, xp.pi, 0]))
1007
1159
 
1008
1160
 
1009
- def test_magnitude_single_rotation():
1010
- r = Rotation.from_quat(np.eye(4))
1161
+ def test_magnitude_single_rotation(xp):
1162
+ r = Rotation.from_quat(xp.eye(4))
1011
1163
  result1 = r[0].magnitude()
1012
- assert_allclose(result1, np.pi)
1164
+ xp_assert_close(result1, xp.pi)
1013
1165
 
1014
1166
  result2 = r[3].magnitude()
1015
- assert_allclose(result2, 0)
1167
+ xp_assert_close(result2, 0.0)
1016
1168
 
1017
1169
 
1018
- def test_approx_equal():
1170
+ def test_approx_equal(xp):
1019
1171
  rng = np.random.default_rng(146972845698875399755764481408308808739)
1020
1172
  p = Rotation.random(10, rng=rng)
1021
1173
  q = Rotation.random(10, rng=rng)
1174
+ p = Rotation.from_quat(xp.asarray(p.as_quat()))
1175
+ q = Rotation.from_quat(xp.asarray(q.as_quat()))
1022
1176
  r = p * q.inv()
1023
1177
  r_mag = r.magnitude()
1024
- atol = np.median(r_mag) # ensure we get mix of Trues and Falses
1025
- assert_equal(p.approx_equal(q, atol), (r_mag < atol))
1178
+ atol = xp.asarray(np.median(r_mag)) # ensure we get mix of Trues and Falses
1179
+ xp_assert_equal(p.approx_equal(q, atol), (r_mag < atol))
1026
1180
 
1027
1181
 
1028
1182
  @pytest.mark.thread_unsafe
1029
- def test_approx_equal_single_rotation():
1183
+ def test_approx_equal_single_rotation(xp):
1030
1184
  # also tests passing single argument to approx_equal
1031
- p = Rotation.from_rotvec([0, 0, 1e-9]) # less than default atol of 1e-8
1032
- q = Rotation.from_quat(np.eye(4))
1185
+ p = Rotation.from_rotvec(xp.asarray([0, 0, 1e-9])) # less than default atol of 1e-8
1186
+ q = Rotation.from_quat(xp.eye(4))
1033
1187
  assert p.approx_equal(q[3])
1034
1188
  assert not p.approx_equal(q[0])
1035
1189
 
@@ -1040,40 +1194,47 @@ def test_approx_equal_single_rotation():
1040
1194
  assert p.approx_equal(q[3], degrees=True)
1041
1195
 
1042
1196
 
1043
- def test_mean():
1197
+ def test_mean(xp):
1198
+ axes = xp.concat((-xp.eye(3), xp.eye(3)))
1044
1199
  axes = np.concatenate((-np.eye(3), np.eye(3)))
1045
- thetas = np.linspace(0, np.pi / 2, 100)
1200
+ thetas = xp.linspace(0, xp.pi / 2, 100)
1046
1201
  for t in thetas:
1047
1202
  r = Rotation.from_rotvec(t * axes)
1048
- assert_allclose(r.mean().magnitude(), 0, atol=1E-10)
1203
+ xp_assert_close(r.mean().magnitude(), 0.0, atol=1e-10)
1049
1204
 
1050
1205
 
1051
- def test_weighted_mean():
1206
+ def test_weighted_mean(xp):
1052
1207
  # test that doubling a weight is equivalent to including a rotation twice.
1053
- axes = np.array([[0, 0, 0], [1, 0, 0], [1, 0, 0]])
1054
- thetas = np.linspace(0, np.pi / 2, 100)
1208
+ axes = xp.asarray([[0.0, 0, 0], [1, 0, 0], [1, 0, 0]])
1209
+ thetas = xp.linspace(0, xp.pi / 2, 100)
1055
1210
  for t in thetas:
1056
- rw = Rotation.from_rotvec(t * axes[:2])
1211
+ rw = Rotation.from_rotvec(t * axes[:2, ...])
1057
1212
  mw = rw.mean(weights=[1, 2])
1058
1213
 
1059
1214
  r = Rotation.from_rotvec(t * axes)
1060
1215
  m = r.mean()
1061
- assert_allclose((m * mw.inv()).magnitude(), 0, atol=1E-10)
1216
+ xp_assert_close((m * mw.inv()).magnitude(), 0.0, atol=1e-10)
1062
1217
 
1063
1218
 
1064
- def test_mean_invalid_weights():
1065
- with pytest.raises(ValueError, match="non-negative"):
1066
- r = Rotation.from_quat(np.eye(4))
1067
- r.mean(weights=-np.ones(4))
1219
+ def test_mean_invalid_weights(xp):
1220
+ r = Rotation.from_quat(xp.eye(4))
1221
+ if is_lazy_array(r.as_quat()):
1222
+ m = r.mean(weights=-xp.ones(4))
1223
+ assert all(xp.isnan(m._quat))
1224
+ else:
1225
+ with pytest.raises(ValueError, match="non-negative"):
1226
+ r.mean(weights=-xp.ones(4))
1068
1227
 
1069
1228
 
1070
- def test_reduction_no_indices():
1071
- result = Rotation.identity().reduce(return_indices=False)
1229
+ def test_reduction_no_indices(xp):
1230
+ r = Rotation.from_quat(xp.asarray([0.0, 0.0, 0.0, 1.0]))
1231
+ result = r.reduce(return_indices=False)
1072
1232
  assert isinstance(result, Rotation)
1073
1233
 
1074
1234
 
1075
- def test_reduction_none_indices():
1076
- result = Rotation.identity().reduce(return_indices=True)
1235
+ def test_reduction_none_indices(xp):
1236
+ r = Rotation.from_quat(xp.asarray([0.0, 0.0, 0.0, 1.0]))
1237
+ result = r.reduce(return_indices=True)
1077
1238
  assert type(result) is tuple
1078
1239
  assert len(result) == 3
1079
1240
 
@@ -1082,11 +1243,12 @@ def test_reduction_none_indices():
1082
1243
  assert right_best is None
1083
1244
 
1084
1245
 
1085
- def test_reduction_scalar_calculation():
1246
+ def test_reduction_scalar_calculation(xp):
1247
+ atol = 1e-12
1086
1248
  rng = np.random.default_rng(146972845698875399755764481408308808739)
1087
- l = Rotation.random(5, rng=rng)
1088
- r = Rotation.random(10, rng=rng)
1089
- p = Rotation.random(7, rng=rng)
1249
+ l = Rotation.from_quat(xp.asarray(Rotation.random(5, rng=rng).as_quat()))
1250
+ r = Rotation.from_quat(xp.asarray(Rotation.random(10, rng=rng).as_quat()))
1251
+ p = Rotation.from_quat(xp.asarray(Rotation.random(7, rng=rng).as_quat()))
1090
1252
  reduced, left_best, right_best = p.reduce(l, r, return_indices=True)
1091
1253
 
1092
1254
  # Loop implementation of the vectorized calculation in Rotation.reduce
@@ -1098,66 +1260,66 @@ def test_reduction_scalar_calculation():
1098
1260
  scalars = np.reshape(np.moveaxis(scalars, 1, 0), (scalars.shape[1], -1))
1099
1261
 
1100
1262
  max_ind = np.argmax(np.reshape(scalars, (len(p), -1)), axis=1)
1101
- left_best_check = max_ind // len(r)
1102
- right_best_check = max_ind % len(r)
1103
- assert (left_best == left_best_check).all()
1104
- assert (right_best == right_best_check).all()
1263
+ left_best_check = xp.asarray(max_ind // len(r))
1264
+ right_best_check = xp.asarray(max_ind % len(r))
1265
+ assert xp.all(left_best == left_best_check)
1266
+ assert xp.all(right_best == right_best_check)
1105
1267
 
1106
1268
  reduced_check = l[left_best_check] * p * r[right_best_check]
1107
1269
  mag = (reduced.inv() * reduced_check).magnitude()
1108
- assert_array_almost_equal(mag, np.zeros(len(p)))
1270
+ xp_assert_close(mag, xp.zeros(len(p)), atol=atol)
1109
1271
 
1110
1272
 
1111
- def test_apply_single_rotation_single_point():
1112
- mat = np.array([
1273
+ def test_apply_single_rotation_single_point(xp):
1274
+ mat = xp.asarray([
1113
1275
  [0, -1, 0],
1114
1276
  [1, 0, 0],
1115
1277
  [0, 0, 1]
1116
1278
  ])
1117
1279
  r_1d = Rotation.from_matrix(mat)
1118
- r_2d = Rotation.from_matrix(np.expand_dims(mat, axis=0))
1280
+ r_2d = Rotation.from_matrix(xp.expand_dims(mat, axis=0))
1119
1281
 
1120
- v_1d = np.array([1, 2, 3])
1121
- v_2d = np.expand_dims(v_1d, axis=0)
1122
- v1d_rotated = np.array([-2, 1, 3])
1123
- v2d_rotated = np.expand_dims(v1d_rotated, axis=0)
1282
+ v_1d = xp.asarray([1.0, 2, 3])
1283
+ v_2d = xp.expand_dims(v_1d, axis=0)
1284
+ v1d_rotated = xp.asarray([-2.0, 1, 3])
1285
+ v2d_rotated = xp.expand_dims(v1d_rotated, axis=0)
1124
1286
 
1125
- assert_allclose(r_1d.apply(v_1d), v1d_rotated)
1126
- assert_allclose(r_1d.apply(v_2d), v2d_rotated)
1127
- assert_allclose(r_2d.apply(v_1d), v2d_rotated)
1128
- assert_allclose(r_2d.apply(v_2d), v2d_rotated)
1287
+ xp_assert_close(r_1d.apply(v_1d), v1d_rotated)
1288
+ xp_assert_close(r_1d.apply(v_2d), v2d_rotated)
1289
+ xp_assert_close(r_2d.apply(v_1d), v2d_rotated)
1290
+ xp_assert_close(r_2d.apply(v_2d), v2d_rotated)
1129
1291
 
1130
- v1d_inverse = np.array([2, -1, 3])
1131
- v2d_inverse = np.expand_dims(v1d_inverse, axis=0)
1292
+ v1d_inverse = xp.asarray([2.0, -1, 3])
1293
+ v2d_inverse = xp.expand_dims(v1d_inverse, axis=0)
1132
1294
 
1133
- assert_allclose(r_1d.apply(v_1d, inverse=True), v1d_inverse)
1134
- assert_allclose(r_1d.apply(v_2d, inverse=True), v2d_inverse)
1135
- assert_allclose(r_2d.apply(v_1d, inverse=True), v2d_inverse)
1136
- assert_allclose(r_2d.apply(v_2d, inverse=True), v2d_inverse)
1295
+ xp_assert_close(r_1d.apply(v_1d, inverse=True), v1d_inverse)
1296
+ xp_assert_close(r_1d.apply(v_2d, inverse=True), v2d_inverse)
1297
+ xp_assert_close(r_2d.apply(v_1d, inverse=True), v2d_inverse)
1298
+ xp_assert_close(r_2d.apply(v_2d, inverse=True), v2d_inverse)
1137
1299
 
1138
1300
 
1139
- def test_apply_single_rotation_multiple_points():
1140
- mat = np.array([
1301
+ def test_apply_single_rotation_multiple_points(xp):
1302
+ mat = xp.asarray([
1141
1303
  [0, -1, 0],
1142
1304
  [1, 0, 0],
1143
1305
  [0, 0, 1]
1144
1306
  ])
1145
1307
  r1 = Rotation.from_matrix(mat)
1146
- r2 = Rotation.from_matrix(np.expand_dims(mat, axis=0))
1308
+ r2 = Rotation.from_matrix(xp.expand_dims(mat, axis=0))
1147
1309
 
1148
- v = np.array([[1, 2, 3], [4, 5, 6]])
1149
- v_rotated = np.array([[-2, 1, 3], [-5, 4, 6]])
1310
+ v = xp.asarray([[1, 2, 3], [4, 5, 6]])
1311
+ v_rotated = xp.asarray([[-2.0, 1, 3], [-5, 4, 6]])
1150
1312
 
1151
- assert_allclose(r1.apply(v), v_rotated)
1152
- assert_allclose(r2.apply(v), v_rotated)
1313
+ xp_assert_close(r1.apply(v), v_rotated)
1314
+ xp_assert_close(r2.apply(v), v_rotated)
1153
1315
 
1154
- v_inverse = np.array([[2, -1, 3], [5, -4, 6]])
1316
+ v_inverse = xp.asarray([[2.0, -1, 3], [5, -4, 6]])
1155
1317
 
1156
- assert_allclose(r1.apply(v, inverse=True), v_inverse)
1157
- assert_allclose(r2.apply(v, inverse=True), v_inverse)
1318
+ xp_assert_close(r1.apply(v, inverse=True), v_inverse)
1319
+ xp_assert_close(r2.apply(v, inverse=True), v_inverse)
1158
1320
 
1159
1321
 
1160
- def test_apply_multiple_rotations_single_point():
1322
+ def test_apply_multiple_rotations_single_point(xp):
1161
1323
  mat = np.empty((2, 3, 3))
1162
1324
  mat[0] = np.array([
1163
1325
  [0, -1, 0],
@@ -1169,23 +1331,24 @@ def test_apply_multiple_rotations_single_point():
1169
1331
  [0, 0, -1],
1170
1332
  [0, 1, 0]
1171
1333
  ])
1334
+ mat = xp.asarray(mat)
1172
1335
  r = Rotation.from_matrix(mat)
1173
1336
 
1174
- v1 = np.array([1, 2, 3])
1175
- v2 = np.expand_dims(v1, axis=0)
1337
+ v1 = xp.asarray([1, 2, 3])
1338
+ v2 = xp.expand_dims(v1, axis=0)
1176
1339
 
1177
- v_rotated = np.array([[-2, 1, 3], [1, -3, 2]])
1340
+ v_rotated = xp.asarray([[-2.0, 1, 3], [1, -3, 2]])
1178
1341
 
1179
- assert_allclose(r.apply(v1), v_rotated)
1180
- assert_allclose(r.apply(v2), v_rotated)
1342
+ xp_assert_close(r.apply(v1), v_rotated)
1343
+ xp_assert_close(r.apply(v2), v_rotated)
1181
1344
 
1182
- v_inverse = np.array([[2, -1, 3], [1, 3, -2]])
1345
+ v_inverse = xp.asarray([[2.0, -1, 3], [1, 3, -2]])
1183
1346
 
1184
- assert_allclose(r.apply(v1, inverse=True), v_inverse)
1185
- assert_allclose(r.apply(v2, inverse=True), v_inverse)
1347
+ xp_assert_close(r.apply(v1, inverse=True), v_inverse)
1348
+ xp_assert_close(r.apply(v2, inverse=True), v_inverse)
1186
1349
 
1187
1350
 
1188
- def test_apply_multiple_rotations_multiple_points():
1351
+ def test_apply_multiple_rotations_multiple_points(xp):
1189
1352
  mat = np.empty((2, 3, 3))
1190
1353
  mat[0] = np.array([
1191
1354
  [0, -1, 0],
@@ -1197,17 +1360,52 @@ def test_apply_multiple_rotations_multiple_points():
1197
1360
  [0, 0, -1],
1198
1361
  [0, 1, 0]
1199
1362
  ])
1363
+ mat = xp.asarray(mat)
1200
1364
  r = Rotation.from_matrix(mat)
1201
1365
 
1202
- v = np.array([[1, 2, 3], [4, 5, 6]])
1203
- v_rotated = np.array([[-2, 1, 3], [4, -6, 5]])
1204
- assert_allclose(r.apply(v), v_rotated)
1205
-
1206
- v_inverse = np.array([[2, -1, 3], [4, 6, -5]])
1207
- assert_allclose(r.apply(v, inverse=True), v_inverse)
1208
-
1209
-
1210
- def test_getitem():
1366
+ v = xp.asarray([[1, 2, 3], [4, 5, 6]])
1367
+ v_rotated = xp.asarray([[-2.0, 1, 3], [4, -6, 5]])
1368
+ xp_assert_close(r.apply(v), v_rotated)
1369
+
1370
+ v_inverse = xp.asarray([[2.0, -1, 3], [4, 6, -5]])
1371
+ xp_assert_close(r.apply(v, inverse=True), v_inverse)
1372
+
1373
+
1374
+ def test_apply_shapes(xp):
1375
+ vector0 = xp.asarray([1.0, 2.0, 3.0])
1376
+ vector1 = xp.asarray([vector0])
1377
+ vector2 = xp.asarray([vector0, vector0])
1378
+ matrix0 = xp.eye(3)
1379
+ matrix1 = xp.asarray([matrix0])
1380
+ matrix2 = xp.asarray([matrix0, matrix0])
1381
+
1382
+ for m, v in product([matrix0, matrix1, matrix2], [vector0, vector1, vector2]):
1383
+ r = Rotation.from_matrix(m)
1384
+ shape = v.shape
1385
+ if not r.single and (v.shape == (3,) or v.shape == (1, 3)):
1386
+ shape = (len(r), 3)
1387
+ x = r.apply(v)
1388
+ assert x.shape == shape
1389
+ x = r.apply(v, inverse=True)
1390
+ assert x.shape == shape
1391
+
1392
+
1393
+ def test_apply_array_like():
1394
+ rng = np.random.default_rng(123)
1395
+ # Single vector
1396
+ r = Rotation.random(rng=rng)
1397
+ t = rng.uniform(-100, 100, size=(3,))
1398
+ v = r.apply(t.tolist())
1399
+ v_expected = r.apply(t)
1400
+ xp_assert_close(v, v_expected, atol=1e-12)
1401
+ # Multiple vectors
1402
+ t = rng.uniform(-100, 100, size=(2, 3))
1403
+ v = r.apply(t.tolist())
1404
+ v_expected = r.apply(t)
1405
+ xp_assert_close(v, v_expected, atol=1e-12)
1406
+
1407
+
1408
+ def test_getitem(xp):
1211
1409
  mat = np.empty((2, 3, 3))
1212
1410
  mat[0] = np.array([
1213
1411
  [0, -1, 0],
@@ -1219,47 +1417,60 @@ def test_getitem():
1219
1417
  [0, 0, -1],
1220
1418
  [0, 1, 0]
1221
1419
  ])
1420
+ mat = xp.asarray(mat)
1222
1421
  r = Rotation.from_matrix(mat)
1223
1422
 
1224
- assert_allclose(r[0].as_matrix(), mat[0], atol=1e-15)
1225
- assert_allclose(r[1].as_matrix(), mat[1], atol=1e-15)
1226
- assert_allclose(r[:-1].as_matrix(), np.expand_dims(mat[0], axis=0), atol=1e-15)
1423
+ xp_assert_close(r[0].as_matrix(), mat[0], atol=1e-15)
1424
+ xp_assert_close(r[1].as_matrix(), mat[1, ...], atol=1e-15)
1425
+ xp_assert_close(r[:-1].as_matrix(), xp.expand_dims(mat[0, ...], axis=0), atol=1e-15)
1227
1426
 
1228
1427
 
1229
- def test_getitem_single():
1428
+ def test_getitem_single(xp):
1230
1429
  with pytest.raises(TypeError, match='not subscriptable'):
1231
- Rotation.identity()[0]
1430
+ Rotation.from_quat(xp.asarray([0, 0, 0, 1]))[0]
1431
+
1432
+
1433
+ def test_getitem_array_like():
1434
+ mat = np.array([[[0.0, -1, 0],
1435
+ [1, 0, 0],
1436
+ [0, 0, 1]],
1437
+ [[1, 0, 0],
1438
+ [0, 0, -1],
1439
+ [0, 1, 0]]])
1440
+ r = Rotation.from_matrix(mat)
1441
+ xp_assert_close(r[[0]].as_matrix(), mat[[0]], atol=1e-15)
1442
+ xp_assert_close(r[[0, 1]].as_matrix(), mat[[0, 1]], atol=1e-15)
1232
1443
 
1233
1444
 
1234
- def test_setitem_single():
1235
- r = Rotation.identity()
1445
+ def test_setitem_single(xp):
1446
+ r = Rotation.from_quat(xp.asarray([0, 0, 0, 1]))
1236
1447
  with pytest.raises(TypeError, match='not subscriptable'):
1237
- r[0] = Rotation.identity()
1448
+ r[0] = Rotation.from_quat(xp.asarray([0, 0, 0, 1]))
1238
1449
 
1239
1450
 
1240
- def test_setitem_slice():
1451
+ def test_setitem_slice(xp):
1241
1452
  rng = np.random.default_rng(146972845698875399755764481408308808739)
1242
- r1 = Rotation.random(10, rng=rng)
1243
- r2 = Rotation.random(5, rng=rng)
1453
+ r1 = Rotation.from_quat(xp.asarray(Rotation.random(10, rng=rng).as_quat()))
1454
+ r2 = Rotation.from_quat(xp.asarray(Rotation.random(5, rng=rng).as_quat()))
1244
1455
  r1[1:6] = r2
1245
- assert_equal(r1[1:6].as_quat(), r2.as_quat())
1456
+ xp_assert_equal(r1[1:6].as_quat(), r2.as_quat())
1246
1457
 
1247
1458
 
1248
- def test_setitem_integer():
1459
+ def test_setitem_integer(xp):
1249
1460
  rng = np.random.default_rng(146972845698875399755764481408308808739)
1250
- r1 = Rotation.random(10, rng=rng)
1251
- r2 = Rotation.random(rng=rng)
1461
+ r1 = Rotation.from_quat(xp.asarray(Rotation.random(10, rng=rng).as_quat()))
1462
+ r2 = Rotation.from_quat(xp.asarray(Rotation.random(rng=rng).as_quat()))
1252
1463
  r1[1] = r2
1253
- assert_equal(r1[1].as_quat(), r2.as_quat())
1464
+ xp_assert_equal(r1[1].as_quat(), r2.as_quat())
1254
1465
 
1255
1466
 
1256
- def test_setitem_wrong_type():
1257
- r = Rotation.random(10, rng=0)
1467
+ def test_setitem_wrong_type(xp):
1468
+ r = Rotation.from_quat(xp.asarray(Rotation.random(10, rng=0).as_quat()))
1258
1469
  with pytest.raises(TypeError, match='Rotation object'):
1259
1470
  r[0] = 1
1260
1471
 
1261
1472
 
1262
- def test_n_rotations():
1473
+ def test_n_rotations(xp):
1263
1474
  mat = np.empty((2, 3, 3))
1264
1475
  mat[0] = np.array([
1265
1476
  [0, -1, 0],
@@ -1271,6 +1482,7 @@ def test_n_rotations():
1271
1482
  [0, 0, -1],
1272
1483
  [0, 1, 0]
1273
1484
  ])
1485
+ mat = xp.asarray(mat)
1274
1486
  r = Rotation.from_matrix(mat)
1275
1487
 
1276
1488
  assert_equal(len(r), 2)
@@ -1278,6 +1490,7 @@ def test_n_rotations():
1278
1490
 
1279
1491
 
1280
1492
  def test_random_rotation_shape():
1493
+ # No xp testing since random rotations are always using NumPy
1281
1494
  rng = np.random.default_rng(146972845698875399755764481408308808739)
1282
1495
  assert_equal(Rotation.random(rng=rng).as_quat().shape, (4,))
1283
1496
  assert_equal(Rotation.random(None, rng=rng).as_quat().shape, (4,))
@@ -1286,80 +1499,80 @@ def test_random_rotation_shape():
1286
1499
  assert_equal(Rotation.random(5, rng=rng).as_quat().shape, (5, 4))
1287
1500
 
1288
1501
 
1289
- def test_align_vectors_no_rotation():
1290
- x = np.array([[1, 2, 3], [4, 5, 6]])
1291
- y = x.copy()
1502
+ def test_align_vectors_no_rotation(xp):
1503
+ x = xp.asarray([[1, 2, 3], [4, 5, 6]])
1504
+ y = xp.asarray(x, copy=True)
1292
1505
 
1293
1506
  r, rssd = Rotation.align_vectors(x, y)
1294
- assert_array_almost_equal(r.as_matrix(), np.eye(3))
1295
- assert_allclose(rssd, 0, atol=1e-6)
1507
+ xp_assert_close(r.as_matrix(), xp.eye(3), atol=1e-12)
1508
+ xp_assert_close(rssd, xp.asarray(0.0)[()], check_shape=False, atol=1e-6)
1296
1509
 
1297
1510
 
1298
- def test_align_vectors_no_noise():
1511
+ def test_align_vectors_no_noise(xp):
1299
1512
  rng = np.random.default_rng(14697284569885399755764481408308808739)
1300
- c = Rotation.random(rng=rng)
1301
- b = rng.normal(size=(5, 3))
1513
+ c = Rotation.from_quat(xp.asarray(Rotation.random(rng=rng).as_quat()))
1514
+ b = xp.asarray(rng.normal(size=(5, 3)))
1302
1515
  a = c.apply(b)
1303
1516
 
1304
1517
  est, rssd = Rotation.align_vectors(a, b)
1305
- assert_allclose(c.as_quat(), est.as_quat())
1306
- assert_allclose(rssd, 0, atol=1e-7)
1518
+ xp_assert_close(c.as_quat(), est.as_quat())
1519
+ xp_assert_close(rssd, xp.asarray(0.0)[()], check_shape=False, atol=1e-7)
1307
1520
 
1308
1521
 
1309
- def test_align_vectors_improper_rotation():
1522
+ def test_align_vectors_improper_rotation(xp):
1310
1523
  # Tests correct logic for issue #10444
1311
- x = np.array([[0.89299824, -0.44372674, 0.0752378],
1312
- [0.60221789, -0.47564102, -0.6411702]])
1313
- y = np.array([[0.02386536, -0.82176463, 0.5693271],
1314
- [-0.27654929, -0.95191427, -0.1318321]])
1524
+ x = xp.asarray([[0.89299824, -0.44372674, 0.0752378],
1525
+ [0.60221789, -0.47564102, -0.6411702]])
1526
+ y = xp.asarray([[0.02386536, -0.82176463, 0.5693271],
1527
+ [-0.27654929, -0.95191427, -0.1318321]])
1315
1528
 
1316
1529
  est, rssd = Rotation.align_vectors(x, y)
1317
- assert_allclose(x, est.apply(y), atol=1e-6)
1318
- assert_allclose(rssd, 0, atol=1e-7)
1530
+ xp_assert_close(x, est.apply(y), atol=1e-6)
1531
+ xp_assert_close(rssd, xp.asarray(0.0)[()], check_shape=False, atol=1e-7)
1319
1532
 
1320
1533
 
1321
- def test_align_vectors_rssd_sensitivity():
1322
- rssd_expected = 0.141421356237308
1323
- sens_expected = np.array([[0.2, 0. , 0.],
1324
- [0. , 1.5, 1.],
1325
- [0. , 1. , 1.]])
1534
+ def test_align_vectors_rssd_sensitivity(xp):
1535
+ rssd_expected = xp.asarray(0.141421356237308)[()]
1536
+ sens_expected = xp.asarray([[0.2, 0. , 0.],
1537
+ [0. , 1.5, 1.],
1538
+ [0. , 1. , 1.]])
1326
1539
  atol = 1e-6
1327
- a = [[0, 1, 0], [0, 1, 1], [0, 1, 1]]
1328
- b = [[1, 0, 0], [1, 1.1, 0], [1, 0.9, 0]]
1540
+ a = xp.asarray([[0, 1, 0], [0, 1, 1], [0, 1, 1]])
1541
+ b = xp.asarray([[1, 0, 0], [1, 1.1, 0], [1, 0.9, 0]])
1329
1542
  rot, rssd, sens = Rotation.align_vectors(a, b, return_sensitivity=True)
1330
- assert np.isclose(rssd, rssd_expected, atol=atol)
1331
- assert np.allclose(sens, sens_expected, atol=atol)
1543
+ xp_assert_close(rssd, rssd_expected, atol=atol)
1544
+ xp_assert_close(sens, sens_expected, atol=atol)
1332
1545
 
1333
1546
 
1334
- def test_align_vectors_scaled_weights():
1547
+ def test_align_vectors_scaled_weights(xp):
1335
1548
  n = 10
1336
- a = Rotation.random(n, rng=0).apply([1, 0, 0])
1337
- b = Rotation.random(n, rng=1).apply([1, 0, 0])
1549
+ a = xp.asarray(Rotation.random(n, rng=0).apply([1, 0, 0]))
1550
+ b = xp.asarray(Rotation.random(n, rng=1).apply([1, 0, 0]))
1338
1551
  scale = 2
1339
1552
 
1340
- est1, rssd1, cov1 = Rotation.align_vectors(a, b, np.ones(n), True)
1341
- est2, rssd2, cov2 = Rotation.align_vectors(a, b, scale * np.ones(n), True)
1553
+ est1, rssd1, cov1 = Rotation.align_vectors(a, b, xp.ones(n), True)
1554
+ est2, rssd2, cov2 = Rotation.align_vectors(a, b, scale * xp.ones(n), True)
1342
1555
 
1343
- assert_allclose(est1.as_matrix(), est2.as_matrix())
1344
- assert_allclose(np.sqrt(scale) * rssd1, rssd2, atol=1e-6)
1345
- assert_allclose(cov1, cov2)
1556
+ xp_assert_close(est1.as_matrix(), est2.as_matrix())
1557
+ xp_assert_close(math.sqrt(scale) * rssd1, rssd2, atol=1e-6)
1558
+ xp_assert_close(cov1, cov2)
1346
1559
 
1347
1560
 
1348
- def test_align_vectors_noise():
1561
+ def test_align_vectors_noise(xp):
1349
1562
  rng = np.random.default_rng(146972845698875399755764481408308808739)
1350
1563
  n_vectors = 100
1351
- rot = Rotation.random(rng=rng)
1352
- vectors = rng.normal(size=(n_vectors, 3))
1564
+ rot = rotation_to_xp(Rotation.random(rng=rng), xp)
1565
+ vectors = xp.asarray(rng.normal(size=(n_vectors, 3)))
1353
1566
  result = rot.apply(vectors)
1354
1567
 
1355
1568
  # The paper adds noise as independently distributed angular errors
1356
1569
  sigma = np.deg2rad(1)
1357
1570
  tolerance = 1.5 * sigma
1358
1571
  noise = Rotation.from_rotvec(
1359
- rng.normal(
1572
+ xp.asarray(rng.normal(
1360
1573
  size=(n_vectors, 3),
1361
1574
  scale=sigma
1362
- )
1575
+ ))
1363
1576
  )
1364
1577
 
1365
1578
  # Attitude errors must preserve norm. Hence apply individual random
@@ -1371,99 +1584,134 @@ def test_align_vectors_noise():
1371
1584
 
1372
1585
  # Use rotation compositions to find out closeness
1373
1586
  error_vector = (rot * est.inv()).as_rotvec()
1374
- assert_allclose(error_vector[0], 0, atol=tolerance)
1375
- assert_allclose(error_vector[1], 0, atol=tolerance)
1376
- assert_allclose(error_vector[2], 0, atol=tolerance)
1587
+ xp_assert_close(error_vector[0], xp.asarray(0.0)[()], atol=tolerance)
1588
+ xp_assert_close(error_vector[1], xp.asarray(0.0)[()], atol=tolerance)
1589
+ xp_assert_close(error_vector[2], xp.asarray(0.0)[()], atol=tolerance)
1377
1590
 
1378
1591
  # Check error bounds using covariance matrix
1379
1592
  cov *= sigma
1380
- assert_allclose(cov[0, 0], 0, atol=tolerance)
1381
- assert_allclose(cov[1, 1], 0, atol=tolerance)
1382
- assert_allclose(cov[2, 2], 0, atol=tolerance)
1593
+ xp_assert_close(cov[0, 0], xp.asarray(0.0)[()], atol=tolerance)
1594
+ xp_assert_close(cov[1, 1], xp.asarray(0.0)[()], atol=tolerance)
1595
+ xp_assert_close(cov[2, 2], xp.asarray(0.0)[()], atol=tolerance)
1383
1596
 
1384
- assert_allclose(rssd, np.sum((noisy_result - est.apply(vectors))**2)**0.5)
1597
+ rssd_check = xp.sum((noisy_result - est.apply(vectors)) ** 2) ** 0.5
1598
+ xp_assert_close(rssd, rssd_check, check_shape=False)
1385
1599
 
1386
1600
 
1387
- def test_align_vectors_invalid_input():
1601
+ def test_align_vectors_invalid_input(xp):
1388
1602
  with pytest.raises(ValueError, match="Expected input `a` to have shape"):
1389
- Rotation.align_vectors([1, 2, 3, 4], [1, 2, 3])
1603
+ a, b = xp.asarray([1, 2, 3, 4]), xp.asarray([1, 2, 3])
1604
+ Rotation.align_vectors(a, b)
1390
1605
 
1391
1606
  with pytest.raises(ValueError, match="Expected input `b` to have shape"):
1392
- Rotation.align_vectors([1, 2, 3], [1, 2, 3, 4])
1607
+ a, b = xp.asarray([1, 2, 3]), xp.asarray([1, 2, 3, 4])
1608
+ Rotation.align_vectors(a, b)
1393
1609
 
1394
1610
  with pytest.raises(ValueError, match="Expected inputs `a` and `b` "
1395
1611
  "to have same shapes"):
1396
- Rotation.align_vectors([[1, 2, 3],[4, 5, 6]], [[1, 2, 3]])
1612
+ a, b = xp.asarray([[1, 2, 3], [4, 5, 6]]), xp.asarray([[1, 2, 3]])
1613
+ Rotation.align_vectors(a, b)
1397
1614
 
1398
1615
  with pytest.raises(ValueError,
1399
1616
  match="Expected `weights` to be 1 dimensional"):
1400
- Rotation.align_vectors([[1, 2, 3]], [[1, 2, 3]], weights=[[1]])
1617
+ a, b = xp.asarray([[1, 2, 3]]), xp.asarray([[1, 2, 3]])
1618
+ weights = xp.asarray([[1]])
1619
+ Rotation.align_vectors(a, b, weights)
1401
1620
 
1402
1621
  with pytest.raises(ValueError,
1403
1622
  match="Expected `weights` to have number of values"):
1404
- Rotation.align_vectors([[1, 2, 3], [4, 5, 6]], [[1, 2, 3], [4, 5, 6]],
1405
- weights=[1, 2, 3])
1406
-
1407
- with pytest.raises(ValueError,
1408
- match="`weights` may not contain negative values"):
1409
- Rotation.align_vectors([[1, 2, 3]], [[1, 2, 3]], weights=[-1])
1410
-
1411
- with pytest.raises(ValueError,
1412
- match="Only one infinite weight is allowed"):
1413
- Rotation.align_vectors([[1, 2, 3], [4, 5, 6]], [[1, 2, 3], [4, 5, 6]],
1414
- weights=[np.inf, np.inf])
1415
-
1416
- with pytest.raises(ValueError,
1417
- match="Cannot align zero length primary vectors"):
1418
- Rotation.align_vectors([[0, 0, 0]], [[1, 2, 3]])
1419
-
1420
- with pytest.raises(ValueError,
1421
- match="Cannot return sensitivity matrix"):
1422
- Rotation.align_vectors([[1, 2, 3], [4, 5, 6]], [[1, 2, 3], [4, 5, 6]],
1423
- return_sensitivity=True, weights=[np.inf, 1])
1424
-
1425
- with pytest.raises(ValueError,
1426
- match="Cannot return sensitivity matrix"):
1427
- Rotation.align_vectors([[1, 2, 3]], [[1, 2, 3]],
1428
- return_sensitivity=True)
1429
-
1430
-
1431
- def test_align_vectors_align_constrain():
1623
+ a, b = xp.asarray([[1, 2, 3], [4, 5, 6]]), xp.asarray([[1, 2, 3], [4, 5, 6]])
1624
+ weights = xp.asarray([1, 2, 3])
1625
+ Rotation.align_vectors(a, b, weights)
1626
+
1627
+ a, b = xp.asarray([[1, 2, 3]]), xp.asarray([[1, 2, 3]])
1628
+ weights = xp.asarray([-1])
1629
+ if is_lazy_array(weights):
1630
+ r, rssd = Rotation.align_vectors(a, b, weights)
1631
+ assert xp.all(xp.isnan(r.as_quat())), "Quaternion should be nan"
1632
+ assert xp.isnan(rssd), "RSSD should be nan"
1633
+ else:
1634
+ with pytest.raises(ValueError,
1635
+ match="`weights` may not contain negative values"):
1636
+ Rotation.align_vectors(a, b, weights)
1637
+
1638
+ a, b = xp.asarray([[1, 2, 3], [4, 5, 6]]), xp.asarray([[1, 2, 3], [4, 5, 6]])
1639
+ weights = xp.asarray([xp.inf, xp.inf])
1640
+ if is_lazy_array(weights):
1641
+ r, rssd = Rotation.align_vectors(a, b, weights)
1642
+ assert xp.all(xp.isnan(r.as_quat())), "Quaternion should be nan"
1643
+ assert xp.isnan(rssd), "RSSD should be nan"
1644
+ else:
1645
+ with pytest.raises(ValueError,
1646
+ match="Only one infinite weight is allowed"):
1647
+ Rotation.align_vectors(a, b, weights)
1648
+
1649
+ a, b = xp.asarray([[0, 0, 0]]), xp.asarray([[1, 2, 3]])
1650
+ if is_lazy_array(a):
1651
+ r, rssd = Rotation.align_vectors(a, b)
1652
+ assert xp.all(xp.isnan(r.as_quat())), "Quaternion should be nan"
1653
+ assert xp.isnan(rssd), "RSSD should be nan"
1654
+ else:
1655
+ with pytest.raises(ValueError,
1656
+ match="Cannot align zero length primary vectors"):
1657
+ Rotation.align_vectors(a, b)
1658
+
1659
+ a, b = xp.asarray([[1, 2, 3], [4, 5, 6]]), xp.asarray([[1, 2, 3], [4, 5, 6]])
1660
+ weights = xp.asarray([xp.inf, 1])
1661
+ if is_lazy_array(a):
1662
+ r, rssd, sens = Rotation.align_vectors(a, b, weights, return_sensitivity=True)
1663
+ assert xp.all(xp.isnan(sens)), "Sensitivity matrix should be nan"
1664
+ else:
1665
+ with pytest.raises(ValueError,
1666
+ match="Cannot return sensitivity matrix"):
1667
+ Rotation.align_vectors(a, b, weights, return_sensitivity=True)
1668
+
1669
+ a, b = xp.asarray([[1, 2, 3]]), xp.asarray([[1, 2, 3]])
1670
+ if is_lazy_array(a):
1671
+ r, rssd, sens = Rotation.align_vectors(a, b, return_sensitivity=True)
1672
+ assert xp.all(xp.isnan(sens)), "Sensitivity matrix should be nan"
1673
+ else:
1674
+ with pytest.raises(ValueError,
1675
+ match="Cannot return sensitivity matrix"):
1676
+ Rotation.align_vectors(a, b, return_sensitivity=True)
1677
+
1678
+
1679
+ def test_align_vectors_align_constrain(xp):
1432
1680
  # Align the primary +X B axis with the primary +Y A axis, and rotate about
1433
1681
  # it such that the +Y B axis (residual of the [1, 1, 0] secondary b vector)
1434
1682
  # is aligned with the +Z A axis (residual of the [0, 1, 1] secondary a
1435
1683
  # vector)
1436
1684
  atol = 1e-12
1437
- b = [[1, 0, 0], [1, 1, 0]]
1438
- a = [[0, 1, 0], [0, 1, 1]]
1439
- m_expected = np.array([[0, 0, 1],
1440
- [1, 0, 0],
1441
- [0, 1, 0]])
1442
- R, rssd = Rotation.align_vectors(a, b, weights=[np.inf, 1])
1443
- assert_allclose(R.as_matrix(), m_expected, atol=atol)
1444
- assert_allclose(R.apply(b), a, atol=atol) # Pri and sec align exactly
1445
- assert np.isclose(rssd, 0, atol=atol)
1685
+ b = xp.asarray([[1, 0, 0], [1, 1, 0]])
1686
+ a = xp.asarray([[0.0, 1, 0], [0, 1, 1]])
1687
+ m_expected = xp.asarray([[0.0, 0, 1],
1688
+ [1, 0, 0],
1689
+ [0, 1, 0]])
1690
+ R, rssd = Rotation.align_vectors(a, b, weights=xp.asarray([xp.inf, 1]))
1691
+ xp_assert_close(R.as_matrix(), m_expected, atol=atol)
1692
+ xp_assert_close(R.apply(b), a, atol=atol) # Pri and sec align exactly
1693
+ assert xpx.isclose(rssd, 0.0, atol=atol, xp=xp)
1446
1694
 
1447
1695
  # Do the same but with an inexact secondary rotation
1448
- b = [[1, 0, 0], [1, 2, 0]]
1696
+ b = xp.asarray([[1, 0, 0], [1, 2, 0]])
1449
1697
  rssd_expected = 1.0
1450
- R, rssd = Rotation.align_vectors(a, b, weights=[np.inf, 1])
1451
- assert_allclose(R.as_matrix(), m_expected, atol=atol)
1452
- assert_allclose(R.apply(b)[0], a[0], atol=atol) # Only pri aligns exactly
1453
- assert np.isclose(rssd, rssd_expected, atol=atol)
1454
- a_expected = [[0, 1, 0], [0, 1, 2]]
1455
- assert_allclose(R.apply(b), a_expected, atol=atol)
1698
+ R, rssd = Rotation.align_vectors(a, b, weights=xp.asarray([xp.inf, 1]))
1699
+ xp_assert_close(R.as_matrix(), m_expected, atol=atol)
1700
+ xp_assert_close(R.apply(b)[0, ...], a[0, ...], atol=atol) # Only pri aligns exactly
1701
+ assert xpx.isclose(rssd, rssd_expected, atol=atol, xp=xp)
1702
+ a_expected = xp.asarray([[0.0, 1, 0], [0, 1, 2]])
1703
+ xp_assert_close(R.apply(b), a_expected, atol=atol)
1456
1704
 
1457
1705
  # Check random vectors
1458
- b = [[1, 2, 3], [-2, 3, -1]]
1459
- a = [[-1, 3, 2], [1, -1, 2]]
1706
+ b = xp.asarray([[1, 2, 3], [-2, 3, -1]])
1707
+ a = xp.asarray([[-1.0, 3, 2], [1, -1, 2]])
1460
1708
  rssd_expected = 1.3101595297515016
1461
- R, rssd = Rotation.align_vectors(a, b, weights=[np.inf, 1])
1462
- assert_allclose(R.apply(b)[0], a[0], atol=atol) # Only pri aligns exactly
1463
- assert np.isclose(rssd, rssd_expected, atol=atol)
1709
+ R, rssd = Rotation.align_vectors(a, b, weights=xp.asarray([xp.inf, 1]))
1710
+ xp_assert_close(R.apply(b)[0, ...], a[0, ...], atol=atol) # Only pri aligns exactly
1711
+ assert xpx.isclose(rssd, rssd_expected, atol=atol, xp=xp)
1464
1712
 
1465
1713
 
1466
- def test_align_vectors_near_inf():
1714
+ def test_align_vectors_near_inf(xp):
1467
1715
  # align_vectors should return near the same result for high weights as for
1468
1716
  # infinite weights. rssd will be different with floating point error on the
1469
1717
  # exactly aligned vector being multiplied by a large non-infinite weight
@@ -1474,58 +1722,60 @@ def test_align_vectors_near_inf():
1474
1722
 
1475
1723
  for i in range(n):
1476
1724
  # Get random pairs of 3-element vectors
1477
- a = [1*mats[0][i][0], 2*mats[1][i][0]]
1478
- b = [3*mats[2][i][0], 4*mats[3][i][0]]
1725
+ a = xp.asarray([1 * mats[0][i][0], 2 * mats[1][i][0]])
1726
+ b = xp.asarray([3 * mats[2][i][0], 4 * mats[3][i][0]])
1479
1727
 
1480
1728
  R, _ = Rotation.align_vectors(a, b, weights=[1e10, 1])
1481
- R2, _ = Rotation.align_vectors(a, b, weights=[np.inf, 1])
1482
- assert_allclose(R.as_matrix(), R2.as_matrix(), atol=1e-4)
1729
+ R2, _ = Rotation.align_vectors(a, b, weights=[xp.inf, 1])
1730
+ xp_assert_close(R.as_matrix(), R2.as_matrix(), atol=1e-4)
1483
1731
 
1484
1732
  for i in range(n):
1485
1733
  # Get random triplets of 3-element vectors
1486
- a = [1*mats[0][i][0], 2*mats[1][i][0], 3*mats[2][i][0]]
1487
- b = [4*mats[3][i][0], 5*mats[4][i][0], 6*mats[5][i][0]]
1734
+ a = xp.asarray([1*mats[0][i][0], 2*mats[1][i][0], 3*mats[2][i][0]])
1735
+ b = xp.asarray([4*mats[3][i][0], 5*mats[4][i][0], 6*mats[5][i][0]])
1488
1736
 
1489
1737
  R, _ = Rotation.align_vectors(a, b, weights=[1e10, 2, 1])
1490
- R2, _ = Rotation.align_vectors(a, b, weights=[np.inf, 2, 1])
1491
- assert_allclose(R.as_matrix(), R2.as_matrix(), atol=1e-4)
1738
+ R2, _ = Rotation.align_vectors(a, b, weights=[xp.inf, 2, 1])
1739
+ xp_assert_close(R.as_matrix(), R2.as_matrix(), atol=1e-4)
1492
1740
 
1493
1741
 
1494
- def test_align_vectors_parallel():
1742
+ def test_align_vectors_parallel(xp):
1495
1743
  atol = 1e-12
1496
- a = [[1, 0, 0], [0, 1, 0]]
1497
- b = [[0, 1, 0], [0, 1, 0]]
1498
- m_expected = np.array([[0, 1, 0],
1499
- [-1, 0, 0],
1500
- [0, 0, 1]])
1501
- R, _ = Rotation.align_vectors(a, b, weights=[np.inf, 1])
1502
- assert_allclose(R.as_matrix(), m_expected, atol=atol)
1503
- R, _ = Rotation.align_vectors(a[0], b[0])
1504
- assert_allclose(R.as_matrix(), m_expected, atol=atol)
1505
- assert_allclose(R.apply(b[0]), a[0], atol=atol)
1506
-
1507
- b = [[1, 0, 0], [1, 0, 0]]
1508
- m_expected = np.array([[1, 0, 0],
1509
- [0, 1, 0],
1510
- [0, 0, 1]])
1511
- R, _ = Rotation.align_vectors(a, b, weights=[np.inf, 1])
1512
- assert_allclose(R.as_matrix(), m_expected, atol=atol)
1513
- R, _ = Rotation.align_vectors(a[0], b[0])
1514
- assert_allclose(R.as_matrix(), m_expected, atol=atol)
1515
- assert_allclose(R.apply(b[0]), a[0], atol=atol)
1516
-
1517
-
1518
- def test_align_vectors_antiparallel():
1744
+ a = xp.asarray([[1.0, 0, 0], [0, 1, 0]])
1745
+ b = xp.asarray([[0.0, 1, 0], [0, 1, 0]])
1746
+ m_expected = xp.asarray([[0.0, 1, 0],
1747
+ [-1, 0, 0],
1748
+ [0, 0, 1]])
1749
+ R, _ = Rotation.align_vectors(a, b, weights=[xp.inf, 1])
1750
+ xp_assert_close(R.as_matrix(), m_expected, atol=atol)
1751
+ R, _ = Rotation.align_vectors(a[0, ...], b[0, ...])
1752
+ xp_assert_close(R.as_matrix(), m_expected, atol=atol)
1753
+ xp_assert_close(R.apply(b[0, ...]), a[0, ...], atol=atol)
1754
+
1755
+ b = xp.asarray([[1, 0, 0], [1, 0, 0]])
1756
+ m_expected = xp.asarray([[1.0, 0, 0],
1757
+ [0, 1, 0],
1758
+ [0, 0, 1]])
1759
+ R, _ = Rotation.align_vectors(a, b, weights=[xp.inf, 1])
1760
+ xp_assert_close(R.as_matrix(), m_expected, atol=atol)
1761
+ R, _ = Rotation.align_vectors(a[0, ...], b[0, ...])
1762
+ xp_assert_close(R.as_matrix(), m_expected, atol=atol)
1763
+ xp_assert_close(R.apply(b[0, ...]), a[0, ...], atol=atol)
1764
+
1765
+
1766
+ def test_align_vectors_antiparallel(xp):
1519
1767
  # Test exact 180 deg rotation
1520
1768
  atol = 1e-12
1521
- as_to_test = np.array([[[1, 0, 0], [0, 1, 0]],
1769
+ as_to_test = np.array([[[1.0, 0, 0], [0, 1, 0]],
1522
1770
  [[0, 1, 0], [1, 0, 0]],
1523
1771
  [[0, 0, 1], [0, 1, 0]]])
1772
+
1524
1773
  bs_to_test = [[-a[0], a[1]] for a in as_to_test]
1525
1774
  for a, b in zip(as_to_test, bs_to_test):
1526
- R, _ = Rotation.align_vectors(a, b, weights=[np.inf, 1])
1527
- assert_allclose(R.magnitude(), np.pi, atol=atol)
1528
- assert_allclose(R.apply(b[0]), a[0], atol=atol)
1775
+ a, b = xp.asarray(a), xp.asarray(b)
1776
+ R, _ = Rotation.align_vectors(a, b, weights=[xp.inf, 1])
1777
+ xp_assert_close(R.magnitude(), xp.pi, atol=atol)
1778
+ xp_assert_close(R.apply(b[0, ...]), a[0, ...], atol=atol)
1529
1779
 
1530
1780
  # Test exact rotations near 180 deg
1531
1781
  Rs = Rotation.random(100, rng=0)
@@ -1534,223 +1784,303 @@ def test_align_vectors_antiparallel():
1534
1784
  b = [[-1, 0, 0], [0, 1, 0]]
1535
1785
  as_to_test = []
1536
1786
  for dR in dRs:
1537
- as_to_test.append([dR.apply(a[0]), a[1]])
1787
+ as_to_test.append(np.array([dR.apply(a[0]), a[1]]))
1538
1788
  for a in as_to_test:
1539
- R, _ = Rotation.align_vectors(a, b, weights=[np.inf, 1])
1789
+ a, b = xp.asarray(a), xp.asarray(b)
1790
+ R, _ = Rotation.align_vectors(a, b, weights=[xp.inf, 1])
1540
1791
  R2, _ = Rotation.align_vectors(a, b, weights=[1e10, 1])
1541
- assert_allclose(R.as_matrix(), R2.as_matrix(), atol=atol)
1792
+ xp_assert_close(R.as_matrix(), R2.as_matrix(), atol=atol)
1542
1793
 
1543
1794
 
1544
- def test_align_vectors_primary_only():
1795
+ def test_align_vectors_primary_only(xp):
1545
1796
  atol = 1e-12
1546
1797
  mats_a = Rotation.random(100, rng=0).as_matrix()
1547
1798
  mats_b = Rotation.random(100, rng=1).as_matrix()
1799
+
1548
1800
  for mat_a, mat_b in zip(mats_a, mats_b):
1549
1801
  # Get random 3-element unit vectors
1550
- a = mat_a[0]
1551
- b = mat_b[0]
1802
+ a = xp.asarray(mat_a[0])
1803
+ b = xp.asarray(mat_b[0])
1552
1804
 
1553
1805
  # Compare to align_vectors with primary only
1554
1806
  R, rssd = Rotation.align_vectors(a, b)
1555
- assert_allclose(R.apply(b), a, atol=atol)
1807
+ xp_assert_close(R.apply(b), a, atol=atol)
1556
1808
  assert np.isclose(rssd, 0, atol=atol)
1557
1809
 
1558
1810
 
1559
- def test_slerp():
1811
+ def test_align_vectors_array_like():
1812
+ rng = np.random.default_rng(123)
1813
+ c = Rotation.random(rng=rng)
1814
+ b = rng.normal(size=(5, 3))
1815
+ a = c.apply(b)
1816
+
1817
+ est_expected, rssd_expected = Rotation.align_vectors(a, b)
1818
+ est, rssd = Rotation.align_vectors(a.tolist(), b.tolist())
1819
+ xp_assert_close(est_expected.as_quat(), est.as_quat())
1820
+ xp_assert_close(rssd, rssd_expected)
1821
+
1822
+
1823
+ def test_repr_single_rotation(xp):
1824
+ q = xp.asarray([0, 0, 0, 1])
1825
+ actual = repr(Rotation.from_quat(q))
1826
+ if is_numpy(xp):
1827
+ expected = """\
1828
+ Rotation.from_matrix(array([[1., 0., 0.],
1829
+ [0., 1., 0.],
1830
+ [0., 0., 1.]]))"""
1831
+ assert actual == expected
1832
+ else:
1833
+ assert actual.startswith("Rotation.from_matrix(")
1834
+
1835
+
1836
+ def test_repr_rotation_sequence(xp):
1837
+ q = xp.asarray([[0.0, 1, 0, 1], [0, 0, 1, 1]]) / math.sqrt(2)
1838
+ actual = f"{Rotation.from_quat(q)!r}"
1839
+ if is_numpy(xp):
1840
+ expected = """\
1841
+ Rotation.from_matrix(array([[[ 0., 0., 1.],
1842
+ [ 0., 1., 0.],
1843
+ [-1., 0., 0.]],
1844
+
1845
+ [[ 0., -1., 0.],
1846
+ [ 1., 0., 0.],
1847
+ [ 0., 0., 1.]]]))"""
1848
+ assert actual == expected
1849
+ else:
1850
+ assert actual.startswith("Rotation.from_matrix(")
1851
+
1852
+
1853
+ def test_slerp(xp):
1560
1854
  rnd = np.random.RandomState(0)
1561
1855
 
1562
- key_rots = Rotation.from_quat(rnd.uniform(size=(5, 4)))
1856
+ key_rots = Rotation.from_quat(xp.asarray(rnd.uniform(size=(5, 4))))
1563
1857
  key_quats = key_rots.as_quat()
1564
1858
 
1565
1859
  key_times = [0, 1, 2, 3, 4]
1566
1860
  interpolator = Slerp(key_times, key_rots)
1861
+ assert isinstance(interpolator.times, type(xp.asarray(0)))
1567
1862
 
1568
1863
  times = [0, 0.5, 0.25, 1, 1.5, 2, 2.75, 3, 3.25, 3.60, 4]
1569
1864
  interp_rots = interpolator(times)
1570
1865
  interp_quats = interp_rots.as_quat()
1571
1866
 
1572
1867
  # Dot products are affected by sign of quaternions
1573
- interp_quats[interp_quats[:, -1] < 0] *= -1
1868
+ mask = (interp_quats[:, -1] < 0)[:, None]
1869
+ interp_quats = xp.where(mask, -interp_quats, interp_quats)
1574
1870
  # Checking for quaternion equality, perform same operation
1575
- key_quats[key_quats[:, -1] < 0] *= -1
1871
+ mask = (key_quats[:, -1] < 0)[:, None]
1872
+ key_quats = xp.where(mask, -key_quats, key_quats)
1576
1873
 
1577
1874
  # Equality at keyframes, including both endpoints
1578
- assert_allclose(interp_quats[0], key_quats[0])
1579
- assert_allclose(interp_quats[3], key_quats[1])
1580
- assert_allclose(interp_quats[5], key_quats[2])
1581
- assert_allclose(interp_quats[7], key_quats[3])
1582
- assert_allclose(interp_quats[10], key_quats[4])
1875
+ xp_assert_close(interp_quats[0, ...], key_quats[0, ...])
1876
+ xp_assert_close(interp_quats[3, ...], key_quats[1, ...])
1877
+ xp_assert_close(interp_quats[5, ...], key_quats[2, ...])
1878
+ xp_assert_close(interp_quats[7, ...], key_quats[3, ...])
1879
+ xp_assert_close(interp_quats[10, ...], key_quats[4, ...])
1583
1880
 
1584
1881
  # Constant angular velocity between keyframes. Check by equating
1585
1882
  # cos(theta) between quaternion pairs with equal time difference.
1586
- cos_theta1 = np.sum(interp_quats[0] * interp_quats[2])
1587
- cos_theta2 = np.sum(interp_quats[2] * interp_quats[1])
1588
- assert_allclose(cos_theta1, cos_theta2)
1883
+ cos_theta1 = xp.sum(interp_quats[0, ...] * interp_quats[2, ...])
1884
+ cos_theta2 = xp.sum(interp_quats[2, ...] * interp_quats[1, ...])
1885
+ xp_assert_close(cos_theta1, cos_theta2)
1589
1886
 
1590
- cos_theta4 = np.sum(interp_quats[3] * interp_quats[4])
1591
- cos_theta5 = np.sum(interp_quats[4] * interp_quats[5])
1592
- assert_allclose(cos_theta4, cos_theta5)
1887
+ cos_theta4 = xp.sum(interp_quats[3, ...] * interp_quats[4, ...])
1888
+ cos_theta5 = xp.sum(interp_quats[4, ...] * interp_quats[5, ...])
1889
+ xp_assert_close(cos_theta4, cos_theta5)
1593
1890
 
1594
1891
  # theta1: 0 -> 0.25, theta3 : 0.5 -> 1
1595
1892
  # Use double angle formula for double the time difference
1596
- cos_theta3 = np.sum(interp_quats[1] * interp_quats[3])
1597
- assert_allclose(cos_theta3, 2 * (cos_theta1**2) - 1)
1893
+ cos_theta3 = xp.sum(interp_quats[1, ...] * interp_quats[3, ...])
1894
+ xp_assert_close(cos_theta3, 2 * (cos_theta1**2) - 1)
1598
1895
 
1599
1896
  # Miscellaneous checks
1600
1897
  assert_equal(len(interp_rots), len(times))
1601
1898
 
1602
1899
 
1603
- def test_slerp_rot_is_rotation():
1900
+ def test_slerp_rot_is_rotation(xp):
1604
1901
  with pytest.raises(TypeError, match="must be a `Rotation` instance"):
1605
- r = np.array([[1,2,3,4],
1606
- [0,0,0,1]])
1607
- t = np.array([0, 1])
1902
+ r = xp.asarray([[1,2,3,4],
1903
+ [0,0,0,1]])
1904
+ t = xp.asarray([0, 1])
1608
1905
  Slerp(t, r)
1609
1906
 
1907
+
1610
1908
  SLERP_EXCEPTION_MESSAGE = "must be a sequence of at least 2 rotations"
1611
1909
 
1612
- def test_slerp_single_rot():
1613
- r = Rotation.from_quat([1, 2, 3, 4])
1910
+
1911
+ def test_slerp_single_rot(xp):
1912
+ r = Rotation.from_quat(xp.asarray([[1.0, 2, 3, 4]]))
1614
1913
  with pytest.raises(ValueError, match=SLERP_EXCEPTION_MESSAGE):
1615
1914
  Slerp([1], r)
1616
1915
 
1617
1916
 
1618
- def test_slerp_rot_len0():
1917
+ def test_slerp_rot_len0(xp):
1619
1918
  r = Rotation.random()
1919
+ r = Rotation.from_quat(xp.asarray(r.as_quat()))
1620
1920
  with pytest.raises(ValueError, match=SLERP_EXCEPTION_MESSAGE):
1621
1921
  Slerp([], r)
1622
1922
 
1623
1923
 
1624
- def test_slerp_rot_len1():
1924
+ def test_slerp_rot_len1(xp):
1625
1925
  r = Rotation.random(1)
1926
+ r = Rotation.from_quat(xp.asarray(r.as_quat()))
1626
1927
  with pytest.raises(ValueError, match=SLERP_EXCEPTION_MESSAGE):
1627
1928
  Slerp([1], r)
1628
1929
 
1629
1930
 
1630
- def test_slerp_time_dim_mismatch():
1931
+ def test_slerp_time_dim_mismatch(xp):
1631
1932
  with pytest.raises(ValueError,
1632
1933
  match="times to be specified in a 1 dimensional array"):
1633
1934
  rnd = np.random.RandomState(0)
1634
- r = Rotation.from_quat(rnd.uniform(size=(2, 4)))
1635
- t = np.array([[1],
1636
- [2]])
1935
+ r = Rotation.from_quat(xp.asarray(rnd.uniform(size=(2, 4))))
1936
+ t = xp.asarray([[1],
1937
+ [2]])
1637
1938
  Slerp(t, r)
1638
1939
 
1639
1940
 
1640
- def test_slerp_num_rotations_mismatch():
1941
+ def test_slerp_num_rotations_mismatch(xp):
1641
1942
  with pytest.raises(ValueError, match="number of rotations to be equal to "
1642
1943
  "number of timestamps"):
1643
1944
  rnd = np.random.RandomState(0)
1644
- r = Rotation.from_quat(rnd.uniform(size=(5, 4)))
1645
- t = np.arange(7)
1945
+ r = Rotation.from_quat(xp.asarray(rnd.uniform(size=(5, 4))))
1946
+ t = xp.arange(7)
1646
1947
  Slerp(t, r)
1647
1948
 
1648
1949
 
1649
- def test_slerp_equal_times():
1650
- with pytest.raises(ValueError, match="strictly increasing order"):
1651
- rnd = np.random.RandomState(0)
1652
- r = Rotation.from_quat(rnd.uniform(size=(5, 4)))
1653
- t = [0, 1, 2, 2, 4]
1654
- Slerp(t, r)
1950
+ def test_slerp_equal_times(xp):
1951
+ rnd = np.random.RandomState(0)
1952
+ q = xp.asarray(rnd.uniform(size=(5, 4)))
1953
+ r = Rotation.from_quat(q)
1954
+ t = [0, 1, 2, 2, 4]
1955
+ if is_lazy_array(q):
1956
+ s = Slerp(t, r)
1957
+ assert xp.all(xp.isnan(s.times))
1958
+ else:
1959
+ with pytest.raises(ValueError, match="strictly increasing order"):
1960
+ Slerp(t, r)
1655
1961
 
1656
1962
 
1657
- def test_slerp_decreasing_times():
1658
- with pytest.raises(ValueError, match="strictly increasing order"):
1659
- rnd = np.random.RandomState(0)
1660
- r = Rotation.from_quat(rnd.uniform(size=(5, 4)))
1661
- t = [0, 1, 3, 2, 4]
1662
- Slerp(t, r)
1963
+ def test_slerp_decreasing_times(xp):
1964
+ rnd = np.random.RandomState(0)
1965
+ q = xp.asarray(rnd.uniform(size=(5, 4)))
1966
+ r = Rotation.from_quat(q)
1967
+ t = [0, 1, 3, 2, 4]
1968
+ if is_lazy_array(q):
1969
+ s = Slerp(t, r)
1970
+ assert xp.all(xp.isnan(s.times))
1971
+ else:
1972
+ with pytest.raises(ValueError, match="strictly increasing order"):
1973
+ Slerp(t, r)
1663
1974
 
1664
1975
 
1665
- def test_slerp_call_time_dim_mismatch():
1976
+ def test_slerp_call_time_dim_mismatch(xp):
1666
1977
  rnd = np.random.RandomState(0)
1667
- r = Rotation.from_quat(rnd.uniform(size=(5, 4)))
1668
- t = np.arange(5)
1978
+ r = Rotation.from_quat(xp.asarray(rnd.uniform(size=(5, 4))))
1979
+ t = xp.arange(5)
1669
1980
  s = Slerp(t, r)
1670
1981
 
1671
1982
  with pytest.raises(ValueError,
1672
1983
  match="`times` must be at most 1-dimensional."):
1673
- interp_times = np.array([[3.5],
1674
- [4.2]])
1984
+ interp_times = xp.asarray([[3.5],
1985
+ [4.2]])
1675
1986
  s(interp_times)
1676
1987
 
1677
1988
 
1678
- def test_slerp_call_time_out_of_range():
1989
+ def test_slerp_call_time_out_of_range(xp):
1679
1990
  rnd = np.random.RandomState(0)
1680
- r = Rotation.from_quat(rnd.uniform(size=(5, 4)))
1681
- t = np.arange(5) + 1
1991
+ r = Rotation.from_quat(xp.asarray(rnd.uniform(size=(5, 4))))
1992
+ t = xp.arange(5) + 1
1682
1993
  s = Slerp(t, r)
1683
1994
 
1684
- with pytest.raises(ValueError, match="times must be within the range"):
1685
- s([0, 1, 2])
1686
- with pytest.raises(ValueError, match="times must be within the range"):
1687
- s([1, 2, 6])
1688
-
1689
-
1690
- def test_slerp_call_scalar_time():
1691
- r = Rotation.from_euler('X', [0, 80], degrees=True)
1995
+ times_low = xp.asarray([0, 1, 2])
1996
+ times_high = xp.asarray([1, 2, 6])
1997
+ if is_lazy_array(times_low):
1998
+ q = s(times_low).as_quat()
1999
+ in_range = xp.logical_and(times_low >= xp.min(t), times_low <= xp.max(t))
2000
+ assert xp.all(xp.isnan(q[~in_range, ...]))
2001
+ assert xp.all(~xp.isnan(q[in_range, ...]))
2002
+ q = s(times_high).as_quat()
2003
+ in_range = xp.logical_and(times_high >= xp.min(t), times_high <= xp.max(t))
2004
+ assert xp.all(xp.isnan(q[~in_range, ...]))
2005
+ assert xp.all(~xp.isnan(q[in_range, ...]))
2006
+ else:
2007
+ with pytest.raises(ValueError, match="times must be within the range"):
2008
+ s(times_low)
2009
+ with pytest.raises(ValueError, match="times must be within the range"):
2010
+ s(times_high)
2011
+
2012
+
2013
+ def test_slerp_call_scalar_time(xp):
2014
+ r = Rotation.from_euler('X', xp.asarray([0, 80]), degrees=True)
1692
2015
  s = Slerp([0, 1], r)
1693
2016
 
1694
2017
  r_interpolated = s(0.25)
1695
- r_interpolated_expected = Rotation.from_euler('X', 20, degrees=True)
2018
+ r_interpolated_expected = Rotation.from_euler('X', xp.asarray(20), degrees=True)
1696
2019
 
1697
2020
  delta = r_interpolated * r_interpolated_expected.inv()
1698
2021
 
1699
- assert_allclose(delta.magnitude(), 0, atol=1e-16)
2022
+ assert xp.allclose(delta.magnitude(), 0, atol=1e-16)
1700
2023
 
1701
2024
 
1702
- def test_multiplication_stability():
2025
+ def test_multiplication_stability(xp):
1703
2026
  qs = Rotation.random(50, rng=0)
2027
+ qs = Rotation.from_quat(xp.asarray(qs.as_quat()))
1704
2028
  rs = Rotation.random(1000, rng=1)
2029
+ rs = Rotation.from_quat(xp.asarray(rs.as_quat()))
2030
+ expected = xp.ones(len(rs))
1705
2031
  for q in qs:
1706
2032
  rs *= q * rs
1707
- assert_allclose(np.linalg.norm(rs.as_quat(), axis=1), 1)
2033
+ xp_assert_close(xp_vector_norm(rs.as_quat(), axis=1), expected)
1708
2034
 
1709
2035
 
1710
- def test_pow():
2036
+ def test_pow(xp):
1711
2037
  atol = 1e-14
1712
2038
  p = Rotation.random(10, rng=0)
2039
+ p = Rotation.from_quat(xp.asarray(p.as_quat()))
1713
2040
  p_inv = p.inv()
1714
2041
  # Test the short-cuts and other integers
1715
2042
  for n in [-5, -2, -1, 0, 1, 2, 5]:
1716
2043
  # Test accuracy
1717
2044
  q = p ** n
1718
2045
  r = Rotation.identity(10)
2046
+ r = Rotation.from_quat(xp.asarray(r.as_quat()))
1719
2047
  for _ in range(abs(n)):
1720
2048
  if n > 0:
1721
2049
  r = r * p
1722
2050
  else:
1723
2051
  r = r * p_inv
1724
2052
  ang = (q * r.inv()).magnitude()
1725
- assert np.all(ang < atol)
2053
+ assert xp.all(ang < atol)
1726
2054
 
1727
2055
  # Test shape preservation
1728
- r = Rotation.from_quat([0, 0, 0, 1])
2056
+ r = Rotation.from_quat(xp.asarray([0, 0, 0, 1]))
1729
2057
  assert (r**n).as_quat().shape == (4,)
1730
- r = Rotation.from_quat([[0, 0, 0, 1]])
2058
+ r = Rotation.from_quat(xp.asarray([[0, 0, 0, 1]]))
1731
2059
  assert (r**n).as_quat().shape == (1, 4)
1732
2060
 
1733
2061
  # Large angle fractional
1734
2062
  for n in [-1.5, -0.5, -0.0, 0.0, 0.5, 1.5]:
1735
2063
  q = p ** n
1736
2064
  r = Rotation.from_rotvec(n * p.as_rotvec())
1737
- assert_allclose(q.as_quat(), r.as_quat(), atol=atol)
2065
+ xp_assert_close(q.as_quat(), r.as_quat(), atol=atol)
1738
2066
 
1739
2067
  # Small angle
1740
- p = Rotation.from_rotvec([1e-12, 0, 0])
2068
+ p = Rotation.from_rotvec(xp.asarray([1e-12, 0, 0]))
1741
2069
  n = 3
1742
2070
  q = p ** n
1743
2071
  r = Rotation.from_rotvec(n * p.as_rotvec())
1744
- assert_allclose(q.as_quat(), r.as_quat(), atol=atol)
2072
+ xp_assert_close(q.as_quat(), r.as_quat(), atol=atol)
1745
2073
 
1746
2074
 
1747
- def test_pow_errors():
2075
+ def test_pow_errors(xp):
1748
2076
  p = Rotation.random(rng=0)
2077
+ p = Rotation.from_quat(xp.asarray(p.as_quat()))
1749
2078
  with pytest.raises(NotImplementedError, match='modulus not supported'):
1750
2079
  pow(p, 1, 1)
1751
2080
 
1752
2081
 
1753
2082
  def test_rotation_within_numpy_array():
2083
+ # TODO: Do we want to support this for all Array API frameworks?
1754
2084
  single = Rotation.random(rng=0)
1755
2085
  multiple = Rotation.random(2, rng=1)
1756
2086
 
@@ -1759,8 +2089,8 @@ def test_rotation_within_numpy_array():
1759
2089
 
1760
2090
  array = np.array(multiple)
1761
2091
  assert_equal(array.shape, (2,))
1762
- assert_allclose(array[0].as_matrix(), multiple[0].as_matrix())
1763
- assert_allclose(array[1].as_matrix(), multiple[1].as_matrix())
2092
+ xp_assert_close(array[0].as_matrix(), multiple[0].as_matrix())
2093
+ xp_assert_close(array[1].as_matrix(), multiple[1].as_matrix())
1764
2094
 
1765
2095
  array = np.array([single])
1766
2096
  assert_equal(array.shape, (1,))
@@ -1768,8 +2098,8 @@ def test_rotation_within_numpy_array():
1768
2098
 
1769
2099
  array = np.array([multiple])
1770
2100
  assert_equal(array.shape, (1, 2))
1771
- assert_allclose(array[0, 0].as_matrix(), multiple[0].as_matrix())
1772
- assert_allclose(array[0, 1].as_matrix(), multiple[1].as_matrix())
2101
+ xp_assert_close(array[0, 0].as_matrix(), multiple[0].as_matrix())
2102
+ xp_assert_close(array[0, 1].as_matrix(), multiple[1].as_matrix())
1773
2103
 
1774
2104
  array = np.array([single, multiple], dtype=object)
1775
2105
  assert_equal(array.shape, (2,))
@@ -1780,20 +2110,25 @@ def test_rotation_within_numpy_array():
1780
2110
  assert_equal(array.shape, (3, 2))
1781
2111
 
1782
2112
 
1783
- def test_pickling():
1784
- r = Rotation.from_quat([0, 0, np.sin(np.pi/4), np.cos(np.pi/4)])
2113
+ def test_pickling(xp):
2114
+ # Note: Array API makes no provision for arrays to be pickleable, so
2115
+ # it's OK to skip this test for the backends that don't support it
2116
+ r = Rotation.from_quat(xp.asarray([0, 0, math.sin(np.pi/4), math.cos(np.pi/4)]))
1785
2117
  pkl = pickle.dumps(r)
1786
2118
  unpickled = pickle.loads(pkl)
1787
- assert_allclose(r.as_matrix(), unpickled.as_matrix(), atol=1e-15)
2119
+ xp_assert_close(r.as_matrix(), unpickled.as_matrix(), atol=1e-15)
1788
2120
 
1789
2121
 
1790
- def test_deepcopy():
1791
- r = Rotation.from_quat([0, 0, np.sin(np.pi/4), np.cos(np.pi/4)])
2122
+ def test_deepcopy(xp):
2123
+ # Note: Array API makes no provision for arrays to support the `__copy__`
2124
+ # protocol, so it's OK to skip this test for the backends that don't
2125
+ r = Rotation.from_quat(xp.asarray([0, 0, math.sin(np.pi/4), math.cos(np.pi/4)]))
1792
2126
  r1 = copy.deepcopy(r)
1793
- assert_allclose(r.as_matrix(), r1.as_matrix(), atol=1e-15)
2127
+ xp_assert_close(r.as_matrix(), r1.as_matrix(), atol=1e-15)
1794
2128
 
1795
2129
 
1796
2130
  def test_as_euler_contiguous():
2131
+ # The Array API does not specify contiguous arrays, so we can only check for NumPy
1797
2132
  r = Rotation.from_quat([0, 0, 0, 1])
1798
2133
  e1 = r.as_euler('xyz') # extrinsic euler rotation
1799
2134
  e2 = r.as_euler('XYZ') # intrinsic
@@ -1803,36 +2138,39 @@ def test_as_euler_contiguous():
1803
2138
  assert all(i >= 0 for i in e2.strides)
1804
2139
 
1805
2140
 
1806
- def test_concatenate():
2141
+ def test_concatenate(xp):
1807
2142
  rotation = Rotation.random(10, rng=0)
2143
+ rotation = Rotation.from_quat(xp.asarray(rotation.as_quat()))
1808
2144
  sizes = [1, 2, 3, 1, 3]
1809
2145
  starts = [0] + list(np.cumsum(sizes))
1810
2146
  split = [rotation[i:i + n] for i, n in zip(starts, sizes)]
1811
2147
  result = Rotation.concatenate(split)
1812
- assert_equal(rotation.as_quat(), result.as_quat())
2148
+ xp_assert_equal(rotation.as_quat(), result.as_quat())
1813
2149
 
1814
2150
  # Test Rotation input for multiple rotations
1815
2151
  result = Rotation.concatenate(rotation)
1816
- assert_equal(rotation.as_quat(), result.as_quat())
2152
+ xp_assert_equal(rotation.as_quat(), result.as_quat())
1817
2153
 
1818
2154
  # Test that a copy is returned
1819
2155
  assert rotation is not result
1820
2156
 
1821
2157
  # Test Rotation input for single rotations
1822
- result = Rotation.concatenate(Rotation.identity())
1823
- assert_equal(Rotation.identity().as_quat(), result.as_quat())
2158
+ rot = Rotation.from_quat(xp.asarray(Rotation.identity().as_quat()))
2159
+ result = Rotation.concatenate(rot)
2160
+ xp_assert_equal(rot.as_quat(), result.as_quat())
1824
2161
 
1825
2162
 
1826
- def test_concatenate_wrong_type():
2163
+ def test_concatenate_wrong_type(xp):
1827
2164
  with pytest.raises(TypeError, match='Rotation objects only'):
1828
- Rotation.concatenate([Rotation.identity(), 1, None])
2165
+ rot = Rotation(xp.asarray(Rotation.identity().as_quat()))
2166
+ Rotation.concatenate([rot, 1, None])
1829
2167
 
1830
2168
 
1831
2169
  # Regression test for gh-16663
1832
- def test_len_and_bool():
1833
- rotation_multi_one = Rotation([[0, 0, 0, 1]])
1834
- rotation_multi = Rotation([[0, 0, 0, 1], [0, 0, 0, 1]])
1835
- rotation_single = Rotation([0, 0, 0, 1])
2170
+ def test_len_and_bool(xp):
2171
+ rotation_multi_one = Rotation(xp.asarray([[0, 0, 0, 1]]))
2172
+ rotation_multi = Rotation(xp.asarray([[0, 0, 0, 1], [0, 0, 0, 1]]))
2173
+ rotation_single = Rotation(xp.asarray([0, 0, 0, 1]))
1836
2174
 
1837
2175
  assert len(rotation_multi_one) == 1
1838
2176
  assert len(rotation_multi) == 2
@@ -1845,61 +2183,93 @@ def test_len_and_bool():
1845
2183
  assert rotation_single
1846
2184
 
1847
2185
 
1848
- def test_from_davenport_single_rotation():
1849
- axis = [0, 0, 1]
2186
+ def test_from_davenport_single_rotation(xp):
2187
+ axis = xp.asarray([0, 0, 1])
1850
2188
  quat = Rotation.from_davenport(axis, 'extrinsic', 90,
1851
2189
  degrees=True).as_quat()
1852
- expected_quat = np.array([0, 0, 1, 1]) / np.sqrt(2)
1853
- assert_allclose(quat, expected_quat)
2190
+ expected_quat = xp.asarray([0.0, 0, 1, 1]) / math.sqrt(2)
2191
+ xp_assert_close(quat, expected_quat)
1854
2192
 
1855
2193
 
1856
- def test_from_davenport_one_or_two_axes():
1857
- ez = [0, 0, 1]
1858
- ey = [0, 1, 0]
2194
+ def test_from_davenport_one_or_two_axes(xp):
2195
+ ez = xp.asarray([0.0, 0, 1])
2196
+ ey = xp.asarray([0.0, 1, 0])
1859
2197
 
1860
2198
  # Single rotation, single axis, axes.shape == (3, )
1861
- rot = Rotation.from_rotvec(np.array(ez) * np.pi/4)
1862
- rot_dav = Rotation.from_davenport(ez, 'e', np.pi/4)
1863
- assert_allclose(rot.as_quat(canonical=True),
2199
+ rot = Rotation.from_rotvec(ez * xp.pi/4)
2200
+ rot_dav = Rotation.from_davenport(ez, 'e', xp.pi/4)
2201
+ xp_assert_close(rot.as_quat(canonical=True),
1864
2202
  rot_dav.as_quat(canonical=True))
1865
2203
 
1866
2204
  # Single rotation, single axis, axes.shape == (1, 3)
1867
- rot = Rotation.from_rotvec([np.array(ez) * np.pi/4])
1868
- rot_dav = Rotation.from_davenport([ez], 'e', [np.pi/4])
1869
- assert_allclose(rot.as_quat(canonical=True),
2205
+ axes = xp.reshape(ez, (1, 3)) # Torch can't create tensors from xp.asarray([ez])
2206
+ rot = Rotation.from_rotvec(axes * xp.pi/4)
2207
+ rot_dav = Rotation.from_davenport(axes, 'e', [xp.pi/4])
2208
+ xp_assert_close(rot.as_quat(canonical=True),
1870
2209
  rot_dav.as_quat(canonical=True))
1871
2210
 
1872
2211
  # Single rotation, two axes, axes.shape == (2, 3)
1873
- rot = Rotation.from_rotvec([np.array(ez) * np.pi/4,
1874
- np.array(ey) * np.pi/6])
2212
+ axes = xp.stack([ez, ey], axis=0)
2213
+ rot = Rotation.from_rotvec(axes * xp.asarray([[xp.pi/4], [xp.pi/6]]))
1875
2214
  rot = rot[0] * rot[1]
1876
- rot_dav = Rotation.from_davenport([ey, ez], 'e', [np.pi/6, np.pi/4])
1877
- assert_allclose(rot.as_quat(canonical=True),
2215
+ axes_dav = xp.stack([ey, ez], axis=0)
2216
+ rot_dav = Rotation.from_davenport(axes_dav, 'e', [xp.pi/6, xp.pi/4])
2217
+ xp_assert_close(rot.as_quat(canonical=True),
1878
2218
  rot_dav.as_quat(canonical=True))
1879
2219
 
1880
2220
  # Two rotations, single axis, axes.shape == (3, )
1881
- rot = Rotation.from_rotvec([np.array(ez) * np.pi/6,
1882
- np.array(ez) * np.pi/4])
1883
- rot_dav = Rotation.from_davenport([ez], 'e', [np.pi/6, np.pi/4])
1884
- assert_allclose(rot.as_quat(canonical=True),
2221
+ axes = xp.stack([ez, ez], axis=0)
2222
+ rot = Rotation.from_rotvec(axes * xp.asarray([[xp.pi/6], [xp.pi/4]]))
2223
+ axes_dav = xp.reshape(ez, (1, 3))
2224
+ rot_dav = Rotation.from_davenport(axes_dav, 'e', [xp.pi/6, xp.pi/4])
2225
+ xp_assert_close(rot.as_quat(canonical=True),
1885
2226
  rot_dav.as_quat(canonical=True))
1886
2227
 
1887
2228
 
1888
- def test_from_davenport_invalid_input():
2229
+ def test_from_davenport_invalid_input(xp):
1889
2230
  ez = [0, 0, 1]
1890
2231
  ey = [0, 1, 0]
1891
2232
  ezy = [0, 1, 1]
1892
- with pytest.raises(ValueError, match="must be orthogonal"):
1893
- Rotation.from_davenport([ez, ezy], 'e', [0, 0])
1894
- with pytest.raises(ValueError, match="must be orthogonal"):
1895
- Rotation.from_davenport([ez, ey, ezy], 'e', [0, 0, 0])
2233
+ # We can only raise in non-lazy frameworks.
2234
+ axes = xp.asarray([ez, ezy])
2235
+ if is_lazy_array(axes):
2236
+ q = Rotation.from_davenport(axes, 'e', [0, 0]).as_quat()
2237
+ assert xp.all(xp.isnan(q))
2238
+ else:
2239
+ with pytest.raises(ValueError, match="must be orthogonal"):
2240
+ Rotation.from_davenport(axes, 'e', [0, 0])
2241
+ axes = xp.asarray([ez, ey, ezy])
2242
+ if is_lazy_array(axes):
2243
+ q = Rotation.from_davenport(axes, 'e', [0, 0, 0]).as_quat()
2244
+ assert xp.all(xp.isnan(q))
2245
+ else:
2246
+ with pytest.raises(ValueError, match="must be orthogonal"):
2247
+ Rotation.from_davenport(axes, 'e', [0, 0, 0])
1896
2248
  with pytest.raises(ValueError, match="order should be"):
1897
- Rotation.from_davenport([ez], 'xyz', [0])
2249
+ Rotation.from_davenport(xp.asarray([ez]), 'xyz', [0])
1898
2250
  with pytest.raises(ValueError, match="Expected `angles`"):
1899
- Rotation.from_davenport([ez, ey, ez], 'e', [0, 1, 2, 3])
2251
+ Rotation.from_davenport(xp.asarray([ez, ey, ez]), 'e', [0, 1, 2, 3])
2252
+
2253
+
2254
+ def test_from_davenport_array_like():
2255
+ rng = np.random.default_rng(123)
2256
+ # Single rotation
2257
+ e1 = np.array([1, 0, 0])
2258
+ e2 = np.array([0, 1, 0])
2259
+ e3 = np.array([0, 0, 1])
2260
+ r_expected = Rotation.random(rng=rng)
2261
+ angles = r_expected.as_davenport([e1, e2, e3], 'e')
2262
+ r = Rotation.from_davenport([e1, e2, e3], 'e', angles.tolist())
2263
+ assert r_expected.approx_equal(r, atol=1e-12)
1900
2264
 
2265
+ # Multiple rotations
2266
+ r_expected = Rotation.random(2, rng=rng)
2267
+ angles = r_expected.as_davenport([e1, e2, e3], 'e')
2268
+ r = Rotation.from_davenport([e1, e2, e3], 'e', angles.tolist())
2269
+ assert np.all(r_expected.approx_equal(r, atol=1e-12))
1901
2270
 
1902
- def test_as_davenport():
2271
+
2272
+ def test_as_davenport(xp):
1903
2273
  rnd = np.random.RandomState(0)
1904
2274
  n = 100
1905
2275
  angles = np.empty((n, 3))
@@ -1908,21 +2278,22 @@ def test_as_davenport():
1908
2278
  angles[:, 2] = rnd.uniform(low=-np.pi, high=np.pi, size=(n,))
1909
2279
  lambdas = rnd.uniform(low=0, high=np.pi, size=(20,))
1910
2280
 
1911
- e1 = np.array([1, 0, 0])
1912
- e2 = np.array([0, 1, 0])
2281
+ e1 = xp.asarray([1.0, 0, 0])
2282
+ e2 = xp.asarray([0.0, 1, 0])
1913
2283
 
1914
2284
  for lamb in lambdas:
1915
- ax_lamb = [e1, e2, Rotation.from_rotvec(lamb*e2).apply(e1)]
2285
+ e3 = xp.asarray(Rotation.from_rotvec(lamb*e2).apply(e1))
2286
+ ax_lamb = xp.stack([e1, e2, e3], axis=0)
1916
2287
  angles[:, 1] = angles_middle - lamb
1917
2288
  for order in ['extrinsic', 'intrinsic']:
1918
- ax = ax_lamb if order == 'intrinsic' else ax_lamb[::-1]
1919
- rot = Rotation.from_davenport(ax, order, angles)
1920
- angles_dav = rot.as_davenport(ax, order)
1921
- assert_allclose(angles_dav, angles)
2289
+ ax = ax_lamb if order == "intrinsic" else xp.flip(ax_lamb, axis=0)
2290
+ rot = Rotation.from_davenport(xp.asarray(ax), order, angles)
2291
+ angles_dav = rot.as_davenport(xp.asarray(ax), order)
2292
+ xp_assert_close(angles_dav, xp.asarray(angles))
1922
2293
 
1923
2294
 
1924
2295
  @pytest.mark.thread_unsafe
1925
- def test_as_davenport_degenerate():
2296
+ def test_as_davenport_degenerate(xp):
1926
2297
  # Since we cannot check for angle equality, we check for rotation matrix
1927
2298
  # equality
1928
2299
  rnd = np.random.RandomState(0)
@@ -1935,23 +2306,25 @@ def test_as_davenport_degenerate():
1935
2306
  angles[:, 2] = rnd.uniform(low=-np.pi, high=np.pi, size=(n,))
1936
2307
  lambdas = rnd.uniform(low=0, high=np.pi, size=(5,))
1937
2308
 
1938
- e1 = np.array([1, 0, 0])
1939
- e2 = np.array([0, 1, 0])
2309
+ e1 = xp.asarray([1.0, 0, 0])
2310
+ e2 = xp.asarray([0.0, 1, 0])
1940
2311
 
1941
2312
  for lamb in lambdas:
1942
- ax_lamb = [e1, e2, Rotation.from_rotvec(lamb*e2).apply(e1)]
2313
+ e3 = xp.asarray(Rotation.from_rotvec(lamb*e2).apply(e1))
2314
+ ax_lamb = xp.stack([e1, e2, e3], axis=0)
1943
2315
  angles[:, 1] = angles_middle - lamb
1944
2316
  for order in ['extrinsic', 'intrinsic']:
1945
2317
  ax = ax_lamb if order == 'intrinsic' else ax_lamb[::-1]
1946
- rot = Rotation.from_davenport(ax, order, angles)
1947
- with pytest.warns(UserWarning, match="Gimbal lock"):
1948
- angles_dav = rot.as_davenport(ax, order)
2318
+ rot = Rotation.from_davenport(xp.asarray(ax), order, angles)
2319
+ with eager_warns(rot, UserWarning, match="Gimbal lock"):
2320
+ angles_dav = rot.as_davenport(xp.asarray(ax), order)
1949
2321
  mat_expected = rot.as_matrix()
1950
- mat_estimated = Rotation.from_davenport(ax, order, angles_dav).as_matrix()
1951
- assert_array_almost_equal(mat_expected, mat_estimated)
2322
+ rot_estimated = Rotation.from_davenport(xp.asarray(ax), order, angles_dav)
2323
+ mat_estimated = rot_estimated.as_matrix()
2324
+ xp_assert_close(mat_expected, mat_estimated, atol=1e-12)
1952
2325
 
1953
2326
 
1954
- def test_compare_from_davenport_from_euler():
2327
+ def test_compare_from_davenport_from_euler(xp):
1955
2328
  rnd = np.random.RandomState(0)
1956
2329
  n = 100
1957
2330
  angles = np.empty((n, 3))
@@ -1966,9 +2339,9 @@ def test_compare_from_davenport_from_euler():
1966
2339
  ax = [basis_vec(i) for i in seq]
1967
2340
  if order == 'intrinsic':
1968
2341
  seq = seq.upper()
1969
- eul = Rotation.from_euler(seq, angles)
1970
- dav = Rotation.from_davenport(ax, order, angles)
1971
- assert_allclose(eul.as_quat(canonical=True), dav.as_quat(canonical=True),
2342
+ eul = Rotation.from_euler(seq, xp.asarray(angles))
2343
+ dav = Rotation.from_davenport(xp.asarray(ax), order, xp.asarray(angles))
2344
+ xp_assert_close(eul.as_quat(canonical=True), dav.as_quat(canonical=True),
1972
2345
  rtol=1e-12)
1973
2346
 
1974
2347
  # asymmetric sequences
@@ -1979,12 +2352,12 @@ def test_compare_from_davenport_from_euler():
1979
2352
  ax = [basis_vec(i) for i in seq]
1980
2353
  if order == 'intrinsic':
1981
2354
  seq = seq.upper()
1982
- eul = Rotation.from_euler(seq, angles)
1983
- dav = Rotation.from_davenport(ax, order, angles)
1984
- assert_allclose(eul.as_quat(), dav.as_quat(), rtol=1e-12)
2355
+ eul = Rotation.from_euler(seq, xp.asarray(angles))
2356
+ dav = Rotation.from_davenport(xp.asarray(ax), order, xp.asarray(angles))
2357
+ xp_assert_close(eul.as_quat(), dav.as_quat(), rtol=1e-12)
1985
2358
 
1986
2359
 
1987
- def test_compare_as_davenport_as_euler():
2360
+ def test_compare_as_davenport_as_euler(xp):
1988
2361
  rnd = np.random.RandomState(0)
1989
2362
  n = 100
1990
2363
  angles = np.empty((n, 3))
@@ -1999,10 +2372,10 @@ def test_compare_as_davenport_as_euler():
1999
2372
  ax = [basis_vec(i) for i in seq]
2000
2373
  if order == 'intrinsic':
2001
2374
  seq = seq.upper()
2002
- rot = Rotation.from_euler(seq, angles)
2375
+ rot = Rotation.from_euler(seq, xp.asarray(angles))
2003
2376
  eul = rot.as_euler(seq)
2004
- dav = rot.as_davenport(ax, order)
2005
- assert_allclose(eul, dav, rtol=1e-12)
2377
+ dav = rot.as_davenport(xp.asarray(ax), order)
2378
+ xp_assert_close(eul, dav, rtol=1e-12)
2006
2379
 
2007
2380
  # asymmetric sequences
2008
2381
  angles[:, 1] -= np.pi / 2
@@ -2012,13 +2385,13 @@ def test_compare_as_davenport_as_euler():
2012
2385
  ax = [basis_vec(i) for i in seq]
2013
2386
  if order == 'intrinsic':
2014
2387
  seq = seq.upper()
2015
- rot = Rotation.from_euler(seq, angles)
2388
+ rot = Rotation.from_euler(seq, xp.asarray(angles))
2016
2389
  eul = rot.as_euler(seq)
2017
- dav = rot.as_davenport(ax, order)
2018
- assert_allclose(eul, dav, rtol=1e-12)
2390
+ dav = rot.as_davenport(xp.asarray(ax), order)
2391
+ xp_assert_close(eul, dav, rtol=1e-12)
2019
2392
 
2020
2393
 
2021
- def test_zero_rotation_construction():
2394
+ def test_zero_rotation_construction(xp):
2022
2395
  r = Rotation.random(num=0)
2023
2396
  assert len(r) == 0
2024
2397
 
@@ -2028,68 +2401,69 @@ def test_zero_rotation_construction():
2028
2401
  r_get = Rotation.random(num=3)[[]]
2029
2402
  assert len(r_get) == 0
2030
2403
 
2031
- r_quat = Rotation.from_quat(np.zeros((0, 4)))
2404
+ r_quat = Rotation.from_quat(xp.zeros((0, 4)))
2032
2405
  assert len(r_quat) == 0
2033
2406
 
2034
- r_matrix = Rotation.from_matrix(np.zeros((0, 3, 3)))
2407
+ r_matrix = Rotation.from_matrix(xp.zeros((0, 3, 3)))
2035
2408
  assert len(r_matrix) == 0
2036
2409
 
2037
- r_euler = Rotation.from_euler("xyz", np.zeros((0, 3)))
2410
+ r_euler = Rotation.from_euler("xyz", xp.zeros((0, 3)))
2038
2411
  assert len(r_euler) == 0
2039
2412
 
2040
- r_vec = Rotation.from_rotvec(np.zeros((0, 3)))
2413
+ r_vec = Rotation.from_rotvec(xp.zeros((0, 3)))
2041
2414
  assert len(r_vec) == 0
2042
2415
 
2043
- r_dav = Rotation.from_davenport(np.eye(3), "extrinsic", np.zeros((0, 3)))
2416
+ r_dav = Rotation.from_davenport(xp.eye(3), "extrinsic", xp.zeros((0, 3)))
2044
2417
  assert len(r_dav) == 0
2045
2418
 
2046
- r_mrp = Rotation.from_mrp(np.zeros((0, 3)))
2419
+ r_mrp = Rotation.from_mrp(xp.zeros((0, 3)))
2047
2420
  assert len(r_mrp) == 0
2048
2421
 
2049
2422
 
2050
- def test_zero_rotation_representation():
2051
- r = Rotation.random(num=0)
2423
+ def test_zero_rotation_representation(xp):
2424
+ r = Rotation.from_quat(xp.zeros((0, 4)))
2052
2425
  assert r.as_quat().shape == (0, 4)
2053
2426
  assert r.as_matrix().shape == (0, 3, 3)
2054
2427
  assert r.as_euler("xyz").shape == (0, 3)
2055
2428
  assert r.as_rotvec().shape == (0, 3)
2056
2429
  assert r.as_mrp().shape == (0, 3)
2057
- assert r.as_davenport(np.eye(3), "extrinsic").shape == (0, 3)
2430
+ assert r.as_davenport(xp.eye(3), "extrinsic").shape == (0, 3)
2058
2431
 
2059
2432
 
2060
- def test_zero_rotation_array_rotation():
2061
- r = Rotation.random(num=0)
2433
+ def test_zero_rotation_array_rotation(xp):
2434
+ r = Rotation.from_quat(xp.zeros((0, 4)))
2062
2435
 
2063
- v = np.array([1, 2, 3])
2436
+ v = xp.asarray([1, 2, 3])
2064
2437
  v_rotated = r.apply(v)
2065
2438
  assert v_rotated.shape == (0, 3)
2066
2439
 
2067
- v0 = np.zeros((0, 3))
2440
+ v0 = xp.zeros((0, 3))
2068
2441
  v0_rot = r.apply(v0)
2069
2442
  assert v0_rot.shape == (0, 3)
2070
2443
 
2071
- v2 = np.ones((2, 3))
2444
+ v2 = xp.ones((2, 3))
2072
2445
  with pytest.raises(
2073
2446
  ValueError, match="Expected equal numbers of rotations and vectors"):
2074
2447
  r.apply(v2)
2075
2448
 
2076
2449
 
2077
- def test_zero_rotation_multiplication():
2078
- r = Rotation.random(num=0)
2450
+ def test_zero_rotation_multiplication(xp):
2451
+ r = Rotation.from_quat(xp.zeros((0, 4)))
2079
2452
 
2080
- r_single = Rotation.random()
2453
+ r_single = Rotation.from_quat(xp.asarray([0.0, 0, 0, 1]))
2081
2454
  r_mult_left = r * r_single
2082
2455
  assert len(r_mult_left) == 0
2083
2456
 
2084
2457
  r_mult_right = r_single * r
2085
2458
  assert len(r_mult_right) == 0
2086
2459
 
2087
- r0 = Rotation.random(0)
2460
+ r0 = Rotation.from_quat(xp.zeros((0, 4)))
2088
2461
  r_mult = r * r0
2089
2462
  assert len(r_mult) == 0
2090
2463
 
2091
2464
  msg_rotation_error = "Expected equal number of rotations"
2092
2465
  r2 = Rotation.random(2)
2466
+ r2 = Rotation.from_quat(xp.asarray(r2.as_quat()))
2093
2467
  with pytest.raises(ValueError, match=msg_rotation_error):
2094
2468
  r0 * r2
2095
2469
 
@@ -2097,55 +2471,62 @@ def test_zero_rotation_multiplication():
2097
2471
  r2 * r0
2098
2472
 
2099
2473
 
2100
- def test_zero_rotation_concatentation():
2101
- r = Rotation.random(num=0)
2474
+ def test_zero_rotation_concatentation(xp):
2475
+ r = Rotation.from_quat(xp.zeros((0, 4)))
2102
2476
 
2103
2477
  r0 = Rotation.concatenate([r, r])
2104
2478
  assert len(r0) == 0
2105
2479
 
2106
- r1 = r.concatenate([Rotation.random(), r])
2480
+ r1 = Rotation.from_quat(xp.asarray([0.0, 0, 0, 1]))
2481
+ r1 = r.concatenate([r1, r])
2107
2482
  assert len(r1) == 1
2108
2483
 
2109
- r3 = r.concatenate([Rotation.random(3), r])
2484
+ r3 = Rotation.from_quat(xp.asarray(Rotation.random(3).as_quat()))
2485
+ r3 = r.concatenate([r3, r])
2110
2486
  assert len(r3) == 3
2111
2487
 
2112
- r4 = r.concatenate([r, Rotation.random(4)])
2488
+ r4 = Rotation.from_quat(xp.asarray(Rotation.random(4).as_quat()))
2489
+ r4 = r.concatenate([r, r4])
2490
+ r4 = r.concatenate([r, r4])
2113
2491
  assert len(r4) == 4
2114
2492
 
2115
2493
 
2116
- def test_zero_rotation_power():
2117
- r = Rotation.random(num=0)
2494
+ def test_zero_rotation_power(xp):
2495
+ r = Rotation.from_quat(xp.zeros((0, 4)))
2118
2496
  for pp in [-1.5, -1, 0, 1, 1.5]:
2119
2497
  pow0 = r**pp
2120
2498
  assert len(pow0) == 0
2121
2499
 
2122
2500
 
2123
- def test_zero_rotation_inverse():
2124
- r = Rotation.random(num=0)
2501
+ def test_zero_rotation_inverse(xp):
2502
+ r = Rotation.from_quat(xp.zeros((0, 4)))
2125
2503
  r_inv = r.inv()
2126
2504
  assert len(r_inv) == 0
2127
2505
 
2128
2506
 
2129
- def test_zero_rotation_magnitude():
2130
- r = Rotation.random(num=0)
2507
+ def test_zero_rotation_magnitude(xp):
2508
+ r = Rotation.from_quat(xp.zeros((0, 4)))
2131
2509
  magnitude = r.magnitude()
2132
2510
  assert magnitude.shape == (0,)
2133
2511
 
2134
2512
 
2135
- def test_zero_rotation_mean():
2136
- r = Rotation.random(num=0)
2513
+ def test_zero_rotation_mean(xp):
2514
+ r = Rotation.from_quat(xp.zeros((0, 4)))
2137
2515
  with pytest.raises(ValueError, match="Mean of an empty rotation set is undefined."):
2138
2516
  r.mean()
2139
2517
 
2140
2518
 
2141
- def test_zero_rotation_approx_equal():
2142
- r = Rotation.random(0)
2143
- assert r.approx_equal(Rotation.random(0)).shape == (0,)
2144
- assert r.approx_equal(Rotation.random()).shape == (0,)
2145
- assert Rotation.random().approx_equal(r).shape == (0,)
2519
+ def test_zero_rotation_approx_equal(xp):
2520
+ r = Rotation.from_quat(xp.zeros((0, 4)))
2521
+ r0 = Rotation.from_quat(xp.zeros((0, 4)))
2522
+ assert r.approx_equal(r0).shape == (0,)
2523
+ r1 = Rotation.from_quat(xp.asarray([0.0, 0, 0, 1]))
2524
+ assert r.approx_equal(r1).shape == (0,)
2525
+ r2 = Rotation.from_quat(xp.asarray(Rotation.random().as_quat()))
2526
+ assert r2.approx_equal(r).shape == (0,)
2146
2527
 
2147
2528
  approx_msg = "Expected equal number of rotations"
2148
- r3 = Rotation.random(2)
2529
+ r3 = Rotation.from_quat(xp.asarray(Rotation.random(2).as_quat()))
2149
2530
  with pytest.raises(ValueError, match=approx_msg):
2150
2531
  r.approx_equal(r3)
2151
2532
 
@@ -2153,36 +2534,36 @@ def test_zero_rotation_approx_equal():
2153
2534
  r3.approx_equal(r)
2154
2535
 
2155
2536
 
2156
- def test_zero_rotation_get_set():
2157
- r = Rotation.random(0)
2537
+ def test_zero_rotation_get_set(xp):
2538
+ r = Rotation.from_quat(xp.zeros((0, 4)))
2158
2539
 
2159
- r_get = r[[]]
2540
+ r_get = r[xp.asarray([], dtype=xp.bool)]
2160
2541
  assert len(r_get) == 0
2161
2542
 
2162
2543
  r_slice = r[:0]
2163
2544
  assert len(r_slice) == 0
2164
2545
 
2165
2546
  with pytest.raises(IndexError):
2166
- r[[0]]
2547
+ r[xp.asarray([0])]
2167
2548
 
2168
2549
  with pytest.raises(IndexError):
2169
- r[[True]]
2550
+ r[xp.asarray([True])]
2170
2551
 
2171
2552
  with pytest.raises(IndexError):
2172
- r[0] = Rotation.random()
2553
+ r[0] = Rotation.from_quat(xp.asarray([0, 0, 0, 1]))
2173
2554
 
2174
2555
 
2175
- def test_boolean_indexes():
2176
- r = Rotation.random(3)
2556
+ def test_boolean_indexes(xp):
2557
+ r = Rotation.from_quat(xp.asarray(Rotation.random(3).as_quat()))
2177
2558
 
2178
- r0 = r[[False, False, False]]
2559
+ r0 = r[xp.asarray([False, False, False])]
2179
2560
  assert len(r0) == 0
2180
2561
 
2181
- r1 = r[[False, True, False]]
2562
+ r1 = r[xp.asarray([False, True, False])]
2182
2563
  assert len(r1) == 1
2183
2564
 
2184
- r3 = r[[True, True, True]]
2565
+ r3 = r[xp.asarray([True, True, True])]
2185
2566
  assert len(r3) == 3
2186
2567
 
2187
2568
  with pytest.raises(IndexError):
2188
- r[[True, True]]
2569
+ r[xp.asarray([True, True])]