scipy 1.15.3__cp313-cp313t-macosx_14_0_arm64.whl → 1.16.0rc2__cp313-cp313t-macosx_14_0_arm64.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 (627) hide show
  1. scipy/__config__.py +3 -3
  2. scipy/__init__.py +3 -6
  3. scipy/_cyutility.cpython-313t-darwin.so +0 -0
  4. scipy/_lib/_array_api.py +486 -161
  5. scipy/_lib/_array_api_compat_vendor.py +9 -0
  6. scipy/_lib/_bunch.py +4 -0
  7. scipy/_lib/_ccallback_c.cpython-313t-darwin.so +0 -0
  8. scipy/_lib/_docscrape.py +1 -1
  9. scipy/_lib/_elementwise_iterative_method.py +15 -26
  10. scipy/_lib/_sparse.py +41 -0
  11. scipy/_lib/_test_deprecation_call.cpython-313t-darwin.so +0 -0
  12. scipy/_lib/_testutils.py +6 -2
  13. scipy/_lib/_util.py +222 -125
  14. scipy/_lib/array_api_compat/__init__.py +4 -4
  15. scipy/_lib/array_api_compat/_internal.py +19 -6
  16. scipy/_lib/array_api_compat/common/__init__.py +1 -1
  17. scipy/_lib/array_api_compat/common/_aliases.py +365 -193
  18. scipy/_lib/array_api_compat/common/_fft.py +94 -64
  19. scipy/_lib/array_api_compat/common/_helpers.py +413 -180
  20. scipy/_lib/array_api_compat/common/_linalg.py +116 -40
  21. scipy/_lib/array_api_compat/common/_typing.py +179 -10
  22. scipy/_lib/array_api_compat/cupy/__init__.py +1 -4
  23. scipy/_lib/array_api_compat/cupy/_aliases.py +61 -41
  24. scipy/_lib/array_api_compat/cupy/_info.py +16 -6
  25. scipy/_lib/array_api_compat/cupy/_typing.py +24 -39
  26. scipy/_lib/array_api_compat/dask/array/__init__.py +6 -3
  27. scipy/_lib/array_api_compat/dask/array/_aliases.py +267 -108
  28. scipy/_lib/array_api_compat/dask/array/_info.py +105 -34
  29. scipy/_lib/array_api_compat/dask/array/fft.py +5 -8
  30. scipy/_lib/array_api_compat/dask/array/linalg.py +21 -22
  31. scipy/_lib/array_api_compat/numpy/__init__.py +13 -15
  32. scipy/_lib/array_api_compat/numpy/_aliases.py +98 -49
  33. scipy/_lib/array_api_compat/numpy/_info.py +36 -16
  34. scipy/_lib/array_api_compat/numpy/_typing.py +27 -43
  35. scipy/_lib/array_api_compat/numpy/fft.py +11 -5
  36. scipy/_lib/array_api_compat/numpy/linalg.py +75 -22
  37. scipy/_lib/array_api_compat/torch/__init__.py +3 -5
  38. scipy/_lib/array_api_compat/torch/_aliases.py +262 -159
  39. scipy/_lib/array_api_compat/torch/_info.py +27 -16
  40. scipy/_lib/array_api_compat/torch/_typing.py +3 -0
  41. scipy/_lib/array_api_compat/torch/fft.py +17 -18
  42. scipy/_lib/array_api_compat/torch/linalg.py +16 -16
  43. scipy/_lib/array_api_extra/__init__.py +26 -3
  44. scipy/_lib/array_api_extra/_delegation.py +171 -0
  45. scipy/_lib/array_api_extra/_lib/__init__.py +1 -0
  46. scipy/_lib/array_api_extra/_lib/_at.py +463 -0
  47. scipy/_lib/array_api_extra/_lib/_backends.py +46 -0
  48. scipy/_lib/array_api_extra/_lib/_funcs.py +937 -0
  49. scipy/_lib/array_api_extra/_lib/_lazy.py +357 -0
  50. scipy/_lib/array_api_extra/_lib/_testing.py +278 -0
  51. scipy/_lib/array_api_extra/_lib/_utils/__init__.py +1 -0
  52. scipy/_lib/array_api_extra/_lib/_utils/_compat.py +74 -0
  53. scipy/_lib/array_api_extra/_lib/_utils/_compat.pyi +45 -0
  54. scipy/_lib/array_api_extra/_lib/_utils/_helpers.py +559 -0
  55. scipy/_lib/array_api_extra/_lib/_utils/_typing.py +10 -0
  56. scipy/_lib/array_api_extra/_lib/_utils/_typing.pyi +105 -0
  57. scipy/_lib/array_api_extra/testing.py +359 -0
  58. scipy/_lib/decorator.py +2 -2
  59. scipy/_lib/doccer.py +1 -7
  60. scipy/_lib/messagestream.cpython-313t-darwin.so +0 -0
  61. scipy/_lib/pyprima/__init__.py +212 -0
  62. scipy/_lib/pyprima/cobyla/__init__.py +0 -0
  63. scipy/_lib/pyprima/cobyla/cobyla.py +559 -0
  64. scipy/_lib/pyprima/cobyla/cobylb.py +714 -0
  65. scipy/_lib/pyprima/cobyla/geometry.py +226 -0
  66. scipy/_lib/pyprima/cobyla/initialize.py +215 -0
  67. scipy/_lib/pyprima/cobyla/trustregion.py +492 -0
  68. scipy/_lib/pyprima/cobyla/update.py +289 -0
  69. scipy/_lib/pyprima/common/__init__.py +0 -0
  70. scipy/_lib/pyprima/common/_bounds.py +34 -0
  71. scipy/_lib/pyprima/common/_linear_constraints.py +46 -0
  72. scipy/_lib/pyprima/common/_nonlinear_constraints.py +54 -0
  73. scipy/_lib/pyprima/common/_project.py +173 -0
  74. scipy/_lib/pyprima/common/checkbreak.py +93 -0
  75. scipy/_lib/pyprima/common/consts.py +47 -0
  76. scipy/_lib/pyprima/common/evaluate.py +99 -0
  77. scipy/_lib/pyprima/common/history.py +38 -0
  78. scipy/_lib/pyprima/common/infos.py +30 -0
  79. scipy/_lib/pyprima/common/linalg.py +435 -0
  80. scipy/_lib/pyprima/common/message.py +290 -0
  81. scipy/_lib/pyprima/common/powalg.py +131 -0
  82. scipy/_lib/pyprima/common/preproc.py +277 -0
  83. scipy/_lib/pyprima/common/present.py +5 -0
  84. scipy/_lib/pyprima/common/ratio.py +54 -0
  85. scipy/_lib/pyprima/common/redrho.py +47 -0
  86. scipy/_lib/pyprima/common/selectx.py +296 -0
  87. scipy/_lib/tests/test__util.py +105 -121
  88. scipy/_lib/tests/test_array_api.py +166 -35
  89. scipy/_lib/tests/test_bunch.py +7 -0
  90. scipy/_lib/tests/test_ccallback.py +2 -10
  91. scipy/_lib/tests/test_public_api.py +13 -0
  92. scipy/cluster/_hierarchy.cpython-313t-darwin.so +0 -0
  93. scipy/cluster/_optimal_leaf_ordering.cpython-313t-darwin.so +0 -0
  94. scipy/cluster/_vq.cpython-313t-darwin.so +0 -0
  95. scipy/cluster/hierarchy.py +393 -223
  96. scipy/cluster/tests/test_hierarchy.py +273 -335
  97. scipy/cluster/tests/test_vq.py +45 -61
  98. scipy/cluster/vq.py +39 -35
  99. scipy/conftest.py +263 -157
  100. scipy/constants/_constants.py +4 -1
  101. scipy/constants/tests/test_codata.py +2 -2
  102. scipy/constants/tests/test_constants.py +11 -18
  103. scipy/datasets/_download_all.py +15 -1
  104. scipy/datasets/_fetchers.py +7 -1
  105. scipy/datasets/_utils.py +1 -1
  106. scipy/differentiate/_differentiate.py +25 -25
  107. scipy/differentiate/tests/test_differentiate.py +24 -25
  108. scipy/fft/_basic.py +20 -0
  109. scipy/fft/_helper.py +3 -34
  110. scipy/fft/_pocketfft/helper.py +29 -1
  111. scipy/fft/_pocketfft/tests/test_basic.py +2 -4
  112. scipy/fft/_pocketfft/tests/test_real_transforms.py +4 -4
  113. scipy/fft/_realtransforms.py +13 -0
  114. scipy/fft/tests/test_basic.py +27 -25
  115. scipy/fft/tests/test_fftlog.py +16 -7
  116. scipy/fft/tests/test_helper.py +18 -34
  117. scipy/fft/tests/test_real_transforms.py +8 -10
  118. scipy/fftpack/convolve.cpython-313t-darwin.so +0 -0
  119. scipy/fftpack/tests/test_basic.py +2 -4
  120. scipy/fftpack/tests/test_real_transforms.py +8 -9
  121. scipy/integrate/_bvp.py +9 -3
  122. scipy/integrate/_cubature.py +3 -2
  123. scipy/integrate/_dop.cpython-313t-darwin.so +0 -0
  124. scipy/integrate/_lsoda.cpython-313t-darwin.so +0 -0
  125. scipy/integrate/_ode.py +9 -2
  126. scipy/integrate/_odepack.cpython-313t-darwin.so +0 -0
  127. scipy/integrate/_quad_vec.py +21 -29
  128. scipy/integrate/_quadpack.cpython-313t-darwin.so +0 -0
  129. scipy/integrate/_quadpack_py.py +11 -7
  130. scipy/integrate/_quadrature.py +3 -3
  131. scipy/integrate/_rules/_base.py +2 -2
  132. scipy/integrate/_tanhsinh.py +48 -47
  133. scipy/integrate/_test_odeint_banded.cpython-313t-darwin.so +0 -0
  134. scipy/integrate/_vode.cpython-313t-darwin.so +0 -0
  135. scipy/integrate/tests/test__quad_vec.py +0 -6
  136. scipy/integrate/tests/test_banded_ode_solvers.py +85 -0
  137. scipy/integrate/tests/test_cubature.py +21 -35
  138. scipy/integrate/tests/test_quadrature.py +6 -8
  139. scipy/integrate/tests/test_tanhsinh.py +56 -48
  140. scipy/interpolate/__init__.py +70 -58
  141. scipy/interpolate/_bary_rational.py +22 -22
  142. scipy/interpolate/_bsplines.py +119 -66
  143. scipy/interpolate/_cubic.py +65 -50
  144. scipy/interpolate/_dfitpack.cpython-313t-darwin.so +0 -0
  145. scipy/interpolate/_dierckx.cpython-313t-darwin.so +0 -0
  146. scipy/interpolate/_fitpack.cpython-313t-darwin.so +0 -0
  147. scipy/interpolate/_fitpack2.py +9 -6
  148. scipy/interpolate/_fitpack_impl.py +32 -26
  149. scipy/interpolate/_fitpack_repro.py +23 -19
  150. scipy/interpolate/_interpnd.cpython-313t-darwin.so +0 -0
  151. scipy/interpolate/_interpolate.py +30 -12
  152. scipy/interpolate/_ndbspline.py +13 -18
  153. scipy/interpolate/_ndgriddata.py +5 -8
  154. scipy/interpolate/_polyint.py +95 -31
  155. scipy/interpolate/_ppoly.cpython-313t-darwin.so +0 -0
  156. scipy/interpolate/_rbf.py +2 -2
  157. scipy/interpolate/_rbfinterp.py +1 -1
  158. scipy/interpolate/_rbfinterp_pythran.cpython-313t-darwin.so +0 -0
  159. scipy/interpolate/_rgi.py +31 -26
  160. scipy/interpolate/_rgi_cython.cpython-313t-darwin.so +0 -0
  161. scipy/interpolate/dfitpack.py +0 -20
  162. scipy/interpolate/interpnd.py +1 -2
  163. scipy/interpolate/tests/test_bary_rational.py +2 -2
  164. scipy/interpolate/tests/test_bsplines.py +97 -1
  165. scipy/interpolate/tests/test_fitpack2.py +39 -1
  166. scipy/interpolate/tests/test_interpnd.py +32 -20
  167. scipy/interpolate/tests/test_interpolate.py +48 -4
  168. scipy/interpolate/tests/test_rgi.py +2 -1
  169. scipy/io/_fast_matrix_market/__init__.py +2 -0
  170. scipy/io/_harwell_boeing/_fortran_format_parser.py +19 -16
  171. scipy/io/_harwell_boeing/hb.py +7 -11
  172. scipy/io/_idl.py +5 -7
  173. scipy/io/_netcdf.py +15 -5
  174. scipy/io/_test_fortran.cpython-313t-darwin.so +0 -0
  175. scipy/io/arff/tests/test_arffread.py +3 -3
  176. scipy/io/matlab/__init__.py +5 -3
  177. scipy/io/matlab/_mio.py +4 -1
  178. scipy/io/matlab/_mio5.py +19 -13
  179. scipy/io/matlab/_mio5_utils.cpython-313t-darwin.so +0 -0
  180. scipy/io/matlab/_mio_utils.cpython-313t-darwin.so +0 -0
  181. scipy/io/matlab/_miobase.py +4 -1
  182. scipy/io/matlab/_streams.cpython-313t-darwin.so +0 -0
  183. scipy/io/matlab/tests/test_mio.py +46 -18
  184. scipy/io/matlab/tests/test_mio_funcs.py +1 -1
  185. scipy/io/tests/test_mmio.py +7 -1
  186. scipy/io/tests/test_wavfile.py +41 -0
  187. scipy/io/wavfile.py +57 -10
  188. scipy/linalg/_basic.py +113 -86
  189. scipy/linalg/_cythonized_array_utils.cpython-313t-darwin.so +0 -0
  190. scipy/linalg/_decomp.py +22 -9
  191. scipy/linalg/_decomp_cholesky.py +28 -13
  192. scipy/linalg/_decomp_cossin.py +45 -30
  193. scipy/linalg/_decomp_interpolative.cpython-313t-darwin.so +0 -0
  194. scipy/linalg/_decomp_ldl.py +4 -1
  195. scipy/linalg/_decomp_lu.py +18 -6
  196. scipy/linalg/_decomp_lu_cython.cpython-313t-darwin.so +0 -0
  197. scipy/linalg/_decomp_polar.py +2 -0
  198. scipy/linalg/_decomp_qr.py +6 -2
  199. scipy/linalg/_decomp_qz.py +3 -0
  200. scipy/linalg/_decomp_schur.py +3 -1
  201. scipy/linalg/_decomp_svd.py +13 -2
  202. scipy/linalg/_decomp_update.cpython-313t-darwin.so +0 -0
  203. scipy/linalg/_expm_frechet.py +4 -0
  204. scipy/linalg/_fblas.cpython-313t-darwin.so +0 -0
  205. scipy/linalg/_flapack.cpython-313t-darwin.so +0 -0
  206. scipy/linalg/_linalg_pythran.cpython-313t-darwin.so +0 -0
  207. scipy/linalg/_matfuncs.py +187 -4
  208. scipy/linalg/_matfuncs_expm.cpython-313t-darwin.so +0 -0
  209. scipy/linalg/_matfuncs_schur_sqrtm.cpython-313t-darwin.so +0 -0
  210. scipy/linalg/_matfuncs_sqrtm.py +1 -99
  211. scipy/linalg/_matfuncs_sqrtm_triu.cpython-313t-darwin.so +0 -0
  212. scipy/linalg/_procrustes.py +2 -0
  213. scipy/linalg/_sketches.py +17 -6
  214. scipy/linalg/_solve_toeplitz.cpython-313t-darwin.so +0 -0
  215. scipy/linalg/_solvers.py +7 -2
  216. scipy/linalg/_special_matrices.py +26 -36
  217. scipy/linalg/cython_blas.cpython-313t-darwin.so +0 -0
  218. scipy/linalg/cython_lapack.cpython-313t-darwin.so +0 -0
  219. scipy/linalg/lapack.py +22 -2
  220. scipy/linalg/tests/_cython_examples/meson.build +7 -0
  221. scipy/linalg/tests/test_basic.py +31 -16
  222. scipy/linalg/tests/test_batch.py +588 -0
  223. scipy/linalg/tests/test_cythonized_array_utils.py +0 -2
  224. scipy/linalg/tests/test_decomp.py +40 -3
  225. scipy/linalg/tests/test_decomp_cossin.py +14 -0
  226. scipy/linalg/tests/test_decomp_ldl.py +1 -1
  227. scipy/linalg/tests/test_lapack.py +115 -7
  228. scipy/linalg/tests/test_matfuncs.py +157 -102
  229. scipy/linalg/tests/test_procrustes.py +0 -7
  230. scipy/linalg/tests/test_solve_toeplitz.py +1 -1
  231. scipy/linalg/tests/test_special_matrices.py +1 -5
  232. scipy/ndimage/__init__.py +1 -0
  233. scipy/ndimage/_cytest.cpython-313t-darwin.so +0 -0
  234. scipy/ndimage/_delegators.py +8 -2
  235. scipy/ndimage/_filters.py +453 -5
  236. scipy/ndimage/_interpolation.py +36 -6
  237. scipy/ndimage/_measurements.py +4 -2
  238. scipy/ndimage/_morphology.py +5 -0
  239. scipy/ndimage/_nd_image.cpython-313t-darwin.so +0 -0
  240. scipy/ndimage/_ni_docstrings.py +5 -1
  241. scipy/ndimage/_ni_label.cpython-313t-darwin.so +0 -0
  242. scipy/ndimage/_ni_support.py +1 -5
  243. scipy/ndimage/_rank_filter_1d.cpython-313t-darwin.so +0 -0
  244. scipy/ndimage/_support_alternative_backends.py +18 -6
  245. scipy/ndimage/tests/test_filters.py +370 -259
  246. scipy/ndimage/tests/test_fourier.py +7 -9
  247. scipy/ndimage/tests/test_interpolation.py +68 -61
  248. scipy/ndimage/tests/test_measurements.py +18 -35
  249. scipy/ndimage/tests/test_morphology.py +143 -131
  250. scipy/ndimage/tests/test_splines.py +1 -3
  251. scipy/odr/__odrpack.cpython-313t-darwin.so +0 -0
  252. scipy/optimize/_basinhopping.py +13 -7
  253. scipy/optimize/_bglu_dense.cpython-313t-darwin.so +0 -0
  254. scipy/optimize/_bracket.py +17 -24
  255. scipy/optimize/_chandrupatla.py +9 -10
  256. scipy/optimize/_cobyla_py.py +104 -123
  257. scipy/optimize/_constraints.py +14 -10
  258. scipy/optimize/_differentiable_functions.py +371 -230
  259. scipy/optimize/_differentialevolution.py +4 -3
  260. scipy/optimize/_direct.cpython-313t-darwin.so +0 -0
  261. scipy/optimize/_dual_annealing.py +1 -1
  262. scipy/optimize/_elementwise.py +1 -4
  263. scipy/optimize/_group_columns.cpython-313t-darwin.so +0 -0
  264. scipy/optimize/_lbfgsb.cpython-313t-darwin.so +0 -0
  265. scipy/optimize/_lbfgsb_py.py +57 -16
  266. scipy/optimize/_linprog_doc.py +2 -2
  267. scipy/optimize/_linprog_highs.py +2 -2
  268. scipy/optimize/_linprog_ip.py +25 -10
  269. scipy/optimize/_linprog_util.py +14 -16
  270. scipy/optimize/_lsap.cpython-313t-darwin.so +0 -0
  271. scipy/optimize/_lsq/common.py +3 -3
  272. scipy/optimize/_lsq/dogbox.py +16 -2
  273. scipy/optimize/_lsq/givens_elimination.cpython-313t-darwin.so +0 -0
  274. scipy/optimize/_lsq/least_squares.py +198 -126
  275. scipy/optimize/_lsq/lsq_linear.py +6 -6
  276. scipy/optimize/_lsq/trf.py +35 -8
  277. scipy/optimize/_milp.py +3 -1
  278. scipy/optimize/_minimize.py +105 -36
  279. scipy/optimize/_minpack.cpython-313t-darwin.so +0 -0
  280. scipy/optimize/_minpack_py.py +21 -14
  281. scipy/optimize/_moduleTNC.cpython-313t-darwin.so +0 -0
  282. scipy/optimize/_nnls.py +20 -21
  283. scipy/optimize/_nonlin.py +34 -3
  284. scipy/optimize/_numdiff.py +288 -110
  285. scipy/optimize/_optimize.py +86 -48
  286. scipy/optimize/_pava_pybind.cpython-313t-darwin.so +0 -0
  287. scipy/optimize/_remove_redundancy.py +5 -5
  288. scipy/optimize/_root_scalar.py +1 -1
  289. scipy/optimize/_shgo.py +6 -0
  290. scipy/optimize/_shgo_lib/_complex.py +1 -1
  291. scipy/optimize/_slsqp_py.py +216 -124
  292. scipy/optimize/_slsqplib.cpython-313t-darwin.so +0 -0
  293. scipy/optimize/_spectral.py +1 -1
  294. scipy/optimize/_tnc.py +8 -1
  295. scipy/optimize/_trlib/_trlib.cpython-313t-darwin.so +0 -0
  296. scipy/optimize/_trustregion.py +20 -6
  297. scipy/optimize/_trustregion_constr/canonical_constraint.py +7 -7
  298. scipy/optimize/_trustregion_constr/equality_constrained_sqp.py +1 -1
  299. scipy/optimize/_trustregion_constr/minimize_trustregion_constr.py +11 -3
  300. scipy/optimize/_trustregion_constr/projections.py +12 -8
  301. scipy/optimize/_trustregion_constr/qp_subproblem.py +9 -9
  302. scipy/optimize/_trustregion_constr/tests/test_projections.py +7 -7
  303. scipy/optimize/_trustregion_constr/tests/test_qp_subproblem.py +77 -77
  304. scipy/optimize/_trustregion_constr/tr_interior_point.py +5 -5
  305. scipy/optimize/_trustregion_exact.py +0 -1
  306. scipy/optimize/_zeros.cpython-313t-darwin.so +0 -0
  307. scipy/optimize/_zeros_py.py +97 -17
  308. scipy/optimize/cython_optimize/_zeros.cpython-313t-darwin.so +0 -0
  309. scipy/optimize/slsqp.py +0 -1
  310. scipy/optimize/tests/test__basinhopping.py +1 -1
  311. scipy/optimize/tests/test__differential_evolution.py +4 -4
  312. scipy/optimize/tests/test__linprog_clean_inputs.py +5 -3
  313. scipy/optimize/tests/test__numdiff.py +66 -22
  314. scipy/optimize/tests/test__remove_redundancy.py +2 -2
  315. scipy/optimize/tests/test__shgo.py +9 -1
  316. scipy/optimize/tests/test_bracket.py +36 -46
  317. scipy/optimize/tests/test_chandrupatla.py +133 -135
  318. scipy/optimize/tests/test_cobyla.py +74 -45
  319. scipy/optimize/tests/test_constraints.py +1 -1
  320. scipy/optimize/tests/test_differentiable_functions.py +226 -6
  321. scipy/optimize/tests/test_lbfgsb_hessinv.py +22 -0
  322. scipy/optimize/tests/test_least_squares.py +125 -13
  323. scipy/optimize/tests/test_linear_assignment.py +3 -3
  324. scipy/optimize/tests/test_linprog.py +3 -3
  325. scipy/optimize/tests/test_lsq_linear.py +6 -6
  326. scipy/optimize/tests/test_minimize_constrained.py +2 -2
  327. scipy/optimize/tests/test_minpack.py +4 -4
  328. scipy/optimize/tests/test_nnls.py +43 -3
  329. scipy/optimize/tests/test_nonlin.py +36 -0
  330. scipy/optimize/tests/test_optimize.py +95 -17
  331. scipy/optimize/tests/test_slsqp.py +36 -4
  332. scipy/optimize/tests/test_zeros.py +34 -1
  333. scipy/signal/__init__.py +12 -23
  334. scipy/signal/_delegators.py +568 -0
  335. scipy/signal/_filter_design.py +459 -241
  336. scipy/signal/_fir_filter_design.py +262 -90
  337. scipy/signal/_lti_conversion.py +3 -2
  338. scipy/signal/_ltisys.py +118 -91
  339. scipy/signal/_max_len_seq_inner.cpython-313t-darwin.so +0 -0
  340. scipy/signal/_peak_finding_utils.cpython-313t-darwin.so +0 -0
  341. scipy/signal/_polyutils.py +172 -0
  342. scipy/signal/_short_time_fft.py +519 -70
  343. scipy/signal/_signal_api.py +30 -0
  344. scipy/signal/_signaltools.py +719 -399
  345. scipy/signal/_sigtools.cpython-313t-darwin.so +0 -0
  346. scipy/signal/_sosfilt.cpython-313t-darwin.so +0 -0
  347. scipy/signal/_spectral_py.py +230 -50
  348. scipy/signal/_spline.cpython-313t-darwin.so +0 -0
  349. scipy/signal/_spline_filters.py +108 -68
  350. scipy/signal/_support_alternative_backends.py +73 -0
  351. scipy/signal/_upfirdn.py +4 -1
  352. scipy/signal/_upfirdn_apply.cpython-313t-darwin.so +0 -0
  353. scipy/signal/_waveforms.py +2 -11
  354. scipy/signal/_wavelets.py +1 -1
  355. scipy/signal/fir_filter_design.py +1 -0
  356. scipy/signal/spline.py +4 -11
  357. scipy/signal/tests/_scipy_spectral_test_shim.py +2 -171
  358. scipy/signal/tests/test_bsplines.py +114 -79
  359. scipy/signal/tests/test_cont2discrete.py +9 -2
  360. scipy/signal/tests/test_filter_design.py +721 -481
  361. scipy/signal/tests/test_fir_filter_design.py +332 -140
  362. scipy/signal/tests/test_savitzky_golay.py +4 -3
  363. scipy/signal/tests/test_short_time_fft.py +221 -3
  364. scipy/signal/tests/test_signaltools.py +2144 -1348
  365. scipy/signal/tests/test_spectral.py +50 -6
  366. scipy/signal/tests/test_splines.py +161 -96
  367. scipy/signal/tests/test_upfirdn.py +84 -50
  368. scipy/signal/tests/test_waveforms.py +20 -0
  369. scipy/signal/tests/test_windows.py +607 -466
  370. scipy/signal/windows/_windows.py +287 -148
  371. scipy/sparse/__init__.py +23 -4
  372. scipy/sparse/_base.py +270 -108
  373. scipy/sparse/_bsr.py +7 -4
  374. scipy/sparse/_compressed.py +59 -231
  375. scipy/sparse/_construct.py +90 -38
  376. scipy/sparse/_coo.py +115 -181
  377. scipy/sparse/_csc.py +4 -4
  378. scipy/sparse/_csparsetools.cpython-313t-darwin.so +0 -0
  379. scipy/sparse/_csr.py +2 -2
  380. scipy/sparse/_data.py +48 -48
  381. scipy/sparse/_dia.py +105 -18
  382. scipy/sparse/_dok.py +0 -23
  383. scipy/sparse/_index.py +4 -4
  384. scipy/sparse/_matrix.py +23 -0
  385. scipy/sparse/_sparsetools.cpython-313t-darwin.so +0 -0
  386. scipy/sparse/_sputils.py +37 -22
  387. scipy/sparse/base.py +0 -9
  388. scipy/sparse/bsr.py +0 -14
  389. scipy/sparse/compressed.py +0 -23
  390. scipy/sparse/construct.py +0 -6
  391. scipy/sparse/coo.py +0 -14
  392. scipy/sparse/csc.py +0 -3
  393. scipy/sparse/csgraph/_flow.cpython-313t-darwin.so +0 -0
  394. scipy/sparse/csgraph/_matching.cpython-313t-darwin.so +0 -0
  395. scipy/sparse/csgraph/_min_spanning_tree.cpython-313t-darwin.so +0 -0
  396. scipy/sparse/csgraph/_reordering.cpython-313t-darwin.so +0 -0
  397. scipy/sparse/csgraph/_shortest_path.cpython-313t-darwin.so +0 -0
  398. scipy/sparse/csgraph/_tools.cpython-313t-darwin.so +0 -0
  399. scipy/sparse/csgraph/_traversal.cpython-313t-darwin.so +0 -0
  400. scipy/sparse/csgraph/tests/test_matching.py +14 -2
  401. scipy/sparse/csgraph/tests/test_pydata_sparse.py +4 -1
  402. scipy/sparse/csgraph/tests/test_shortest_path.py +83 -27
  403. scipy/sparse/csr.py +0 -5
  404. scipy/sparse/data.py +1 -6
  405. scipy/sparse/dia.py +0 -7
  406. scipy/sparse/dok.py +0 -10
  407. scipy/sparse/linalg/_dsolve/_superlu.cpython-313t-darwin.so +0 -0
  408. scipy/sparse/linalg/_dsolve/linsolve.py +9 -0
  409. scipy/sparse/linalg/_dsolve/tests/test_linsolve.py +35 -28
  410. scipy/sparse/linalg/_eigen/arpack/_arpack.cpython-313t-darwin.so +0 -0
  411. scipy/sparse/linalg/_eigen/arpack/arpack.py +23 -17
  412. scipy/sparse/linalg/_eigen/lobpcg/lobpcg.py +6 -6
  413. scipy/sparse/linalg/_interface.py +17 -18
  414. scipy/sparse/linalg/_isolve/_gcrotmk.py +4 -4
  415. scipy/sparse/linalg/_isolve/iterative.py +51 -45
  416. scipy/sparse/linalg/_isolve/lgmres.py +6 -6
  417. scipy/sparse/linalg/_isolve/minres.py +5 -5
  418. scipy/sparse/linalg/_isolve/tfqmr.py +7 -7
  419. scipy/sparse/linalg/_isolve/utils.py +2 -8
  420. scipy/sparse/linalg/_matfuncs.py +1 -1
  421. scipy/sparse/linalg/_norm.py +1 -1
  422. scipy/sparse/linalg/_propack/_cpropack.cpython-313t-darwin.so +0 -0
  423. scipy/sparse/linalg/_propack/_dpropack.cpython-313t-darwin.so +0 -0
  424. scipy/sparse/linalg/_propack/_spropack.cpython-313t-darwin.so +0 -0
  425. scipy/sparse/linalg/_propack/_zpropack.cpython-313t-darwin.so +0 -0
  426. scipy/sparse/linalg/_special_sparse_arrays.py +39 -38
  427. scipy/sparse/linalg/tests/test_pydata_sparse.py +14 -0
  428. scipy/sparse/tests/test_arithmetic1d.py +5 -2
  429. scipy/sparse/tests/test_base.py +214 -42
  430. scipy/sparse/tests/test_common1d.py +7 -7
  431. scipy/sparse/tests/test_construct.py +1 -1
  432. scipy/sparse/tests/test_coo.py +272 -4
  433. scipy/sparse/tests/test_sparsetools.py +5 -0
  434. scipy/sparse/tests/test_sputils.py +36 -7
  435. scipy/spatial/_ckdtree.cpython-313t-darwin.so +0 -0
  436. scipy/spatial/_distance_pybind.cpython-313t-darwin.so +0 -0
  437. scipy/spatial/_distance_wrap.cpython-313t-darwin.so +0 -0
  438. scipy/spatial/_hausdorff.cpython-313t-darwin.so +0 -0
  439. scipy/spatial/_qhull.cpython-313t-darwin.so +0 -0
  440. scipy/spatial/_voronoi.cpython-313t-darwin.so +0 -0
  441. scipy/spatial/distance.py +49 -42
  442. scipy/spatial/tests/test_distance.py +15 -1
  443. scipy/spatial/tests/test_kdtree.py +1 -0
  444. scipy/spatial/tests/test_qhull.py +7 -2
  445. scipy/spatial/transform/__init__.py +5 -3
  446. scipy/spatial/transform/_rigid_transform.cpython-313t-darwin.so +0 -0
  447. scipy/spatial/transform/_rotation.cpython-313t-darwin.so +0 -0
  448. scipy/spatial/transform/tests/test_rigid_transform.py +1221 -0
  449. scipy/spatial/transform/tests/test_rotation.py +1213 -832
  450. scipy/spatial/transform/tests/test_rotation_groups.py +3 -3
  451. scipy/spatial/transform/tests/test_rotation_spline.py +29 -8
  452. scipy/special/__init__.py +1 -47
  453. scipy/special/_add_newdocs.py +34 -772
  454. scipy/special/_basic.py +22 -25
  455. scipy/special/_comb.cpython-313t-darwin.so +0 -0
  456. scipy/special/_ellip_harm_2.cpython-313t-darwin.so +0 -0
  457. scipy/special/_gufuncs.cpython-313t-darwin.so +0 -0
  458. scipy/special/_logsumexp.py +67 -58
  459. scipy/special/_orthogonal.pyi +1 -1
  460. scipy/special/_specfun.cpython-313t-darwin.so +0 -0
  461. scipy/special/_special_ufuncs.cpython-313t-darwin.so +0 -0
  462. scipy/special/_spherical_bessel.py +4 -4
  463. scipy/special/_support_alternative_backends.py +212 -119
  464. scipy/special/_test_internal.cpython-313t-darwin.so +0 -0
  465. scipy/special/_testutils.py +4 -4
  466. scipy/special/_ufuncs.cpython-313t-darwin.so +0 -0
  467. scipy/special/_ufuncs.pyi +1 -0
  468. scipy/special/_ufuncs.pyx +215 -1400
  469. scipy/special/_ufuncs_cxx.cpython-313t-darwin.so +0 -0
  470. scipy/special/_ufuncs_cxx.pxd +2 -15
  471. scipy/special/_ufuncs_cxx.pyx +5 -44
  472. scipy/special/_ufuncs_cxx_defs.h +2 -16
  473. scipy/special/_ufuncs_defs.h +0 -8
  474. scipy/special/cython_special.cpython-313t-darwin.so +0 -0
  475. scipy/special/cython_special.pxd +1 -1
  476. scipy/special/tests/_cython_examples/meson.build +10 -1
  477. scipy/special/tests/test_basic.py +153 -20
  478. scipy/special/tests/test_boost_ufuncs.py +3 -0
  479. scipy/special/tests/test_cdflib.py +35 -11
  480. scipy/special/tests/test_gammainc.py +16 -0
  481. scipy/special/tests/test_hyp2f1.py +2 -2
  482. scipy/special/tests/test_log1mexp.py +85 -0
  483. scipy/special/tests/test_logsumexp.py +206 -64
  484. scipy/special/tests/test_mpmath.py +1 -0
  485. scipy/special/tests/test_nan_inputs.py +1 -1
  486. scipy/special/tests/test_orthogonal.py +17 -18
  487. scipy/special/tests/test_sf_error.py +3 -2
  488. scipy/special/tests/test_sph_harm.py +6 -7
  489. scipy/special/tests/test_support_alternative_backends.py +211 -76
  490. scipy/stats/__init__.py +4 -1
  491. scipy/stats/_ansari_swilk_statistics.cpython-313t-darwin.so +0 -0
  492. scipy/stats/_axis_nan_policy.py +5 -12
  493. scipy/stats/_biasedurn.cpython-313t-darwin.so +0 -0
  494. scipy/stats/_continued_fraction.py +387 -0
  495. scipy/stats/_continuous_distns.py +277 -310
  496. scipy/stats/_correlation.py +1 -1
  497. scipy/stats/_covariance.py +6 -3
  498. scipy/stats/_discrete_distns.py +39 -32
  499. scipy/stats/_distn_infrastructure.py +39 -12
  500. scipy/stats/_distribution_infrastructure.py +900 -238
  501. scipy/stats/_entropy.py +9 -10
  502. scipy/{_lib → stats}/_finite_differences.py +1 -1
  503. scipy/stats/_hypotests.py +83 -50
  504. scipy/stats/_kde.py +53 -49
  505. scipy/stats/_ksstats.py +1 -1
  506. scipy/stats/_levy_stable/__init__.py +7 -15
  507. scipy/stats/_levy_stable/levyst.cpython-313t-darwin.so +0 -0
  508. scipy/stats/_morestats.py +118 -73
  509. scipy/stats/_mstats_basic.py +13 -17
  510. scipy/stats/_mstats_extras.py +8 -8
  511. scipy/stats/_multivariate.py +89 -113
  512. scipy/stats/_new_distributions.py +97 -20
  513. scipy/stats/_page_trend_test.py +12 -5
  514. scipy/stats/_probability_distribution.py +265 -43
  515. scipy/stats/_qmc.py +14 -9
  516. scipy/stats/_qmc_cy.cpython-313t-darwin.so +0 -0
  517. scipy/stats/_qmvnt.py +16 -95
  518. scipy/stats/_qmvnt_cy.cpython-313t-darwin.so +0 -0
  519. scipy/stats/_quantile.py +335 -0
  520. scipy/stats/_rcont/rcont.cpython-313t-darwin.so +0 -0
  521. scipy/stats/_resampling.py +4 -29
  522. scipy/stats/_sampling.py +1 -1
  523. scipy/stats/_sobol.cpython-313t-darwin.so +0 -0
  524. scipy/stats/_stats.cpython-313t-darwin.so +0 -0
  525. scipy/stats/_stats_mstats_common.py +21 -2
  526. scipy/stats/_stats_py.py +550 -476
  527. scipy/stats/_stats_pythran.cpython-313t-darwin.so +0 -0
  528. scipy/stats/_unuran/unuran_wrapper.cpython-313t-darwin.so +0 -0
  529. scipy/stats/_unuran/unuran_wrapper.pyi +2 -1
  530. scipy/stats/_variation.py +6 -8
  531. scipy/stats/_wilcoxon.py +13 -7
  532. scipy/stats/tests/common_tests.py +6 -4
  533. scipy/stats/tests/test_axis_nan_policy.py +62 -24
  534. scipy/stats/tests/test_continued_fraction.py +173 -0
  535. scipy/stats/tests/test_continuous.py +379 -60
  536. scipy/stats/tests/test_continuous_basic.py +18 -12
  537. scipy/stats/tests/test_discrete_basic.py +14 -8
  538. scipy/stats/tests/test_discrete_distns.py +16 -16
  539. scipy/stats/tests/test_distributions.py +95 -75
  540. scipy/stats/tests/test_entropy.py +40 -48
  541. scipy/stats/tests/test_fit.py +4 -3
  542. scipy/stats/tests/test_hypotests.py +153 -24
  543. scipy/stats/tests/test_kdeoth.py +109 -41
  544. scipy/stats/tests/test_marray.py +289 -0
  545. scipy/stats/tests/test_morestats.py +79 -47
  546. scipy/stats/tests/test_mstats_basic.py +3 -3
  547. scipy/stats/tests/test_multivariate.py +434 -83
  548. scipy/stats/tests/test_qmc.py +13 -10
  549. scipy/stats/tests/test_quantile.py +199 -0
  550. scipy/stats/tests/test_rank.py +119 -112
  551. scipy/stats/tests/test_resampling.py +47 -56
  552. scipy/stats/tests/test_sampling.py +9 -4
  553. scipy/stats/tests/test_stats.py +799 -939
  554. scipy/stats/tests/test_variation.py +8 -6
  555. scipy/version.py +2 -2
  556. {scipy-1.15.3.dist-info → scipy-1.16.0rc2.dist-info}/LICENSE.txt +4 -4
  557. {scipy-1.15.3.dist-info → scipy-1.16.0rc2.dist-info}/METADATA +11 -11
  558. {scipy-1.15.3.dist-info → scipy-1.16.0rc2.dist-info}/RECORD +559 -566
  559. scipy-1.16.0rc2.dist-info/WHEEL +6 -0
  560. scipy/_lib/array_api_extra/_funcs.py +0 -484
  561. scipy/_lib/array_api_extra/_typing.py +0 -8
  562. scipy/interpolate/_bspl.cpython-313t-darwin.so +0 -0
  563. scipy/optimize/_cobyla.cpython-313t-darwin.so +0 -0
  564. scipy/optimize/_cython_nnls.cpython-313t-darwin.so +0 -0
  565. scipy/optimize/_slsqp.cpython-313t-darwin.so +0 -0
  566. scipy/spatial/qhull_src/COPYING.txt +0 -38
  567. scipy/special/libsf_error_state.dylib +0 -0
  568. scipy/special/tests/test_log_softmax.py +0 -109
  569. scipy/special/tests/test_xsf_cuda.py +0 -114
  570. scipy/special/xsf/binom.h +0 -89
  571. scipy/special/xsf/cdflib.h +0 -100
  572. scipy/special/xsf/cephes/airy.h +0 -307
  573. scipy/special/xsf/cephes/besselpoly.h +0 -51
  574. scipy/special/xsf/cephes/beta.h +0 -257
  575. scipy/special/xsf/cephes/cbrt.h +0 -131
  576. scipy/special/xsf/cephes/chbevl.h +0 -85
  577. scipy/special/xsf/cephes/chdtr.h +0 -193
  578. scipy/special/xsf/cephes/const.h +0 -87
  579. scipy/special/xsf/cephes/ellie.h +0 -293
  580. scipy/special/xsf/cephes/ellik.h +0 -251
  581. scipy/special/xsf/cephes/ellpe.h +0 -107
  582. scipy/special/xsf/cephes/ellpk.h +0 -117
  583. scipy/special/xsf/cephes/expn.h +0 -260
  584. scipy/special/xsf/cephes/gamma.h +0 -398
  585. scipy/special/xsf/cephes/hyp2f1.h +0 -596
  586. scipy/special/xsf/cephes/hyperg.h +0 -361
  587. scipy/special/xsf/cephes/i0.h +0 -149
  588. scipy/special/xsf/cephes/i1.h +0 -158
  589. scipy/special/xsf/cephes/igam.h +0 -421
  590. scipy/special/xsf/cephes/igam_asymp_coeff.h +0 -195
  591. scipy/special/xsf/cephes/igami.h +0 -313
  592. scipy/special/xsf/cephes/j0.h +0 -225
  593. scipy/special/xsf/cephes/j1.h +0 -198
  594. scipy/special/xsf/cephes/jv.h +0 -715
  595. scipy/special/xsf/cephes/k0.h +0 -164
  596. scipy/special/xsf/cephes/k1.h +0 -163
  597. scipy/special/xsf/cephes/kn.h +0 -243
  598. scipy/special/xsf/cephes/lanczos.h +0 -112
  599. scipy/special/xsf/cephes/ndtr.h +0 -275
  600. scipy/special/xsf/cephes/poch.h +0 -85
  601. scipy/special/xsf/cephes/polevl.h +0 -167
  602. scipy/special/xsf/cephes/psi.h +0 -194
  603. scipy/special/xsf/cephes/rgamma.h +0 -111
  604. scipy/special/xsf/cephes/scipy_iv.h +0 -811
  605. scipy/special/xsf/cephes/shichi.h +0 -248
  606. scipy/special/xsf/cephes/sici.h +0 -224
  607. scipy/special/xsf/cephes/sindg.h +0 -221
  608. scipy/special/xsf/cephes/tandg.h +0 -139
  609. scipy/special/xsf/cephes/trig.h +0 -58
  610. scipy/special/xsf/cephes/unity.h +0 -186
  611. scipy/special/xsf/cephes/zeta.h +0 -172
  612. scipy/special/xsf/config.h +0 -304
  613. scipy/special/xsf/digamma.h +0 -205
  614. scipy/special/xsf/error.h +0 -57
  615. scipy/special/xsf/evalpoly.h +0 -47
  616. scipy/special/xsf/expint.h +0 -266
  617. scipy/special/xsf/hyp2f1.h +0 -694
  618. scipy/special/xsf/iv_ratio.h +0 -173
  619. scipy/special/xsf/lambertw.h +0 -150
  620. scipy/special/xsf/loggamma.h +0 -163
  621. scipy/special/xsf/sici.h +0 -200
  622. scipy/special/xsf/tools.h +0 -427
  623. scipy/special/xsf/trig.h +0 -164
  624. scipy/special/xsf/wright_bessel.h +0 -843
  625. scipy/special/xsf/zlog1.h +0 -35
  626. scipy/stats/_mvn.cpython-313t-darwin.so +0 -0
  627. scipy-1.15.3.dist-info/WHEEL +0 -4
@@ -0,0 +1,1221 @@
1
+ import pytest
2
+
3
+ import numpy as np
4
+ from scipy.spatial.transform import Rotation, RigidTransform
5
+ from scipy.spatial.transform._rigid_transform import normalize_dual_quaternion
6
+ from scipy._lib._array_api import (
7
+ is_lazy_array,
8
+ xp_vector_norm,
9
+ is_numpy,
10
+ xp_assert_close,
11
+ )
12
+ import scipy._lib.array_api_extra as xpx
13
+
14
+
15
+ pytestmark = pytest.mark.skip_xp_backends(np_only=True)
16
+
17
+
18
+ def rotation_to_xp(r: Rotation, xp):
19
+ return Rotation.from_quat(xp.asarray(r.as_quat()))
20
+
21
+
22
+ def rigid_transform_to_xp(r: RigidTransform, xp):
23
+ return RigidTransform.from_matrix(xp.asarray(r.as_matrix()))
24
+
25
+
26
+ def test_repr(xp):
27
+ actual = repr(RigidTransform.from_matrix(xp.eye(4)))
28
+ expected = """\
29
+ RigidTransform.from_matrix(array([[1., 0., 0., 0.],
30
+ [0., 1., 0., 0.],
31
+ [0., 0., 1., 0.],
32
+ [0., 0., 0., 1.]]))"""
33
+ assert actual == expected
34
+
35
+ tf = RigidTransform.from_matrix(xp.asarray(RigidTransform.identity(2).as_matrix()))
36
+ actual = repr(tf)
37
+ expected = """\
38
+ RigidTransform.from_matrix(array([[[1., 0., 0., 0.],
39
+ [0., 1., 0., 0.],
40
+ [0., 0., 1., 0.],
41
+ [0., 0., 0., 1.]],
42
+
43
+ [[1., 0., 0., 0.],
44
+ [0., 1., 0., 0.],
45
+ [0., 0., 1., 0.],
46
+ [0., 0., 0., 1.]]]))"""
47
+ assert actual == expected
48
+
49
+
50
+ def test_from_rotation(xp):
51
+ atol = 1e-12
52
+
53
+ # Test single rotation
54
+ r = Rotation.from_matrix(xp.eye(3))
55
+ tf = RigidTransform.from_rotation(r)
56
+ xp_assert_close(tf.as_matrix(), xp.eye(4), atol=atol)
57
+ assert tf.single
58
+
59
+ r = Rotation.from_euler('z', xp.asarray(90), degrees=True)
60
+ tf = RigidTransform.from_rotation(r)
61
+ xp_assert_close(tf.as_matrix()[:3, :3], r.as_matrix(), atol=atol)
62
+ xp_assert_close(tf.as_matrix()[:3, 3], xp.asarray([0.0, 0, 0]), atol=atol)
63
+ xp_assert_close(tf.as_matrix()[3, :], xp.asarray([0.0, 0, 0, 1]), atol=atol)
64
+ assert tf.single
65
+
66
+ # Test multiple rotations
67
+ r = Rotation.from_euler('zyx', xp.asarray([[90, 0, 0], [0, 90, 0]]), degrees=True)
68
+ tf = RigidTransform.from_rotation(r)
69
+ xp_assert_close(tf.as_matrix()[:, :3, :3], r.as_matrix(), atol=atol)
70
+ xp_assert_close(tf.as_matrix()[:, :3, 3], xp.asarray([[0.0, 0, 0], [0, 0, 0]]),
71
+ atol=atol)
72
+ xp_assert_close(tf.as_matrix()[:, 3, :], xp.asarray([[0.0, 0, 0, 1], [0, 0, 0, 1]]),
73
+ atol=atol)
74
+ assert not tf.single
75
+
76
+
77
+ def test_from_translation(xp):
78
+ # Test single translation
79
+ t = xp.asarray([1, 2, 3])
80
+ tf = RigidTransform.from_translation(t)
81
+ expected = xp.eye(4)
82
+ expected = xpx.at(expected)[..., :3, 3].set(t)
83
+ xp_assert_close(tf.as_matrix(), expected)
84
+ assert tf.single
85
+
86
+ # Test multiple translations
87
+ t = xp.asarray([[1, 2, 3], [4, 5, 6]])
88
+ tf = RigidTransform.from_translation(t)
89
+ for i in range(t.shape[0]):
90
+ expected = xp.eye(4)
91
+ expected = xpx.at(expected)[..., :3, 3].set(t[i, ...])
92
+ xp_assert_close(tf.as_matrix()[i, ...], expected)
93
+ assert not tf.single
94
+
95
+
96
+ def test_from_translation_array_like():
97
+ # Test single translation
98
+ t = [1, 2, 3]
99
+ tf = RigidTransform.from_translation(t)
100
+ tf_expected = RigidTransform.from_translation(np.array(t))
101
+ xp_assert_close(tf.as_matrix(), tf_expected.as_matrix())
102
+ assert tf.single
103
+
104
+ # Test multiple translations
105
+ t = [[1, 2, 3], [4, 5, 6]]
106
+ tf = RigidTransform.from_translation(t)
107
+ tf_expected = RigidTransform.from_translation(np.array(t))
108
+ xp_assert_close(tf.as_matrix(), tf_expected.as_matrix())
109
+ assert not tf.single
110
+
111
+
112
+ def test_from_matrix(xp):
113
+ atol = 1e-12
114
+
115
+ # Test single transform matrix
116
+ matrix = xp.eye(4)
117
+ matrix = xpx.at(matrix)[..., :3, 3].set(xp.asarray([1, 2, 3]))
118
+ tf = RigidTransform.from_matrix(matrix)
119
+ xp_assert_close(tf.as_matrix(), matrix, atol=atol)
120
+ assert tf.single
121
+
122
+ # Test multiple transform matrices
123
+ matrices = xp.repeat(xp.eye(4)[None, ...], 2, axis=0)
124
+ matrices = xpx.at(matrices)[0, :3, 3].set(xp.asarray([1, 2, 3]))
125
+ matrices = xpx.at(matrices)[1, :3, 3].set(xp.asarray([4, 5, 6]))
126
+ tf = RigidTransform.from_matrix(matrices)
127
+ xp_assert_close(tf.as_matrix(), matrices, atol=atol)
128
+ assert not tf.single
129
+
130
+ # Test non-1 determinant
131
+ matrix = xp.eye(4)
132
+ matrix = xpx.at(matrix)[..., :3, :3].set(xp.eye(3) * 2.0)
133
+ tf = RigidTransform.from_matrix(matrix)
134
+ xp_assert_close(tf.as_matrix(), xp.eye(4), atol=atol)
135
+
136
+ # Test non-orthogonal rotation matrix
137
+ matrix = xp.asarray([[1, 1, 0, 0],
138
+ [0, 1, 0, 0],
139
+ [0, 0, 1, 0],
140
+ [0, 0, 0, 1]])
141
+ tf = RigidTransform.from_matrix(matrix)
142
+ expected = xp.asarray([[0.894427, 0.447214, 0, 0],
143
+ [-0.447214, 0.894427, 0, 0],
144
+ [0, 0, 1, 0],
145
+ [0, 0, 0, 1]])
146
+ xp_assert_close(tf.as_matrix(), expected, atol=1e-6)
147
+
148
+ # Test invalid matrix
149
+ invalid = xp.eye(4)
150
+ invalid = xpx.at(invalid)[..., 3, 3].set(2) # Invalid last row
151
+ if is_lazy_array(invalid):
152
+ tf = RigidTransform.from_matrix(invalid)
153
+ assert xp.all(xp.isnan(tf.as_matrix()))
154
+ else:
155
+ with pytest.raises(ValueError):
156
+ RigidTransform.from_matrix(invalid)
157
+
158
+
159
+ def test_from_matrix_array_like():
160
+ # Test single transform matrix
161
+ matrix = [[1, 0, 0, 0],
162
+ [0, 1, 0, 0],
163
+ [0, 0, 1, 0],
164
+ [0, 0, 0, 1]]
165
+ expected = np.eye(4)
166
+ tf = RigidTransform.from_matrix(matrix)
167
+ xp_assert_close(tf.as_matrix(), expected)
168
+ assert tf.single
169
+
170
+ # Test multiple transform matrices
171
+ matrices = [matrix, matrix]
172
+ tf = RigidTransform.from_matrix(matrices)
173
+ for i in range(len(matrices)):
174
+ xp_assert_close(tf.as_matrix()[i, ...], expected)
175
+ assert not tf.single
176
+
177
+
178
+ def test_from_components(xp):
179
+ atol = 1e-12
180
+
181
+ # Test single rotation and translation
182
+ t = xp.asarray([1, 2, 3])
183
+ r = Rotation.from_euler("zyx", xp.asarray([90, 0, 0]), degrees=True)
184
+ tf = RigidTransform.from_components(t, r)
185
+
186
+ expected = xp.zeros((4, 4))
187
+ expected = xpx.at(expected)[..., :3, :3].set(r.as_matrix())
188
+ expected = xpx.at(expected)[..., :3, 3].set(t)
189
+ expected = xpx.at(expected)[..., 3, 3].set(1)
190
+ xp_assert_close(tf.as_matrix(), expected, atol=atol)
191
+ assert tf.single
192
+
193
+ # Test single rotation and multiple translations
194
+ t = xp.asarray([[1, 2, 3], [4, 5, 6]])
195
+ r = Rotation.from_euler('z', xp.asarray(90), degrees=True)
196
+ tf = RigidTransform.from_components(t, r)
197
+ assert not tf.single
198
+
199
+ for i in range(t.shape[0]):
200
+ expected = xp.zeros((4, 4))
201
+ expected = xpx.at(expected)[..., :3, :3].set(r.as_matrix())
202
+ expected = xpx.at(expected)[..., :3, 3].set(t[i, ...])
203
+ expected = xpx.at(expected)[..., 3, 3].set(1)
204
+ xp_assert_close(tf.as_matrix()[i, ...], expected, atol=atol)
205
+
206
+ # Test multiple rotations and translations
207
+ t = xp.asarray([[1, 2, 3], [4, 5, 6]])
208
+ r = Rotation.from_euler('zyx', xp.asarray([[90, 0, 0], [0, 90, 0]]), degrees=True)
209
+ tf = RigidTransform.from_components(t, r)
210
+ assert not tf.single
211
+
212
+ for i in range(t.shape[0]):
213
+ expected = xp.zeros((4, 4))
214
+ expected = xpx.at(expected)[..., :3, :3].set(r.as_matrix()[i, ...])
215
+ expected = xpx.at(expected)[..., :3, 3].set(t[i, ...])
216
+ expected = xpx.at(expected)[..., 3, 3].set(1)
217
+ xp_assert_close(tf.as_matrix()[i, ...], expected, atol=atol)
218
+
219
+
220
+ def test_from_components_array_like():
221
+ rng = np.random.default_rng(123)
222
+ # Test single rotation and translation
223
+ t = [1, 2, 3]
224
+ r = Rotation.random(rng=rng)
225
+ tf = RigidTransform.from_components(t, r)
226
+ tf_expected = RigidTransform.from_components(np.array(t), r)
227
+ xp_assert_close(tf.as_matrix(), tf_expected.as_matrix(), atol=1e-12)
228
+ assert tf.single
229
+
230
+ # Test multiple rotations and translations
231
+ t = [[1, 2, 3], [4, 5, 6]]
232
+ r = Rotation.random(len(t), rng=rng)
233
+ tf = RigidTransform.from_components(t, r)
234
+ tf_expected = RigidTransform.from_components(np.array(t), r)
235
+ xp_assert_close(tf.as_matrix(), tf_expected.as_matrix(), atol=1e-12)
236
+ assert not tf.single
237
+
238
+
239
+
240
+ def test_as_components(xp):
241
+ atol = 1e-12
242
+ n = 10
243
+ rng = np.random.default_rng(123)
244
+ t = xp.asarray(rng.normal(size=(n, 3)))
245
+ r = rotation_to_xp(Rotation.random(n, rng=rng), xp=xp)
246
+ tf = RigidTransform.from_components(t, r)
247
+ new_t, new_r = tf.as_components()
248
+ assert all(new_r.approx_equal(r, atol=atol))
249
+ xp_assert_close(new_t, t, atol=atol)
250
+
251
+
252
+ def test_from_exp_coords(xp):
253
+ # example from 3.3 of
254
+ # https://hades.mech.northwestern.edu/images/2/25/MR-v2.pdf
255
+ angle1 = np.deg2rad(30.0)
256
+ mat = xp.asarray([
257
+ [np.cos(angle1), -np.sin(angle1), 0.0, 1.0],
258
+ [np.sin(angle1), np.cos(angle1), 0.0, 2.0],
259
+ [0.0, 0.0, 1.0, 0.0],
260
+ [0.0, 0.0, 0.0, 1.0]
261
+ ])
262
+ tf1 = RigidTransform.from_matrix(mat)
263
+ angle2 = np.deg2rad(60.0)
264
+ mat = xp.asarray([
265
+ [np.cos(angle2), -np.sin(angle2), 0.0, 2.0],
266
+ [np.sin(angle2), np.cos(angle2), 0.0, 1.0],
267
+ [0.0, 0.0, 1.0, 0.0],
268
+ [0.0, 0.0, 0.0, 1.0]
269
+ ])
270
+ tf2 = RigidTransform.from_matrix(mat)
271
+ expected = tf2 * tf1.inv()
272
+ actual = RigidTransform.from_exp_coords(
273
+ np.deg2rad(30.0) * xp.asarray([0.0, 0.0, 1.0, 3.37, -3.37, 0.0]))
274
+ xp_assert_close(actual.as_matrix(), expected.as_matrix(), atol=1e-2)
275
+
276
+ # test cases generated by comparison to pytransform3d
277
+ exp_coords = xp.asarray([
278
+ [-2.01041204, -0.52983629, 0.65773501,
279
+ 0.10386614, 0.05855009, 0.54959179],
280
+ [-0.22537438, -0.24132627, -2.4747121,
281
+ -0.09158594, 1.88075832, -0.03197204]
282
+ ])
283
+ expected_matrix = xp.asarray([
284
+ [[0.76406621, 0.10504613, -0.63652819, -0.10209961],
285
+ [0.59956454, -0.47987325, 0.64050295, 0.40158789],
286
+ [-0.2381705, -0.87102639, -0.42963687, 0.19637636],
287
+ [0., 0., 0., 1.]],
288
+ [[-0.78446989, 0.61157488, 0.10287448, 1.33330055],
289
+ [-0.58017785, -0.78232107, 0.22664378, 0.52660831],
290
+ [0.21909052, 0.11810973, 0.96852952, -0.02968529],
291
+ [0., 0., 0., 1.]]
292
+ ])
293
+ xp_assert_close(
294
+ RigidTransform.from_exp_coords(exp_coords).as_matrix(),
295
+ expected_matrix, atol=1e-8)
296
+
297
+ # identity
298
+ xp_assert_close(
299
+ RigidTransform.from_exp_coords(xp.zeros(6)).as_matrix(),
300
+ xp.eye(4), atol=1e-12)
301
+
302
+ # only translation
303
+ expected_matrix = xp.asarray([
304
+ [[1.0, 0.0, 0.0, 3.0],
305
+ [0.0, 1.0, 0.0, -5.4],
306
+ [0.0, 0.0, 1.0, 100.2],
307
+ [0.0, 0.0, 0.0, 1.0]],
308
+ [[1.0, 0.0, 0.0, -3.0],
309
+ [0.0, 1.0, 0.0, 13.3],
310
+ [0.0, 0.0, 1.0, 1.3],
311
+ [0.0, 0.0, 0.0, 1.0]]
312
+ ])
313
+ actual = RigidTransform.from_exp_coords(xp.asarray([
314
+ [0.0, 0.0, 0.0, 3.0, -5.4, 100.2],
315
+ [0.0, 0.0, 0.0, -3.0, 13.3, 1.3],
316
+ ]))
317
+ xp_assert_close(actual.as_matrix(), expected_matrix, atol=1e-12)
318
+
319
+ # only rotation
320
+ rot = Rotation.from_euler(
321
+ 'zyx',
322
+ xp.asarray([[34, -12, 0.5],
323
+ [-102, -55, 30]]),
324
+ degrees=True)
325
+ rotvec = rot.as_rotvec()
326
+ expected_matrix = xp.repeat(xp.eye(4)[None, ...], 2, axis=0)
327
+ expected_matrix = xpx.at(expected_matrix)[..., :3, :3].set(rot.as_matrix())
328
+ actual = RigidTransform.from_exp_coords(
329
+ xp.concat((rotvec, xp.zeros((2, 3))), axis=-1))
330
+ xp_assert_close(actual.as_matrix(), expected_matrix, atol=1e-12)
331
+
332
+
333
+ def test_from_exp_coords_array_like():
334
+ rng = np.random.default_rng(123)
335
+ # Test single transform
336
+ t = np.array([1, 2, 3])
337
+ r = Rotation.random(rng=rng)
338
+ tf_expected = RigidTransform.from_components(t, r)
339
+ exp_coords = tf_expected.as_exp_coords().tolist()
340
+ assert isinstance(exp_coords, list)
341
+ tf = RigidTransform.from_exp_coords(exp_coords)
342
+ xp_assert_close(tf.as_matrix(), tf_expected.as_matrix(), atol=1e-12)
343
+
344
+ # Test multiple transforms
345
+ t = [[1, 2, 3], [4, 5, 6]]
346
+ r = Rotation.random(len(t), rng=rng)
347
+ tf_expected = RigidTransform.from_components(t, r)
348
+ exp_coords = tf_expected.as_exp_coords().tolist()
349
+ assert isinstance(exp_coords, list)
350
+ tf = RigidTransform.from_exp_coords(exp_coords)
351
+ xp_assert_close(tf.as_matrix(), tf_expected.as_matrix(), atol=1e-12)
352
+
353
+
354
+ def test_as_exp_coords(xp):
355
+ # identity
356
+ expected = xp.zeros(6)
357
+ actual = RigidTransform.from_exp_coords(expected).as_exp_coords()
358
+ xp_assert_close(actual, expected, atol=1e-12)
359
+
360
+ rng = np.random.default_rng(10)
361
+
362
+ # pure rotation
363
+ rot_vec = xp.asarray(rng.normal(scale=0.1, size=(1000, 3)))
364
+ tf = RigidTransform.from_rotation(Rotation.from_rotvec(rot_vec))
365
+ exp_coords = tf.as_exp_coords()
366
+ xp_assert_close(exp_coords[:, :3], rot_vec, rtol=1e-13)
367
+ expected = xp.zeros_like(rot_vec)
368
+ xp_assert_close(exp_coords[:, 3:], expected, atol=1e-16)
369
+
370
+ # pure translation
371
+ translation = xp.asarray(rng.normal(scale=100.0, size=(1000, 3)))
372
+ tf = RigidTransform.from_translation(translation)
373
+ exp_coords = tf.as_exp_coords()
374
+ xp_assert_close(exp_coords[:, :3], expected, atol=1e-16)
375
+ xp_assert_close(exp_coords[:, 3:], translation, rtol=1e-15)
376
+
377
+
378
+ def test_from_dual_quat(xp):
379
+ # identity
380
+ xp_assert_close(
381
+ RigidTransform.from_dual_quat(
382
+ xp.asarray([0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0])).as_matrix(),
383
+ xp.eye(4), atol=1e-12)
384
+ xp_assert_close(
385
+ RigidTransform.from_dual_quat(
386
+ xp.asarray([1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]),
387
+ scalar_first=True).as_matrix(),
388
+ xp.eye(4), atol=1e-12)
389
+
390
+ # only translation
391
+ actual = RigidTransform.from_dual_quat(
392
+ xp.asarray([0, 0, 0, 1, 0.25, 0.15, -0.7, 0]))
393
+ expected_matrix = xp.asarray([
394
+ [1, 0, 0, 0.5],
395
+ [0, 1, 0, 0.3],
396
+ [0, 0, 1, -1.4],
397
+ [0, 0, 0, 1]
398
+ ])
399
+ xp_assert_close(actual.as_matrix(), expected_matrix, atol=1e-12)
400
+ actual = RigidTransform.from_dual_quat(
401
+ xp.asarray([1, 0, 0, 0, 0, 0.25, 0.15, -0.7]), scalar_first=True)
402
+ expected_matrix = xp.asarray([
403
+ [1, 0, 0, 0.5],
404
+ [0, 1, 0, 0.3],
405
+ [0, 0, 1, -1.4],
406
+ [0, 0, 0, 1]
407
+ ])
408
+ xp_assert_close(actual.as_matrix(), expected_matrix, atol=1e-12)
409
+
410
+ # only rotation
411
+ actual_rot = Rotation.from_euler("xyz", xp.asarray([65, -13, 90]), degrees=True)
412
+ actual = RigidTransform.from_dual_quat(
413
+ xp.concat((actual_rot.as_quat(), xp.zeros(4)), axis=-1))
414
+ expected_matrix = xp.eye(4)
415
+ expected_matrix = xpx.at(expected_matrix)[..., :3, :3].set(actual_rot.as_matrix())
416
+ xp_assert_close(actual.as_matrix(), expected_matrix, atol=1e-12)
417
+
418
+ actual = RigidTransform.from_dual_quat(
419
+ xp.concat((actual_rot.as_quat(scalar_first=True), xp.zeros(4)), axis=-1),
420
+ scalar_first=True)
421
+ expected_matrix = xp.eye(4)
422
+ expected_matrix = xpx.at(expected_matrix)[..., :3, :3].set(actual_rot.as_matrix())
423
+ xp_assert_close(actual.as_matrix(), expected_matrix, atol=1e-12)
424
+
425
+ # rotation and translation
426
+ # rtol is set to 1e-7 because xp_assert_close deviates from
427
+ # np.testing.assert_allclose in that it does not automatically default to 1e-7 for
428
+ # floating point inputs.
429
+ # See https://numpy.org/doc/2.2/reference/generated/numpy.testing.assert_allclose.html
430
+ actual = RigidTransform.from_dual_quat(
431
+ xp.asarray(
432
+ [[0.0617101, -0.06483886, 0.31432811, 0.94508498,
433
+ 0.04985168, -0.26119618, 0.1691491, -0.07743254],
434
+ [0.19507259, 0.49404931, -0.06091285, 0.8450749,
435
+ 0.65049656, -0.30782513, 0.16566752, 0.04174544]]))
436
+ expected_matrix = xp.asarray(
437
+ [[[0.79398752, -0.60213598, -0.08376202, 0.24605262],
438
+ [0.58613113, 0.79477941, -0.15740392, -0.4932833],
439
+ [0.16135089, 0.07588122, 0.98397557, 0.34262676],
440
+ [0., 0., 0., 1.]],
441
+ [[0.50440981, 0.2957028, 0.81125249, 1.20934468],
442
+ [0.08979911, 0.91647262, -0.3898898, -0.70540077],
443
+ [-0.8587822, 0.26951399, 0.43572393, -0.47776265],
444
+ [0., 0., 0., 1.]]])
445
+ xp_assert_close(actual.as_matrix(), expected_matrix, atol=1e-12, rtol=1e-7)
446
+
447
+ actual = RigidTransform.from_dual_quat(
448
+ xp.asarray(
449
+ [[0.94508498, 0.0617101, -0.06483886, 0.31432811,
450
+ -0.07743254, 0.04985168, -0.26119618, 0.1691491],
451
+ [0.8450749, 0.19507259, 0.49404931, -0.06091285,
452
+ 0.04174544, 0.65049656, -0.30782513, 0.16566752]]),
453
+ scalar_first=True)
454
+ xp_assert_close(actual.as_matrix(), expected_matrix, atol=1e-12, rtol=1e-7)
455
+
456
+ # unnormalized dual quaternions
457
+
458
+ # invalid real quaternion with norm 0
459
+ actual = RigidTransform.from_dual_quat(xp.zeros(8))
460
+ xp_assert_close(actual.as_matrix(), xp.eye(4), atol=1e-12)
461
+
462
+ # real quaternion with norm != 1
463
+ unnormalized_dual_quat = xp.asarray(
464
+ [-0.2547655, 1.23506123, 0.20230088, 0.24247194, # norm 1.3
465
+ 0.38559628, 0.08184063, 0.1755943, -0.1582222] # orthogonal
466
+ )
467
+ xp_assert_close(xp_vector_norm(unnormalized_dual_quat[:4]), xp.asarray(1.3)[()],
468
+ atol=1e-12)
469
+ xp_assert_close(xp.vecdot(unnormalized_dual_quat[:4],
470
+ unnormalized_dual_quat[4:])[()],
471
+ xp.asarray(0.0)[()], atol=1e-8)
472
+
473
+ dual_quat = RigidTransform.from_dual_quat(
474
+ unnormalized_dual_quat).as_dual_quat()
475
+ xp_assert_close(xp_vector_norm(dual_quat[:4]), xp.asarray(1.0)[()], atol=1e-12)
476
+ xp_assert_close(xp.vecdot(dual_quat[:4], dual_quat[4:])[()], xp.asarray(0.0)[()],
477
+ atol=1e-12)
478
+
479
+ # real and dual quaternion are not orthogonal
480
+ unnormalized_dual_quat = xp.asarray(
481
+ [0.20824458, 0.75098079, 0.54542913, -0.30849493, # unit norm
482
+ -0.16051025, 0.10742978, 0.21277201, 0.20596935] # not orthogonal
483
+ )
484
+ xp_assert_close(xp_vector_norm(unnormalized_dual_quat[:4]), xp.asarray(1.0)[()],
485
+ atol=1e-12)
486
+ assert xp.vecdot(unnormalized_dual_quat[:4], unnormalized_dual_quat[4:]) != 0.0
487
+ dual_quat = RigidTransform.from_dual_quat(
488
+ unnormalized_dual_quat).as_dual_quat()
489
+ xp_assert_close(xp_vector_norm(dual_quat[:4]), xp.asarray(1.0)[()], atol=1e-12)
490
+ xp_assert_close(xp.vecdot(dual_quat[:4], dual_quat[4:])[()], xp.asarray(0.0)[()],
491
+ atol=1e-12)
492
+
493
+ # invalid real quaternion with norm 0, non-orthogonal dual quaternion
494
+ unnormalized_dual_quat = xp.asarray(
495
+ [0.0, 0.0, 0.0, 0.0, -0.16051025, 0.10742978, 0.21277201, 0.20596935])
496
+ assert xp.vecdot(xp.asarray([0.0, 0, 0, 1]), unnormalized_dual_quat[4:]) != 0.0
497
+ dual_quat = RigidTransform.from_dual_quat(
498
+ unnormalized_dual_quat).as_dual_quat()
499
+ xp_assert_close(dual_quat[:4], xp.asarray([0.0, 0, 0, 1]), atol=1e-12)
500
+ xp_assert_close(xp.vecdot(dual_quat[:4], dual_quat[4:])[()], xp.asarray(0.0)[()],
501
+ atol=1e-12)
502
+
503
+ # compensation for precision loss in real quaternion
504
+ rng = np.random.default_rng(1000)
505
+ t = xp.asarray(rng.normal(size=(3,)))
506
+ r = rotation_to_xp(Rotation.random(10, rng=rng), xp=xp)
507
+ random_dual_quats = RigidTransform.from_components(t, r).as_dual_quat()
508
+
509
+ # ensure that random quaternions are not normalized
510
+ random_dual_quats = xpx.at(random_dual_quats)[:, :4].add(0.01)
511
+ assert not xp.any(xpx.isclose(xp_vector_norm(random_dual_quats[:, :4], axis=1),
512
+ 1.0, atol=0.0001))
513
+ dual_quat_norm = RigidTransform.from_dual_quat(
514
+ random_dual_quats).as_dual_quat()
515
+ expected = xp.ones(dual_quat_norm.shape[0])
516
+ xp_assert_close(xp_vector_norm(dual_quat_norm[:, :4], axis=1), expected, atol=1e-12)
517
+
518
+ # compensation for precision loss in dual quaternion, results in violation
519
+ # of orthogonality constraint
520
+ t = xp.asarray(rng.normal(size=(10, 3)))
521
+ r = rotation_to_xp(Rotation.random(10, rng=rng), xp=xp)
522
+ random_dual_quats = RigidTransform.from_components(t, r).as_dual_quat()
523
+
524
+ # ensure that random quaternions are not normalized
525
+ random_dual_quats = xpx.at(random_dual_quats)[:, 4:].add(0.01)
526
+ q_norm = xp.vecdot(random_dual_quats[:, :4], random_dual_quats[:, 4:])
527
+ assert not xp.any(xpx.isclose(q_norm, 0.0, atol=0.0001))
528
+ dual_quat_norm = RigidTransform.from_dual_quat(
529
+ random_dual_quats).as_dual_quat()
530
+ expected = xp.zeros(dual_quat_norm.shape[0])
531
+ xp_assert_close(xp.vecdot(dual_quat_norm[:, :4], dual_quat_norm[:, 4:]), expected,
532
+ atol=1e-12)
533
+ xp_assert_close(random_dual_quats[:, :4], dual_quat_norm[:, :4], atol=1e-12)
534
+
535
+
536
+ def test_from_dual_quat_array_like():
537
+ rng = np.random.default_rng(123)
538
+ # Test single transform
539
+ t = np.array([1, 2, 3])
540
+ r = Rotation.random(rng=rng)
541
+ tf_expected = RigidTransform.from_components(t, r)
542
+ dual_quat = tf_expected.as_dual_quat().tolist()
543
+ assert isinstance(dual_quat, list)
544
+ tf = RigidTransform.from_dual_quat(dual_quat)
545
+ xp_assert_close(tf.as_matrix(), tf_expected.as_matrix(), atol=1e-12)
546
+
547
+ # Test multiple transforms
548
+ t = [[1, 2, 3], [4, 5, 6]]
549
+ r = Rotation.random(len(t), rng=rng)
550
+ tf_expected = RigidTransform.from_components(t, r)
551
+ dual_quat = tf_expected.as_dual_quat().tolist()
552
+ assert isinstance(dual_quat, list)
553
+ tf = RigidTransform.from_dual_quat(dual_quat)
554
+ xp_assert_close(tf.as_matrix(), tf_expected.as_matrix(), atol=1e-12)
555
+
556
+
557
+ def test_as_dual_quat(xp):
558
+ # identity
559
+ expected = xp.asarray([0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0])
560
+ actual = xp.asarray(RigidTransform.identity().as_dual_quat())
561
+ xp_assert_close(actual, expected, atol=1e-12)
562
+
563
+ expected = xp.asarray([1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])
564
+ actual = xp.asarray(RigidTransform.identity().as_dual_quat(scalar_first=True))
565
+ xp_assert_close(actual, expected, atol=1e-12)
566
+
567
+ rng = np.random.default_rng(10)
568
+
569
+ # only rotation
570
+ for _ in range(10):
571
+ real_part = xp.asarray(Rotation.random(rng=rng).as_quat())
572
+ dual_part = xp.zeros(4)
573
+ expected = xp.concat((real_part, dual_part), axis=-1)
574
+ actual = RigidTransform.from_dual_quat(expected).as_dual_quat()
575
+ # because of double cover:
576
+ if xp.sign(expected[0]) != xp.sign(actual[0]):
577
+ actual = -actual
578
+ xp_assert_close(actual, expected, atol=1e-12)
579
+
580
+ # only translation
581
+ for _ in range(10):
582
+ tf = 0.5 * rng.normal(size=3)
583
+ expected = xp.asarray([0.0, 0, 0, 1, *tf.tolist(), 0])
584
+ actual = RigidTransform.from_dual_quat(expected).as_dual_quat()
585
+ # because of double cover:
586
+ if xp.sign(expected[0]) != xp.sign(actual[0]):
587
+ actual = -actual
588
+ xp_assert_close(actual, expected, atol=1e-12)
589
+
590
+ # rotation and translation
591
+ for _ in range(10):
592
+ t = xp.asarray(rng.normal(size=3))
593
+ r = rotation_to_xp(Rotation.random(rng=rng), xp=xp)
594
+ expected = RigidTransform.from_components(t, r).as_dual_quat()
595
+ actual = RigidTransform.from_dual_quat(expected).as_dual_quat()
596
+ # because of double cover:
597
+ if xp.sign(expected[0]) != xp.sign(actual[0]):
598
+ actual = -actual
599
+ xp_assert_close(actual, expected, atol=1e-12)
600
+
601
+
602
+ def test_from_as_internal_consistency(xp):
603
+ atol = 1e-12
604
+ n = 1000
605
+ rng = np.random.default_rng(10)
606
+ t = xp.asarray(rng.normal(size=(n, 3)))
607
+ r = rotation_to_xp(Rotation.random(n, rng=rng), xp=xp)
608
+ tf0 = RigidTransform.from_components(t, r)
609
+
610
+ tf1 = RigidTransform.from_components(*tf0.as_components())
611
+ xp_assert_close(tf0.as_matrix(), tf1.as_matrix(), atol=atol)
612
+
613
+ tf1 = RigidTransform.from_components(tf0.translation, tf0.rotation)
614
+ xp_assert_close(tf0.as_matrix(), tf1.as_matrix(), atol=atol)
615
+
616
+ tf1 = RigidTransform.from_exp_coords(tf0.as_exp_coords())
617
+ xp_assert_close(tf0.as_matrix(), tf1.as_matrix(), atol=atol)
618
+
619
+ tf1 = RigidTransform.from_matrix(tf0.as_matrix())
620
+ xp_assert_close(tf0.as_matrix(), tf1.as_matrix(), atol=atol)
621
+
622
+ tf1 = RigidTransform.from_dual_quat(tf0.as_dual_quat())
623
+ xp_assert_close(tf0.as_matrix(), tf1.as_matrix(), atol=atol)
624
+
625
+ # exp_coords small rotation
626
+ tf0 = RigidTransform.from_components(
627
+ xp.asarray(rng.normal(scale=1000.0, size=(1000, 3))),
628
+ Rotation.from_rotvec(xp.asarray(rng.normal(scale=1e-10, size=(1000, 3)))))
629
+ tf1 = RigidTransform.from_exp_coords(tf0.as_exp_coords())
630
+ xp_assert_close(tf0.as_matrix(), tf1.as_matrix(), atol=atol)
631
+
632
+
633
+ def test_identity():
634
+ # We do not use xp here because identity always returns numpy arrays
635
+ atol = 1e-12
636
+
637
+ # Test single identity
638
+ tf = RigidTransform.identity()
639
+ xp_assert_close(tf.as_matrix(), np.eye(4), atol=atol)
640
+
641
+ # Test multiple identities
642
+ tf = RigidTransform.identity(5)
643
+ xp_assert_close(tf.as_matrix(), np.array([np.eye(4)] * 5), atol=atol)
644
+
645
+
646
+ def test_apply(xp):
647
+ atol = 1e-12
648
+
649
+ ## Single transform
650
+ r = Rotation.from_euler('z', xp.asarray(90), degrees=True)
651
+ t = xp.asarray([2, 3, 4])
652
+ tf = RigidTransform.from_components(t, r)
653
+
654
+ # Single vector, single transform
655
+ vec = xp.asarray([1, 0, 0])
656
+ expected = t + r.apply(vec)
657
+ res = tf.apply(vec)
658
+ xp_assert_close(res, expected, atol=atol)
659
+
660
+ # Multiple vectors, single transform
661
+ vecs = xp.asarray([[1, 0, 0], [0, 1, 0]])
662
+ expected = t + r.apply(vecs)
663
+ xp_assert_close(tf.apply(vecs), expected, atol=atol)
664
+
665
+ ## Multiple transforms
666
+ r = Rotation.from_euler('z', xp.asarray([90, 0]), degrees=True)
667
+ t = xp.asarray([[2, 3, 4], [5, 6, 7]])
668
+ tf = RigidTransform.from_components(t, r)
669
+
670
+ # Single vector, multiple transforms
671
+ vec = xp.asarray([1, 0, 0])
672
+ expected = t + r.apply(vec)
673
+ xp_assert_close(tf.apply(vec), expected, atol=atol)
674
+
675
+ # Multiple vectors, multiple transforms
676
+ vecs = xp.asarray([[1, 0, 0], [0, 1, 0]])
677
+ expected = t + r.apply(vecs)
678
+ xp_assert_close(tf.apply(vecs), expected, atol=atol)
679
+
680
+
681
+ def test_apply_array_like():
682
+ rng = np.random.default_rng(123)
683
+ # Single vector
684
+ t = np.array([1, 2, 3])
685
+ r = Rotation.random(rng=rng)
686
+ tf = RigidTransform.from_components(t, r)
687
+ vec = [1, 0, 0]
688
+ expected = t + r.apply(vec)
689
+ xp_assert_close(tf.apply(vec), expected, atol=1e-12)
690
+
691
+ # Multiple vectors
692
+ t = np.array([[1, 2, 3], [4, 5, 6]])
693
+ r = Rotation.random(len(t), rng=rng)
694
+ tf = RigidTransform.from_components(t, r)
695
+ vec = [[1, 0, 0], [0, 1, 0]]
696
+ expected = t + r.apply(vec)
697
+ xp_assert_close(tf.apply(vec), expected, atol=1e-12)
698
+
699
+
700
+ def test_inverse_apply(xp):
701
+ atol = 1e-12
702
+
703
+ # Test applying inverse transform
704
+ t = xp.asarray([1, 2, 3])
705
+ r = Rotation.from_euler('z', xp.asarray(90), degrees=True)
706
+ tf = RigidTransform.from_components(t, r)
707
+
708
+ # Test single vector
709
+ vec = xp.asarray([1, 0, 0])
710
+ expected = tf.inv().apply(vec)
711
+ xp_assert_close(tf.apply(vec, inverse=True), expected, atol=atol)
712
+
713
+ # Test multiple vectors
714
+ vecs = xp.asarray([[1, 0, 0], [0, 1, 0]])
715
+ expected = tf.inv().apply(vecs)
716
+ xp_assert_close(tf.apply(vecs, inverse=True), expected, atol=atol)
717
+
718
+
719
+ def test_rotation_alone(xp):
720
+ atol = 1e-12
721
+
722
+ r = Rotation.from_euler('z', xp.asarray(90), degrees=True)
723
+ tf = RigidTransform.from_rotation(r)
724
+ vec = xp.asarray([1, 0, 0])
725
+ expected = r.apply(vec)
726
+ xp_assert_close(tf.apply(vec), expected, atol=atol)
727
+
728
+
729
+ def test_translation_alone(xp):
730
+ atol = 1e-12
731
+ t = xp.asarray([1.0, 2, 3])
732
+ tf = RigidTransform.from_translation(t)
733
+ vec = xp.asarray([5.0, 6, 7])
734
+ expected = t + vec
735
+ xp_assert_close(tf.apply(vec), expected, atol=atol)
736
+
737
+
738
+ def test_composition(xp):
739
+ atol = 1e-12
740
+
741
+ # Test composing single transforms
742
+ t1 = xp.asarray([1.0, 0, 0])
743
+ r1 = Rotation.from_euler('z', xp.asarray(90), degrees=True)
744
+ tf1 = RigidTransform.from_components(t1, r1)
745
+
746
+ t2 = xp.asarray([0, 1, 0])
747
+ r2 = Rotation.from_euler('x', xp.asarray(90), degrees=True)
748
+ tf2 = RigidTransform.from_components(t2, r2)
749
+
750
+ composed = tf2 * tf1
751
+ vec = xp.asarray([1, 0, 0])
752
+ expected = tf2.apply(tf1.apply(vec))
753
+ xp_assert_close(composed.apply(vec), expected, atol=atol)
754
+ assert composed.single
755
+
756
+ expected = t2 + r2.apply(t1 + r1.apply(vec))
757
+ xp_assert_close(composed.apply(vec), expected, atol=atol)
758
+
759
+ # Multiple transforms with single transform
760
+ t2 = np.array([[1, 2, 3], [4, 5, 6]])
761
+ tf2 = RigidTransform.from_components(t2, r2)
762
+
763
+ composed = tf2 * tf1
764
+ expected = tf2.apply(tf1.apply(vec))
765
+ xp_assert_close(composed.apply(vec), expected, atol=atol)
766
+ assert not composed.single
767
+
768
+ expected = t2 + r2.apply(t1 + r1.apply(vec))
769
+ xp_assert_close(composed.apply(vec), expected, atol=atol)
770
+
771
+ # Multiple transforms with multiple transforms
772
+ t1 = xp.asarray([[1, 0, 0], [0, -1, 1]])
773
+ tf1 = RigidTransform.from_components(t1, r1)
774
+
775
+ composed = tf2 * tf1
776
+ expected = tf2.apply(tf1.apply(vec))
777
+ xp_assert_close(composed.apply(vec), expected, atol=atol)
778
+ assert not composed.single
779
+
780
+ expected = t2 + r2.apply(t1 + r1.apply(vec))
781
+ xp_assert_close(composed.apply(vec), expected, atol=atol)
782
+
783
+
784
+ def test_pow(xp):
785
+ atol = 1e-12
786
+ num = 10
787
+ rng = np.random.default_rng(100)
788
+ t = xp.asarray(rng.normal(size=(num, 3)))
789
+ r = rotation_to_xp(Rotation.random(num, rng=rng), xp=xp)
790
+ p = RigidTransform.from_components(t, r)
791
+ p_inv = p.inv()
792
+
793
+ # Test the short-cuts and other integers
794
+ for n in [-5, -2, -1, 0, 1, 2, 5]:
795
+ q = p**n
796
+ r = rigid_transform_to_xp(RigidTransform.identity(num), xp=xp)
797
+ for _ in range(abs(n)):
798
+ if n > 0:
799
+ r = r * p
800
+ else:
801
+ r = r * p_inv
802
+ xp_assert_close(q.as_matrix(), r.as_matrix(), atol=atol)
803
+
804
+ # Test shape preservation
805
+ r = RigidTransform.from_rotation(Rotation.from_quat(xp.asarray([0, 0, 0, 1])))
806
+ assert (r**n).as_matrix().shape == (4, 4)
807
+ r = RigidTransform.from_rotation(Rotation.from_quat(xp.asarray([[0, 0, 0, 1]])))
808
+ assert (r**n).as_matrix().shape == (1, 4, 4)
809
+
810
+ # Test fractional powers
811
+ q = p**0.5
812
+ xp_assert_close((q * q).as_matrix(), p.as_matrix(), atol=atol)
813
+ q = p**-0.5
814
+ xp_assert_close((q * q).as_matrix(), p.inv().as_matrix(), atol=atol)
815
+ q = p** 1.5
816
+ xp_assert_close((q * q).as_matrix(), (p**3).as_matrix(), atol=atol)
817
+ q = p** -1.5
818
+ xp_assert_close((q * q).as_matrix(), (p**-3).as_matrix(), atol=atol)
819
+
820
+ # pow function
821
+ tf = pow(RigidTransform.from_matrix(xp.eye(4)), 2)
822
+ xp_assert_close(tf.as_matrix(), xp.eye(4), atol=atol)
823
+
824
+
825
+ def test_pow_equivalence_with_rotation(xp):
826
+ atol = 1e-12
827
+ num = 10
828
+ rng = np.random.default_rng(100)
829
+ r = rotation_to_xp(Rotation.random(num, rng=rng), xp=xp)
830
+ p = RigidTransform.from_rotation(r)
831
+ for n in [-5, -2, -1.5, -1, -0.5, 0.0, 0.5, 1, 1.5, 2, 5]:
832
+ xp_assert_close((p**n).rotation.as_matrix(), (r**n).as_matrix(), atol=atol)
833
+
834
+
835
+ def test_inverse(xp):
836
+ atol = 1e-12
837
+
838
+ # Test inverse transform
839
+ r = Rotation.from_euler('z', xp.asarray(90), degrees=True)
840
+ t = xp.asarray([1, 2, 3])
841
+ tf = RigidTransform.from_components(t, r)
842
+
843
+ # Test that tf * tf.inv() equals identity
844
+ tf_inv = tf.inv()
845
+ composed = tf * tf_inv
846
+ xp_assert_close(composed.as_matrix(), xp.eye(4), atol=atol)
847
+
848
+ n = 10
849
+ rng = np.random.default_rng(1000)
850
+ t = xp.asarray(rng.normal(size=(n, 3)))
851
+ r = rotation_to_xp(Rotation.random(n, rng=rng), xp=xp)
852
+ tf = RigidTransform.from_components(t, r)
853
+ tf_inv = tf.inv()
854
+ composed = tf * tf_inv
855
+ expected = xp.repeat(xp.eye(4)[None, ...], n, axis=0)
856
+ xp_assert_close(composed.as_matrix(), expected, atol=atol)
857
+
858
+ # Test multiple transforms
859
+ r = Rotation.from_euler('zyx', xp.asarray([[90, 0, 0], [0, 90, 0]]), degrees=True)
860
+ t = xp.asarray([[1, 2, 3], [4, 5, 6]])
861
+ tf = RigidTransform.from_components(t, r)
862
+ tf_inv = tf.inv()
863
+ composed = tf * tf_inv
864
+ expected = xp.repeat(xp.eye(4)[None, ...], 2, axis=0)
865
+ xp_assert_close(composed.as_matrix(), expected, atol=atol)
866
+
867
+
868
+ def test_properties(xp):
869
+ atol = 1e-12
870
+
871
+ # Test rotation and translation properties for single transform
872
+ r = Rotation.from_euler('z', xp.asarray(90), degrees=True)
873
+ t = xp.asarray([1.0, 2, 3])
874
+ tf = RigidTransform.from_components(t, r)
875
+
876
+ xp_assert_close(tf.rotation.as_matrix(), r.as_matrix(), atol=atol)
877
+ assert tf.rotation.approx_equal(r)
878
+ xp_assert_close(tf.translation, t, atol=atol)
879
+
880
+ # Test rotation and translation properties for multiple transforms
881
+ r = Rotation.from_euler('zyx', xp.asarray([[90, 0, 0], [0, 90, 0]]), degrees=True)
882
+ t = xp.asarray([[1.0, 2, 3], [4, 5, 6]])
883
+ tf = RigidTransform.from_components(t, r)
884
+
885
+ xp_assert_close(tf.rotation.as_matrix(), r.as_matrix(), atol=atol)
886
+ assert all(tf.rotation.approx_equal(r))
887
+ xp_assert_close(tf.translation, t, atol=atol)
888
+
889
+
890
+ def test_indexing(xp):
891
+ atol = 1e-12
892
+
893
+ # Test indexing for multiple transforms
894
+ r = Rotation.from_euler('zyx', xp.asarray([[90, 0, 0], [0, 90, 0]]), degrees=True)
895
+ t = xp.asarray([[1.0, 2, 3], [4, 5, 6]])
896
+ tf = RigidTransform.from_components(t, r)
897
+
898
+ # Test single index
899
+ xp_assert_close(tf[0].as_matrix()[:3, :3], r[0].as_matrix(), atol=atol)
900
+ xp_assert_close(tf[0].as_matrix()[:3, 3], t[0, ...], atol=atol)
901
+
902
+ # Test slice
903
+ tf_slice = tf[0:2]
904
+ xp_assert_close(tf_slice.as_matrix()[:, :3, :3], r[0:2].as_matrix(), atol=atol)
905
+ xp_assert_close(tf_slice.as_matrix()[:, :3, 3], t[0:2, ...], atol=atol)
906
+
907
+ # Test boolean indexing
908
+ tf_masked = tf[xp.asarray([True, True])]
909
+ xp_assert_close(tf_masked.as_matrix()[:, :3, :3], r.as_matrix(), atol=atol)
910
+ xp_assert_close(tf_masked.as_matrix()[:, :3, 3], t, atol=atol)
911
+
912
+ tf_masked = tf[xp.asarray([False, True])]
913
+ xp_assert_close(tf_masked.as_matrix()[:, :3, :3],
914
+ r[xp.asarray([False, True])].as_matrix(), atol=atol)
915
+ xp_assert_close(tf_masked.as_matrix()[:, :3, 3], t[xp.asarray([False, True])],
916
+ atol=atol)
917
+
918
+ tf_masked = tf[xp.asarray([False, False])]
919
+ assert len(tf_masked) == 0
920
+
921
+
922
+ def test_indexing_array_like():
923
+ atol = 1e-12
924
+
925
+ r = Rotation.from_euler('zyx', np.array([[90, 0, 0], [0, 90, 0]]), degrees=True)
926
+ t = np.array([[1.0, 2, 3], [4, 5, 6]])
927
+ tf = RigidTransform.from_components(t, r)
928
+
929
+ tf_masked = tf[[False, True]]
930
+ xp_assert_close(tf_masked.as_matrix()[:, :3, :3], r[[False, True]].as_matrix(),
931
+ atol=atol)
932
+ xp_assert_close(tf_masked.as_matrix()[:, :3, 3], t[[False, True]], atol=atol)
933
+ tf_masked = tf[[False, False]]
934
+ assert len(tf_masked) == 0
935
+
936
+
937
+ def test_concatenate(xp):
938
+ atol = 1e-12
939
+
940
+ # Test concatenation of transforms
941
+ t1 = xp.asarray([1, 0, 0])
942
+ r1 = Rotation.from_euler('z', xp.asarray(90), degrees=True)
943
+ tf1 = RigidTransform.from_components(t1, r1)
944
+
945
+ t2 = xp.asarray([0, 1, 0])
946
+ r2 = Rotation.from_euler('x', xp.asarray(90), degrees=True)
947
+ tf2 = RigidTransform.from_components(t2, r2)
948
+
949
+ # Concatenate single transforms
950
+ concatenated1 = RigidTransform.concatenate([tf1, tf2])
951
+ xp_assert_close(concatenated1[0].as_matrix(), tf1.as_matrix(), atol=atol)
952
+ xp_assert_close(concatenated1[1].as_matrix(), tf2.as_matrix(), atol=atol)
953
+
954
+ # Concatenate multiple transforms
955
+ concatenated2 = RigidTransform.concatenate([tf1, concatenated1])
956
+ xp_assert_close(concatenated2[0].as_matrix(), tf1.as_matrix(), atol=atol)
957
+ xp_assert_close(concatenated2[1].as_matrix(), tf1.as_matrix(), atol=atol)
958
+ xp_assert_close(concatenated2[2].as_matrix(), tf2.as_matrix(), atol=atol)
959
+
960
+
961
+ def test_input_validation(xp):
962
+ # Test invalid matrix shapes
963
+ inputs = [xp.eye(3), xp.zeros((4, 3)), [], xp.zeros((1, 1, 4, 4))]
964
+ for input in inputs:
965
+ with pytest.raises(ValueError, match="Expected `matrix` to have shape"):
966
+ RigidTransform.from_matrix(input)
967
+
968
+ # Test invalid last row
969
+ matrix = xp.eye(4)
970
+ matrix = xpx.at(matrix)[3, :].set(xp.asarray([1, 0, 0, 1]))
971
+ if is_lazy_array(matrix):
972
+ matrix = RigidTransform.from_matrix(matrix).as_matrix()
973
+ assert xp.all(xp.isnan(matrix))
974
+ else:
975
+ with pytest.raises(ValueError, match="last row of transformation matrix 0"):
976
+ RigidTransform.from_matrix(matrix)
977
+
978
+ # Test invalid last row for multiple transforms
979
+ matrix = xp.zeros((2, 4, 4))
980
+ matrix = xpx.at(matrix)[...].set(xp.eye(4))
981
+ matrix = xpx.at(matrix)[1, 3, :].set(xp.asarray([1, 0, 0, 1]))
982
+ if is_lazy_array(matrix):
983
+ matrix = RigidTransform.from_matrix(matrix).as_matrix()
984
+ assert not xp.any(xp.isnan(matrix[0, ...]))
985
+ assert xp.all(xp.isnan(matrix[1, ...]))
986
+ else:
987
+ with pytest.raises(ValueError, match="last row of transformation matrix 1"):
988
+ RigidTransform.from_matrix(matrix)
989
+
990
+ # Test left handed rotation matrix
991
+ matrix = xp.eye(4)
992
+ matrix = xpx.at(matrix)[0, 0].set(-1)
993
+ if is_lazy_array(matrix):
994
+ matrix = RigidTransform.from_matrix(matrix).as_matrix()
995
+ assert xp.all(xp.isnan(matrix[..., :3, :3]))
996
+ else:
997
+ with pytest.raises(ValueError, match="Non-positive determinant"):
998
+ RigidTransform(matrix, normalize=True)
999
+
1000
+ # Test non-Rotation input
1001
+ with pytest.raises(ValueError,
1002
+ match="Expected `rotation` to be a `Rotation` instance"):
1003
+ RigidTransform.from_rotation(xp.eye(3))
1004
+
1005
+
1006
+ def test_translation_validation(xp):
1007
+ # Test invalid translation shapes
1008
+ with pytest.raises(ValueError, match="Expected `translation` to have shape"):
1009
+ RigidTransform.from_translation(xp.asarray([1, 2]))
1010
+
1011
+ with pytest.raises(ValueError, match="Expected `translation` to have shape"):
1012
+ RigidTransform.from_translation(xp.zeros((2, 2)))
1013
+
1014
+ with pytest.raises(ValueError, match="Expected `translation` to have shape"):
1015
+ RigidTransform.from_translation(xp.zeros((1, 1, 3)))
1016
+
1017
+
1018
+ def test_vector_validation(xp):
1019
+ tf = rigid_transform_to_xp(RigidTransform.identity(2), xp=xp)
1020
+
1021
+ # Test invalid vector shapes
1022
+ with pytest.raises(ValueError, match="Expected vector to have shape"):
1023
+ tf.apply(xp.asarray([1, 2]))
1024
+
1025
+ with pytest.raises(ValueError, match="Expected vector to have shape"):
1026
+ tf.apply(xp.zeros((2, 2)))
1027
+
1028
+ with pytest.raises(ValueError, match="Expected vector to have shape"):
1029
+ tf.apply(xp.zeros((1, 1, 3)))
1030
+
1031
+
1032
+ def test_indexing_validation(xp):
1033
+ tf = RigidTransform.from_matrix(xp.eye(4))
1034
+
1035
+ # Test indexing on single transform
1036
+ with pytest.raises(TypeError, match="Single transform is not subscriptable"):
1037
+ tf[0]
1038
+
1039
+ with pytest.raises(TypeError, match="Single transform is not subscriptable"):
1040
+ tf[0:1]
1041
+
1042
+ # Test length on single transform
1043
+ with pytest.raises(TypeError, match="Single transform has no len"):
1044
+ len(tf)
1045
+
1046
+
1047
+ def test_composition_validation(xp):
1048
+ tf2 = RigidTransform.from_translation(xp.asarray([[1, 2, 3], [4, 5, 6]]))
1049
+ tf3 = RigidTransform.from_translation(xp.asarray([[1, 2, 3], [4, 5, 6], [7, 8, 9]]))
1050
+
1051
+ # Test incompatible shapes
1052
+ with pytest.raises(ValueError, match="Expected equal number of transforms"):
1053
+ tf2 * tf3
1054
+
1055
+
1056
+ def test_concatenate_validation(xp):
1057
+ tf = RigidTransform.from_matrix(xp.eye(4))
1058
+
1059
+ # Test invalid inputs
1060
+ with pytest.raises(TypeError,
1061
+ match="input must contain RigidTransform objects"):
1062
+ RigidTransform.concatenate([tf, xp.eye(4)])
1063
+
1064
+
1065
+ def test_setitem_validation(xp):
1066
+ tf = RigidTransform.from_translation(xp.asarray([[1, 2, 3], [4, 5, 6]]))
1067
+ single = RigidTransform.from_matrix(xp.eye(4))
1068
+
1069
+ # Test setting item on single transform
1070
+ with pytest.raises(TypeError, match="Single transform is not subscriptable"):
1071
+ single[0] = tf
1072
+
1073
+ # Test invalid value type
1074
+ with pytest.raises(TypeError, match="value must be a RigidTransform"):
1075
+ tf[0] = xp.eye(4)
1076
+
1077
+
1078
+ @pytest.mark.skip_xp_backends("jax.numpy",
1079
+ reason="JAX does not support memory sharing")
1080
+ def test_copy_flag(xp):
1081
+ # Test that copy=True creates new memory
1082
+ matrix = xp.eye(4)
1083
+ tf = RigidTransform(matrix, normalize=False, copy=True)
1084
+ matrix[0, 0] = 2
1085
+ assert tf.as_matrix()[0, 0] == 1
1086
+
1087
+ # Test that copy=False shares memory
1088
+ matrix = xp.eye(4)
1089
+ tf = RigidTransform(matrix, normalize=False, copy=False)
1090
+ matrix[0, 0] = 2
1091
+ assert tf.as_matrix()[0, 0] == 2
1092
+
1093
+
1094
+ def test_normalize_dual_quaternion(xp):
1095
+ dual_quat = normalize_dual_quaternion(xp.zeros((1, 8)))
1096
+ xp_assert_close(xp_vector_norm(dual_quat[0, :4], axis=-1), xp.asarray(1.0)[()],
1097
+ atol=1e-12)
1098
+ xp_assert_close(xp.vecdot(dual_quat[0, :4], dual_quat[0, 4:])[()],
1099
+ xp.asarray(0.0)[()], atol=1e-12)
1100
+
1101
+ rng = np.random.default_rng(103213650)
1102
+ dual_quat = xp.asarray(rng.normal(size=(1000, 8)))
1103
+ dual_quat = normalize_dual_quaternion(dual_quat)
1104
+ expected = xp.ones(dual_quat.shape[0])
1105
+ xp_assert_close(xp_vector_norm(dual_quat[:, :4], axis=-1), expected, atol=1e-12)
1106
+ expected = xp.zeros(dual_quat.shape[0])
1107
+ xp_assert_close(xp.vecdot(dual_quat[:, :4], dual_quat[:, 4:]), expected, atol=1e-12)
1108
+
1109
+
1110
+ def test_empty_transform_construction(xp):
1111
+ tf = RigidTransform.from_matrix(xp.empty((0, 4, 4)))
1112
+ assert len(tf) == 0
1113
+ assert not tf.single
1114
+
1115
+ tf = RigidTransform.from_rotation(Rotation.from_quat(xp.zeros((0, 4))))
1116
+ assert len(tf) == 0
1117
+ assert not tf.single
1118
+
1119
+ tf = RigidTransform.from_translation(xp.empty((0, 3)))
1120
+ assert len(tf) == 0
1121
+ assert not tf.single
1122
+
1123
+ empty_rot = Rotation.from_quat(xp.zeros((0, 4)))
1124
+ tf = RigidTransform.from_components(xp.empty((0, 3)), empty_rot)
1125
+ assert len(tf) == 0
1126
+ assert not tf.single
1127
+
1128
+ tf = RigidTransform.from_exp_coords(xp.empty((0, 6)))
1129
+ assert len(tf) == 0
1130
+ assert not tf.single
1131
+
1132
+ tf = RigidTransform.from_dual_quat(xp.empty((0, 8)))
1133
+ assert len(tf) == 0
1134
+ assert not tf.single
1135
+
1136
+ tf = RigidTransform.identity(0)
1137
+ assert len(tf) == 0
1138
+ assert not tf.single
1139
+
1140
+
1141
+ def test_empty_transform_representation(xp):
1142
+ tf = RigidTransform.from_matrix(xp.empty((0, 4, 4)))
1143
+
1144
+ assert len(tf.rotation) == 0
1145
+ assert tf.translation.shape == (0, 3)
1146
+
1147
+ t, r = tf.as_components()
1148
+ assert t.shape == (0, 3)
1149
+ assert len(r) == 0
1150
+
1151
+ assert tf.as_matrix().shape == (0, 4, 4)
1152
+ assert tf.as_exp_coords().shape == (0, 6)
1153
+ assert tf.as_dual_quat().shape == (0, 8)
1154
+
1155
+
1156
+ def test_empty_transform_application(xp):
1157
+ tf = RigidTransform.from_matrix(xp.empty((0, 4, 4)))
1158
+
1159
+ assert tf.apply(xp.zeros((3,))).shape == (0, 3)
1160
+ assert tf.apply(xp.empty((0, 3))).shape == (0, 3)
1161
+
1162
+ with pytest.raises(ValueError, match="operands could not be broadcast together"):
1163
+ tf.apply(xp.zeros((2, 3)))
1164
+
1165
+
1166
+ def test_empty_transform_composition(xp):
1167
+ tf_empty = RigidTransform.from_matrix(xp.empty((0, 4, 4)))
1168
+ tf_single = RigidTransform.from_matrix(xp.eye(4))
1169
+ tf_many = rigid_transform_to_xp(RigidTransform.identity(3), xp=xp)
1170
+
1171
+ assert len(tf_empty * tf_empty) == 0
1172
+ assert len(tf_empty * tf_single) == 0
1173
+ assert len(tf_single * tf_empty) == 0
1174
+
1175
+ with pytest.raises(ValueError, match="Expected equal number of transforms"):
1176
+ tf_many * tf_empty
1177
+
1178
+ with pytest.raises(ValueError, match="Expected equal number of transforms"):
1179
+ tf_empty * tf_many
1180
+
1181
+
1182
+ def test_empty_transform_concatenation(xp):
1183
+ tf_empty = RigidTransform.from_matrix(xp.empty((0, 4, 4)))
1184
+ tf_single = RigidTransform.from_matrix(xp.eye(4))
1185
+ tf_many = rigid_transform_to_xp(RigidTransform.identity(2), xp=xp)
1186
+
1187
+ assert len(RigidTransform.concatenate([tf_empty, tf_empty])) == 0
1188
+ assert len(RigidTransform.concatenate([tf_empty, tf_single])) == 1
1189
+ assert len(RigidTransform.concatenate([tf_single, tf_empty])) == 1
1190
+ assert len(RigidTransform.concatenate([tf_empty, tf_many])) == 2
1191
+ assert len(RigidTransform.concatenate([tf_many, tf_empty])) == 2
1192
+ assert len(RigidTransform.concatenate([tf_many, tf_empty, tf_single])) == 3
1193
+
1194
+
1195
+ def test_empty_transform_inv_and_pow(xp):
1196
+ tf = RigidTransform.from_matrix(xp.empty((0, 4, 4)))
1197
+ assert len(tf.inv()) == 0
1198
+ assert len(tf ** 0) == 0
1199
+ assert len(tf ** 1) == 0
1200
+ assert len(tf ** -1) == 0
1201
+ assert len(tf ** 0.5) == 0
1202
+
1203
+
1204
+ def test_empty_transform_indexing(xp):
1205
+ tf_many = rigid_transform_to_xp(RigidTransform.identity(3), xp=xp)
1206
+ tf_zero = tf_many[xp.asarray([], dtype=xp.int64)]
1207
+ assert len(tf_zero) == 0
1208
+
1209
+ assert len(tf_zero[xp.asarray([], dtype=xp.int64)]) == 0
1210
+ # Array API does not specify out-of-bounds indexing. Only check for numpy.
1211
+ if is_numpy(xp):
1212
+ assert len(tf_zero[:5]) == 0 # Slices can go out of bounds.
1213
+
1214
+ with pytest.raises(IndexError):
1215
+ tf_zero[0]
1216
+
1217
+ with pytest.raises(IndexError):
1218
+ tf_zero[xp.asarray([0, 2])]
1219
+
1220
+ with pytest.raises(IndexError):
1221
+ tf_zero[xp.asarray([False, True])]