scipy 1.15.3__cp312-cp312-macosx_12_0_arm64.whl → 1.16.0rc2__cp312-cp312-macosx_12_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 (629) hide show
  1. scipy/.dylibs/libscipy_openblas.dylib +0 -0
  2. scipy/__config__.py +8 -8
  3. scipy/__init__.py +3 -6
  4. scipy/_cyutility.cpython-312-darwin.so +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.cpython-312-darwin.so +0 -0
  9. scipy/_lib/_docscrape.py +1 -1
  10. scipy/_lib/_elementwise_iterative_method.py +15 -26
  11. scipy/_lib/_sparse.py +41 -0
  12. scipy/_lib/_test_deprecation_call.cpython-312-darwin.so +0 -0
  13. scipy/_lib/_test_deprecation_def.cpython-312-darwin.so +0 -0
  14. scipy/_lib/_testutils.py +6 -2
  15. scipy/_lib/_util.py +222 -125
  16. scipy/_lib/array_api_compat/__init__.py +4 -4
  17. scipy/_lib/array_api_compat/_internal.py +19 -6
  18. scipy/_lib/array_api_compat/common/__init__.py +1 -1
  19. scipy/_lib/array_api_compat/common/_aliases.py +365 -193
  20. scipy/_lib/array_api_compat/common/_fft.py +94 -64
  21. scipy/_lib/array_api_compat/common/_helpers.py +413 -180
  22. scipy/_lib/array_api_compat/common/_linalg.py +116 -40
  23. scipy/_lib/array_api_compat/common/_typing.py +179 -10
  24. scipy/_lib/array_api_compat/cupy/__init__.py +1 -4
  25. scipy/_lib/array_api_compat/cupy/_aliases.py +61 -41
  26. scipy/_lib/array_api_compat/cupy/_info.py +16 -6
  27. scipy/_lib/array_api_compat/cupy/_typing.py +24 -39
  28. scipy/_lib/array_api_compat/dask/array/__init__.py +6 -3
  29. scipy/_lib/array_api_compat/dask/array/_aliases.py +267 -108
  30. scipy/_lib/array_api_compat/dask/array/_info.py +105 -34
  31. scipy/_lib/array_api_compat/dask/array/fft.py +5 -8
  32. scipy/_lib/array_api_compat/dask/array/linalg.py +21 -22
  33. scipy/_lib/array_api_compat/numpy/__init__.py +13 -15
  34. scipy/_lib/array_api_compat/numpy/_aliases.py +98 -49
  35. scipy/_lib/array_api_compat/numpy/_info.py +36 -16
  36. scipy/_lib/array_api_compat/numpy/_typing.py +27 -43
  37. scipy/_lib/array_api_compat/numpy/fft.py +11 -5
  38. scipy/_lib/array_api_compat/numpy/linalg.py +75 -22
  39. scipy/_lib/array_api_compat/torch/__init__.py +3 -5
  40. scipy/_lib/array_api_compat/torch/_aliases.py +262 -159
  41. scipy/_lib/array_api_compat/torch/_info.py +27 -16
  42. scipy/_lib/array_api_compat/torch/_typing.py +3 -0
  43. scipy/_lib/array_api_compat/torch/fft.py +17 -18
  44. scipy/_lib/array_api_compat/torch/linalg.py +16 -16
  45. scipy/_lib/array_api_extra/__init__.py +26 -3
  46. scipy/_lib/array_api_extra/_delegation.py +171 -0
  47. scipy/_lib/array_api_extra/_lib/__init__.py +1 -0
  48. scipy/_lib/array_api_extra/_lib/_at.py +463 -0
  49. scipy/_lib/array_api_extra/_lib/_backends.py +46 -0
  50. scipy/_lib/array_api_extra/_lib/_funcs.py +937 -0
  51. scipy/_lib/array_api_extra/_lib/_lazy.py +357 -0
  52. scipy/_lib/array_api_extra/_lib/_testing.py +278 -0
  53. scipy/_lib/array_api_extra/_lib/_utils/__init__.py +1 -0
  54. scipy/_lib/array_api_extra/_lib/_utils/_compat.py +74 -0
  55. scipy/_lib/array_api_extra/_lib/_utils/_compat.pyi +45 -0
  56. scipy/_lib/array_api_extra/_lib/_utils/_helpers.py +559 -0
  57. scipy/_lib/array_api_extra/_lib/_utils/_typing.py +10 -0
  58. scipy/_lib/array_api_extra/_lib/_utils/_typing.pyi +105 -0
  59. scipy/_lib/array_api_extra/testing.py +359 -0
  60. scipy/_lib/decorator.py +2 -2
  61. scipy/_lib/doccer.py +1 -7
  62. scipy/_lib/messagestream.cpython-312-darwin.so +0 -0
  63. scipy/_lib/pyprima/__init__.py +212 -0
  64. scipy/_lib/pyprima/cobyla/__init__.py +0 -0
  65. scipy/_lib/pyprima/cobyla/cobyla.py +559 -0
  66. scipy/_lib/pyprima/cobyla/cobylb.py +714 -0
  67. scipy/_lib/pyprima/cobyla/geometry.py +226 -0
  68. scipy/_lib/pyprima/cobyla/initialize.py +215 -0
  69. scipy/_lib/pyprima/cobyla/trustregion.py +492 -0
  70. scipy/_lib/pyprima/cobyla/update.py +289 -0
  71. scipy/_lib/pyprima/common/__init__.py +0 -0
  72. scipy/_lib/pyprima/common/_bounds.py +34 -0
  73. scipy/_lib/pyprima/common/_linear_constraints.py +46 -0
  74. scipy/_lib/pyprima/common/_nonlinear_constraints.py +54 -0
  75. scipy/_lib/pyprima/common/_project.py +173 -0
  76. scipy/_lib/pyprima/common/checkbreak.py +93 -0
  77. scipy/_lib/pyprima/common/consts.py +47 -0
  78. scipy/_lib/pyprima/common/evaluate.py +99 -0
  79. scipy/_lib/pyprima/common/history.py +38 -0
  80. scipy/_lib/pyprima/common/infos.py +30 -0
  81. scipy/_lib/pyprima/common/linalg.py +435 -0
  82. scipy/_lib/pyprima/common/message.py +290 -0
  83. scipy/_lib/pyprima/common/powalg.py +131 -0
  84. scipy/_lib/pyprima/common/preproc.py +277 -0
  85. scipy/_lib/pyprima/common/present.py +5 -0
  86. scipy/_lib/pyprima/common/ratio.py +54 -0
  87. scipy/_lib/pyprima/common/redrho.py +47 -0
  88. scipy/_lib/pyprima/common/selectx.py +296 -0
  89. scipy/_lib/tests/test__util.py +105 -121
  90. scipy/_lib/tests/test_array_api.py +166 -35
  91. scipy/_lib/tests/test_bunch.py +7 -0
  92. scipy/_lib/tests/test_ccallback.py +2 -10
  93. scipy/_lib/tests/test_public_api.py +13 -0
  94. scipy/cluster/_hierarchy.cpython-312-darwin.so +0 -0
  95. scipy/cluster/_optimal_leaf_ordering.cpython-312-darwin.so +0 -0
  96. scipy/cluster/_vq.cpython-312-darwin.so +0 -0
  97. scipy/cluster/hierarchy.py +393 -223
  98. scipy/cluster/tests/test_hierarchy.py +273 -335
  99. scipy/cluster/tests/test_vq.py +45 -61
  100. scipy/cluster/vq.py +39 -35
  101. scipy/conftest.py +263 -157
  102. scipy/constants/_constants.py +4 -1
  103. scipy/constants/tests/test_codata.py +2 -2
  104. scipy/constants/tests/test_constants.py +11 -18
  105. scipy/datasets/_download_all.py +15 -1
  106. scipy/datasets/_fetchers.py +7 -1
  107. scipy/datasets/_utils.py +1 -1
  108. scipy/differentiate/_differentiate.py +25 -25
  109. scipy/differentiate/tests/test_differentiate.py +24 -25
  110. scipy/fft/_basic.py +20 -0
  111. scipy/fft/_helper.py +3 -34
  112. scipy/fft/_pocketfft/helper.py +29 -1
  113. scipy/fft/_pocketfft/tests/test_basic.py +2 -4
  114. scipy/fft/_pocketfft/tests/test_real_transforms.py +4 -4
  115. scipy/fft/_realtransforms.py +13 -0
  116. scipy/fft/tests/test_basic.py +27 -25
  117. scipy/fft/tests/test_fftlog.py +16 -7
  118. scipy/fft/tests/test_helper.py +18 -34
  119. scipy/fft/tests/test_real_transforms.py +8 -10
  120. scipy/fftpack/convolve.cpython-312-darwin.so +0 -0
  121. scipy/fftpack/tests/test_basic.py +2 -4
  122. scipy/fftpack/tests/test_real_transforms.py +8 -9
  123. scipy/integrate/_bvp.py +9 -3
  124. scipy/integrate/_cubature.py +3 -2
  125. scipy/integrate/_dop.cpython-312-darwin.so +0 -0
  126. scipy/integrate/_lsoda.cpython-312-darwin.so +0 -0
  127. scipy/integrate/_ode.py +9 -2
  128. scipy/integrate/_odepack.cpython-312-darwin.so +0 -0
  129. scipy/integrate/_quad_vec.py +21 -29
  130. scipy/integrate/_quadpack.cpython-312-darwin.so +0 -0
  131. scipy/integrate/_quadpack_py.py +11 -7
  132. scipy/integrate/_quadrature.py +3 -3
  133. scipy/integrate/_rules/_base.py +2 -2
  134. scipy/integrate/_tanhsinh.py +48 -47
  135. scipy/integrate/_test_odeint_banded.cpython-312-darwin.so +0 -0
  136. scipy/integrate/_vode.cpython-312-darwin.so +0 -0
  137. scipy/integrate/tests/test__quad_vec.py +0 -6
  138. scipy/integrate/tests/test_banded_ode_solvers.py +85 -0
  139. scipy/integrate/tests/test_cubature.py +21 -35
  140. scipy/integrate/tests/test_quadrature.py +6 -8
  141. scipy/integrate/tests/test_tanhsinh.py +56 -48
  142. scipy/interpolate/__init__.py +70 -58
  143. scipy/interpolate/_bary_rational.py +22 -22
  144. scipy/interpolate/_bsplines.py +119 -66
  145. scipy/interpolate/_cubic.py +65 -50
  146. scipy/interpolate/_dfitpack.cpython-312-darwin.so +0 -0
  147. scipy/interpolate/_dierckx.cpython-312-darwin.so +0 -0
  148. scipy/interpolate/_fitpack.cpython-312-darwin.so +0 -0
  149. scipy/interpolate/_fitpack2.py +9 -6
  150. scipy/interpolate/_fitpack_impl.py +32 -26
  151. scipy/interpolate/_fitpack_repro.py +23 -19
  152. scipy/interpolate/_interpnd.cpython-312-darwin.so +0 -0
  153. scipy/interpolate/_interpolate.py +30 -12
  154. scipy/interpolate/_ndbspline.py +13 -18
  155. scipy/interpolate/_ndgriddata.py +5 -8
  156. scipy/interpolate/_polyint.py +95 -31
  157. scipy/interpolate/_ppoly.cpython-312-darwin.so +0 -0
  158. scipy/interpolate/_rbf.py +2 -2
  159. scipy/interpolate/_rbfinterp.py +1 -1
  160. scipy/interpolate/_rbfinterp_pythran.cpython-312-darwin.so +0 -0
  161. scipy/interpolate/_rgi.py +31 -26
  162. scipy/interpolate/_rgi_cython.cpython-312-darwin.so +0 -0
  163. scipy/interpolate/dfitpack.py +0 -20
  164. scipy/interpolate/interpnd.py +1 -2
  165. scipy/interpolate/tests/test_bary_rational.py +2 -2
  166. scipy/interpolate/tests/test_bsplines.py +97 -1
  167. scipy/interpolate/tests/test_fitpack2.py +39 -1
  168. scipy/interpolate/tests/test_interpnd.py +32 -20
  169. scipy/interpolate/tests/test_interpolate.py +48 -4
  170. scipy/interpolate/tests/test_rgi.py +2 -1
  171. scipy/io/_fast_matrix_market/__init__.py +2 -0
  172. scipy/io/_harwell_boeing/_fortran_format_parser.py +19 -16
  173. scipy/io/_harwell_boeing/hb.py +7 -11
  174. scipy/io/_idl.py +5 -7
  175. scipy/io/_netcdf.py +15 -5
  176. scipy/io/_test_fortran.cpython-312-darwin.so +0 -0
  177. scipy/io/arff/tests/test_arffread.py +3 -3
  178. scipy/io/matlab/__init__.py +5 -3
  179. scipy/io/matlab/_mio.py +4 -1
  180. scipy/io/matlab/_mio5.py +19 -13
  181. scipy/io/matlab/_mio5_utils.cpython-312-darwin.so +0 -0
  182. scipy/io/matlab/_mio_utils.cpython-312-darwin.so +0 -0
  183. scipy/io/matlab/_miobase.py +4 -1
  184. scipy/io/matlab/_streams.cpython-312-darwin.so +0 -0
  185. scipy/io/matlab/tests/test_mio.py +46 -18
  186. scipy/io/matlab/tests/test_mio_funcs.py +1 -1
  187. scipy/io/tests/test_mmio.py +7 -1
  188. scipy/io/tests/test_wavfile.py +41 -0
  189. scipy/io/wavfile.py +57 -10
  190. scipy/linalg/_basic.py +113 -86
  191. scipy/linalg/_cythonized_array_utils.cpython-312-darwin.so +0 -0
  192. scipy/linalg/_decomp.py +22 -9
  193. scipy/linalg/_decomp_cholesky.py +28 -13
  194. scipy/linalg/_decomp_cossin.py +45 -30
  195. scipy/linalg/_decomp_interpolative.cpython-312-darwin.so +0 -0
  196. scipy/linalg/_decomp_ldl.py +4 -1
  197. scipy/linalg/_decomp_lu.py +18 -6
  198. scipy/linalg/_decomp_lu_cython.cpython-312-darwin.so +0 -0
  199. scipy/linalg/_decomp_polar.py +2 -0
  200. scipy/linalg/_decomp_qr.py +6 -2
  201. scipy/linalg/_decomp_qz.py +3 -0
  202. scipy/linalg/_decomp_schur.py +3 -1
  203. scipy/linalg/_decomp_svd.py +13 -2
  204. scipy/linalg/_decomp_update.cpython-312-darwin.so +0 -0
  205. scipy/linalg/_expm_frechet.py +4 -0
  206. scipy/linalg/_fblas.cpython-312-darwin.so +0 -0
  207. scipy/linalg/_flapack.cpython-312-darwin.so +0 -0
  208. scipy/linalg/_linalg_pythran.cpython-312-darwin.so +0 -0
  209. scipy/linalg/_matfuncs.py +187 -4
  210. scipy/linalg/_matfuncs_expm.cpython-312-darwin.so +0 -0
  211. scipy/linalg/_matfuncs_schur_sqrtm.cpython-312-darwin.so +0 -0
  212. scipy/linalg/_matfuncs_sqrtm.py +1 -99
  213. scipy/linalg/_matfuncs_sqrtm_triu.cpython-312-darwin.so +0 -0
  214. scipy/linalg/_procrustes.py +2 -0
  215. scipy/linalg/_sketches.py +17 -6
  216. scipy/linalg/_solve_toeplitz.cpython-312-darwin.so +0 -0
  217. scipy/linalg/_solvers.py +7 -2
  218. scipy/linalg/_special_matrices.py +26 -36
  219. scipy/linalg/cython_blas.cpython-312-darwin.so +0 -0
  220. scipy/linalg/cython_lapack.cpython-312-darwin.so +0 -0
  221. scipy/linalg/lapack.py +22 -2
  222. scipy/linalg/tests/_cython_examples/meson.build +7 -0
  223. scipy/linalg/tests/test_basic.py +31 -16
  224. scipy/linalg/tests/test_batch.py +588 -0
  225. scipy/linalg/tests/test_cythonized_array_utils.py +0 -2
  226. scipy/linalg/tests/test_decomp.py +40 -3
  227. scipy/linalg/tests/test_decomp_cossin.py +14 -0
  228. scipy/linalg/tests/test_decomp_ldl.py +1 -1
  229. scipy/linalg/tests/test_lapack.py +115 -7
  230. scipy/linalg/tests/test_matfuncs.py +157 -102
  231. scipy/linalg/tests/test_procrustes.py +0 -7
  232. scipy/linalg/tests/test_solve_toeplitz.py +1 -1
  233. scipy/linalg/tests/test_special_matrices.py +1 -5
  234. scipy/ndimage/__init__.py +1 -0
  235. scipy/ndimage/_cytest.cpython-312-darwin.so +0 -0
  236. scipy/ndimage/_delegators.py +8 -2
  237. scipy/ndimage/_filters.py +453 -5
  238. scipy/ndimage/_interpolation.py +36 -6
  239. scipy/ndimage/_measurements.py +4 -2
  240. scipy/ndimage/_morphology.py +5 -0
  241. scipy/ndimage/_nd_image.cpython-312-darwin.so +0 -0
  242. scipy/ndimage/_ni_docstrings.py +5 -1
  243. scipy/ndimage/_ni_label.cpython-312-darwin.so +0 -0
  244. scipy/ndimage/_ni_support.py +1 -5
  245. scipy/ndimage/_rank_filter_1d.cpython-312-darwin.so +0 -0
  246. scipy/ndimage/_support_alternative_backends.py +18 -6
  247. scipy/ndimage/tests/test_filters.py +370 -259
  248. scipy/ndimage/tests/test_fourier.py +7 -9
  249. scipy/ndimage/tests/test_interpolation.py +68 -61
  250. scipy/ndimage/tests/test_measurements.py +18 -35
  251. scipy/ndimage/tests/test_morphology.py +143 -131
  252. scipy/ndimage/tests/test_splines.py +1 -3
  253. scipy/odr/__odrpack.cpython-312-darwin.so +0 -0
  254. scipy/optimize/_basinhopping.py +13 -7
  255. scipy/optimize/_bglu_dense.cpython-312-darwin.so +0 -0
  256. scipy/optimize/_bracket.py +17 -24
  257. scipy/optimize/_chandrupatla.py +9 -10
  258. scipy/optimize/_cobyla_py.py +104 -123
  259. scipy/optimize/_constraints.py +14 -10
  260. scipy/optimize/_differentiable_functions.py +371 -230
  261. scipy/optimize/_differentialevolution.py +4 -3
  262. scipy/optimize/_direct.cpython-312-darwin.so +0 -0
  263. scipy/optimize/_dual_annealing.py +1 -1
  264. scipy/optimize/_elementwise.py +1 -4
  265. scipy/optimize/_group_columns.cpython-312-darwin.so +0 -0
  266. scipy/optimize/_lbfgsb.cpython-312-darwin.so +0 -0
  267. scipy/optimize/_lbfgsb_py.py +57 -16
  268. scipy/optimize/_linprog_doc.py +2 -2
  269. scipy/optimize/_linprog_highs.py +2 -2
  270. scipy/optimize/_linprog_ip.py +25 -10
  271. scipy/optimize/_linprog_util.py +14 -16
  272. scipy/optimize/_lsap.cpython-312-darwin.so +0 -0
  273. scipy/optimize/_lsq/common.py +3 -3
  274. scipy/optimize/_lsq/dogbox.py +16 -2
  275. scipy/optimize/_lsq/givens_elimination.cpython-312-darwin.so +0 -0
  276. scipy/optimize/_lsq/least_squares.py +198 -126
  277. scipy/optimize/_lsq/lsq_linear.py +6 -6
  278. scipy/optimize/_lsq/trf.py +35 -8
  279. scipy/optimize/_milp.py +3 -1
  280. scipy/optimize/_minimize.py +105 -36
  281. scipy/optimize/_minpack.cpython-312-darwin.so +0 -0
  282. scipy/optimize/_minpack_py.py +21 -14
  283. scipy/optimize/_moduleTNC.cpython-312-darwin.so +0 -0
  284. scipy/optimize/_nnls.py +20 -21
  285. scipy/optimize/_nonlin.py +34 -3
  286. scipy/optimize/_numdiff.py +288 -110
  287. scipy/optimize/_optimize.py +86 -48
  288. scipy/optimize/_pava_pybind.cpython-312-darwin.so +0 -0
  289. scipy/optimize/_remove_redundancy.py +5 -5
  290. scipy/optimize/_root_scalar.py +1 -1
  291. scipy/optimize/_shgo.py +6 -0
  292. scipy/optimize/_shgo_lib/_complex.py +1 -1
  293. scipy/optimize/_slsqp_py.py +216 -124
  294. scipy/optimize/_slsqplib.cpython-312-darwin.so +0 -0
  295. scipy/optimize/_spectral.py +1 -1
  296. scipy/optimize/_tnc.py +8 -1
  297. scipy/optimize/_trlib/_trlib.cpython-312-darwin.so +0 -0
  298. scipy/optimize/_trustregion.py +20 -6
  299. scipy/optimize/_trustregion_constr/canonical_constraint.py +7 -7
  300. scipy/optimize/_trustregion_constr/equality_constrained_sqp.py +1 -1
  301. scipy/optimize/_trustregion_constr/minimize_trustregion_constr.py +11 -3
  302. scipy/optimize/_trustregion_constr/projections.py +12 -8
  303. scipy/optimize/_trustregion_constr/qp_subproblem.py +9 -9
  304. scipy/optimize/_trustregion_constr/tests/test_projections.py +7 -7
  305. scipy/optimize/_trustregion_constr/tests/test_qp_subproblem.py +77 -77
  306. scipy/optimize/_trustregion_constr/tr_interior_point.py +5 -5
  307. scipy/optimize/_trustregion_exact.py +0 -1
  308. scipy/optimize/_zeros.cpython-312-darwin.so +0 -0
  309. scipy/optimize/_zeros_py.py +97 -17
  310. scipy/optimize/cython_optimize/_zeros.cpython-312-darwin.so +0 -0
  311. scipy/optimize/slsqp.py +0 -1
  312. scipy/optimize/tests/test__basinhopping.py +1 -1
  313. scipy/optimize/tests/test__differential_evolution.py +4 -4
  314. scipy/optimize/tests/test__linprog_clean_inputs.py +5 -3
  315. scipy/optimize/tests/test__numdiff.py +66 -22
  316. scipy/optimize/tests/test__remove_redundancy.py +2 -2
  317. scipy/optimize/tests/test__shgo.py +9 -1
  318. scipy/optimize/tests/test_bracket.py +36 -46
  319. scipy/optimize/tests/test_chandrupatla.py +133 -135
  320. scipy/optimize/tests/test_cobyla.py +74 -45
  321. scipy/optimize/tests/test_constraints.py +1 -1
  322. scipy/optimize/tests/test_differentiable_functions.py +226 -6
  323. scipy/optimize/tests/test_lbfgsb_hessinv.py +22 -0
  324. scipy/optimize/tests/test_least_squares.py +125 -13
  325. scipy/optimize/tests/test_linear_assignment.py +3 -3
  326. scipy/optimize/tests/test_linprog.py +3 -3
  327. scipy/optimize/tests/test_lsq_linear.py +6 -6
  328. scipy/optimize/tests/test_minimize_constrained.py +2 -2
  329. scipy/optimize/tests/test_minpack.py +4 -4
  330. scipy/optimize/tests/test_nnls.py +43 -3
  331. scipy/optimize/tests/test_nonlin.py +36 -0
  332. scipy/optimize/tests/test_optimize.py +95 -17
  333. scipy/optimize/tests/test_slsqp.py +36 -4
  334. scipy/optimize/tests/test_zeros.py +34 -1
  335. scipy/signal/__init__.py +12 -23
  336. scipy/signal/_delegators.py +568 -0
  337. scipy/signal/_filter_design.py +459 -241
  338. scipy/signal/_fir_filter_design.py +262 -90
  339. scipy/signal/_lti_conversion.py +3 -2
  340. scipy/signal/_ltisys.py +118 -91
  341. scipy/signal/_max_len_seq_inner.cpython-312-darwin.so +0 -0
  342. scipy/signal/_peak_finding_utils.cpython-312-darwin.so +0 -0
  343. scipy/signal/_polyutils.py +172 -0
  344. scipy/signal/_short_time_fft.py +519 -70
  345. scipy/signal/_signal_api.py +30 -0
  346. scipy/signal/_signaltools.py +719 -399
  347. scipy/signal/_sigtools.cpython-312-darwin.so +0 -0
  348. scipy/signal/_sosfilt.cpython-312-darwin.so +0 -0
  349. scipy/signal/_spectral_py.py +230 -50
  350. scipy/signal/_spline.cpython-312-darwin.so +0 -0
  351. scipy/signal/_spline_filters.py +108 -68
  352. scipy/signal/_support_alternative_backends.py +73 -0
  353. scipy/signal/_upfirdn.py +4 -1
  354. scipy/signal/_upfirdn_apply.cpython-312-darwin.so +0 -0
  355. scipy/signal/_waveforms.py +2 -11
  356. scipy/signal/_wavelets.py +1 -1
  357. scipy/signal/fir_filter_design.py +1 -0
  358. scipy/signal/spline.py +4 -11
  359. scipy/signal/tests/_scipy_spectral_test_shim.py +2 -171
  360. scipy/signal/tests/test_bsplines.py +114 -79
  361. scipy/signal/tests/test_cont2discrete.py +9 -2
  362. scipy/signal/tests/test_filter_design.py +721 -481
  363. scipy/signal/tests/test_fir_filter_design.py +332 -140
  364. scipy/signal/tests/test_savitzky_golay.py +4 -3
  365. scipy/signal/tests/test_short_time_fft.py +221 -3
  366. scipy/signal/tests/test_signaltools.py +2144 -1348
  367. scipy/signal/tests/test_spectral.py +50 -6
  368. scipy/signal/tests/test_splines.py +161 -96
  369. scipy/signal/tests/test_upfirdn.py +84 -50
  370. scipy/signal/tests/test_waveforms.py +20 -0
  371. scipy/signal/tests/test_windows.py +607 -466
  372. scipy/signal/windows/_windows.py +287 -148
  373. scipy/sparse/__init__.py +23 -4
  374. scipy/sparse/_base.py +270 -108
  375. scipy/sparse/_bsr.py +7 -4
  376. scipy/sparse/_compressed.py +59 -231
  377. scipy/sparse/_construct.py +90 -38
  378. scipy/sparse/_coo.py +115 -181
  379. scipy/sparse/_csc.py +4 -4
  380. scipy/sparse/_csparsetools.cpython-312-darwin.so +0 -0
  381. scipy/sparse/_csr.py +2 -2
  382. scipy/sparse/_data.py +48 -48
  383. scipy/sparse/_dia.py +105 -18
  384. scipy/sparse/_dok.py +0 -23
  385. scipy/sparse/_index.py +4 -4
  386. scipy/sparse/_matrix.py +23 -0
  387. scipy/sparse/_sparsetools.cpython-312-darwin.so +0 -0
  388. scipy/sparse/_sputils.py +37 -22
  389. scipy/sparse/base.py +0 -9
  390. scipy/sparse/bsr.py +0 -14
  391. scipy/sparse/compressed.py +0 -23
  392. scipy/sparse/construct.py +0 -6
  393. scipy/sparse/coo.py +0 -14
  394. scipy/sparse/csc.py +0 -3
  395. scipy/sparse/csgraph/_flow.cpython-312-darwin.so +0 -0
  396. scipy/sparse/csgraph/_matching.cpython-312-darwin.so +0 -0
  397. scipy/sparse/csgraph/_min_spanning_tree.cpython-312-darwin.so +0 -0
  398. scipy/sparse/csgraph/_reordering.cpython-312-darwin.so +0 -0
  399. scipy/sparse/csgraph/_shortest_path.cpython-312-darwin.so +0 -0
  400. scipy/sparse/csgraph/_tools.cpython-312-darwin.so +0 -0
  401. scipy/sparse/csgraph/_traversal.cpython-312-darwin.so +0 -0
  402. scipy/sparse/csgraph/tests/test_matching.py +14 -2
  403. scipy/sparse/csgraph/tests/test_pydata_sparse.py +4 -1
  404. scipy/sparse/csgraph/tests/test_shortest_path.py +83 -27
  405. scipy/sparse/csr.py +0 -5
  406. scipy/sparse/data.py +1 -6
  407. scipy/sparse/dia.py +0 -7
  408. scipy/sparse/dok.py +0 -10
  409. scipy/sparse/linalg/_dsolve/_superlu.cpython-312-darwin.so +0 -0
  410. scipy/sparse/linalg/_dsolve/linsolve.py +9 -0
  411. scipy/sparse/linalg/_dsolve/tests/test_linsolve.py +35 -28
  412. scipy/sparse/linalg/_eigen/arpack/_arpack.cpython-312-darwin.so +0 -0
  413. scipy/sparse/linalg/_eigen/arpack/arpack.py +23 -17
  414. scipy/sparse/linalg/_eigen/lobpcg/lobpcg.py +6 -6
  415. scipy/sparse/linalg/_interface.py +17 -18
  416. scipy/sparse/linalg/_isolve/_gcrotmk.py +4 -4
  417. scipy/sparse/linalg/_isolve/iterative.py +51 -45
  418. scipy/sparse/linalg/_isolve/lgmres.py +6 -6
  419. scipy/sparse/linalg/_isolve/minres.py +5 -5
  420. scipy/sparse/linalg/_isolve/tfqmr.py +7 -7
  421. scipy/sparse/linalg/_isolve/utils.py +2 -8
  422. scipy/sparse/linalg/_matfuncs.py +1 -1
  423. scipy/sparse/linalg/_norm.py +1 -1
  424. scipy/sparse/linalg/_propack/_cpropack.cpython-312-darwin.so +0 -0
  425. scipy/sparse/linalg/_propack/_dpropack.cpython-312-darwin.so +0 -0
  426. scipy/sparse/linalg/_propack/_spropack.cpython-312-darwin.so +0 -0
  427. scipy/sparse/linalg/_propack/_zpropack.cpython-312-darwin.so +0 -0
  428. scipy/sparse/linalg/_special_sparse_arrays.py +39 -38
  429. scipy/sparse/linalg/tests/test_pydata_sparse.py +14 -0
  430. scipy/sparse/tests/test_arithmetic1d.py +5 -2
  431. scipy/sparse/tests/test_base.py +214 -42
  432. scipy/sparse/tests/test_common1d.py +7 -7
  433. scipy/sparse/tests/test_construct.py +1 -1
  434. scipy/sparse/tests/test_coo.py +272 -4
  435. scipy/sparse/tests/test_sparsetools.py +5 -0
  436. scipy/sparse/tests/test_sputils.py +36 -7
  437. scipy/spatial/_ckdtree.cpython-312-darwin.so +0 -0
  438. scipy/spatial/_distance_pybind.cpython-312-darwin.so +0 -0
  439. scipy/spatial/_distance_wrap.cpython-312-darwin.so +0 -0
  440. scipy/spatial/_hausdorff.cpython-312-darwin.so +0 -0
  441. scipy/spatial/_qhull.cpython-312-darwin.so +0 -0
  442. scipy/spatial/_voronoi.cpython-312-darwin.so +0 -0
  443. scipy/spatial/distance.py +49 -42
  444. scipy/spatial/tests/test_distance.py +15 -1
  445. scipy/spatial/tests/test_kdtree.py +1 -0
  446. scipy/spatial/tests/test_qhull.py +7 -2
  447. scipy/spatial/transform/__init__.py +5 -3
  448. scipy/spatial/transform/_rigid_transform.cpython-312-darwin.so +0 -0
  449. scipy/spatial/transform/_rotation.cpython-312-darwin.so +0 -0
  450. scipy/spatial/transform/tests/test_rigid_transform.py +1221 -0
  451. scipy/spatial/transform/tests/test_rotation.py +1213 -832
  452. scipy/spatial/transform/tests/test_rotation_groups.py +3 -3
  453. scipy/spatial/transform/tests/test_rotation_spline.py +29 -8
  454. scipy/special/__init__.py +1 -47
  455. scipy/special/_add_newdocs.py +34 -772
  456. scipy/special/_basic.py +22 -25
  457. scipy/special/_comb.cpython-312-darwin.so +0 -0
  458. scipy/special/_ellip_harm_2.cpython-312-darwin.so +0 -0
  459. scipy/special/_gufuncs.cpython-312-darwin.so +0 -0
  460. scipy/special/_logsumexp.py +67 -58
  461. scipy/special/_orthogonal.pyi +1 -1
  462. scipy/special/_specfun.cpython-312-darwin.so +0 -0
  463. scipy/special/_special_ufuncs.cpython-312-darwin.so +0 -0
  464. scipy/special/_spherical_bessel.py +4 -4
  465. scipy/special/_support_alternative_backends.py +212 -119
  466. scipy/special/_test_internal.cpython-312-darwin.so +0 -0
  467. scipy/special/_testutils.py +4 -4
  468. scipy/special/_ufuncs.cpython-312-darwin.so +0 -0
  469. scipy/special/_ufuncs.pyi +1 -0
  470. scipy/special/_ufuncs.pyx +215 -1400
  471. scipy/special/_ufuncs_cxx.cpython-312-darwin.so +0 -0
  472. scipy/special/_ufuncs_cxx.pxd +2 -15
  473. scipy/special/_ufuncs_cxx.pyx +5 -44
  474. scipy/special/_ufuncs_cxx_defs.h +2 -16
  475. scipy/special/_ufuncs_defs.h +0 -8
  476. scipy/special/cython_special.cpython-312-darwin.so +0 -0
  477. scipy/special/cython_special.pxd +1 -1
  478. scipy/special/tests/_cython_examples/meson.build +10 -1
  479. scipy/special/tests/test_basic.py +153 -20
  480. scipy/special/tests/test_boost_ufuncs.py +3 -0
  481. scipy/special/tests/test_cdflib.py +35 -11
  482. scipy/special/tests/test_gammainc.py +16 -0
  483. scipy/special/tests/test_hyp2f1.py +2 -2
  484. scipy/special/tests/test_log1mexp.py +85 -0
  485. scipy/special/tests/test_logsumexp.py +206 -64
  486. scipy/special/tests/test_mpmath.py +1 -0
  487. scipy/special/tests/test_nan_inputs.py +1 -1
  488. scipy/special/tests/test_orthogonal.py +17 -18
  489. scipy/special/tests/test_sf_error.py +3 -2
  490. scipy/special/tests/test_sph_harm.py +6 -7
  491. scipy/special/tests/test_support_alternative_backends.py +211 -76
  492. scipy/stats/__init__.py +4 -1
  493. scipy/stats/_ansari_swilk_statistics.cpython-312-darwin.so +0 -0
  494. scipy/stats/_axis_nan_policy.py +5 -12
  495. scipy/stats/_biasedurn.cpython-312-darwin.so +0 -0
  496. scipy/stats/_continued_fraction.py +387 -0
  497. scipy/stats/_continuous_distns.py +277 -310
  498. scipy/stats/_correlation.py +1 -1
  499. scipy/stats/_covariance.py +6 -3
  500. scipy/stats/_discrete_distns.py +39 -32
  501. scipy/stats/_distn_infrastructure.py +39 -12
  502. scipy/stats/_distribution_infrastructure.py +900 -238
  503. scipy/stats/_entropy.py +9 -10
  504. scipy/{_lib → stats}/_finite_differences.py +1 -1
  505. scipy/stats/_hypotests.py +83 -50
  506. scipy/stats/_kde.py +53 -49
  507. scipy/stats/_ksstats.py +1 -1
  508. scipy/stats/_levy_stable/__init__.py +7 -15
  509. scipy/stats/_levy_stable/levyst.cpython-312-darwin.so +0 -0
  510. scipy/stats/_morestats.py +118 -73
  511. scipy/stats/_mstats_basic.py +13 -17
  512. scipy/stats/_mstats_extras.py +8 -8
  513. scipy/stats/_multivariate.py +89 -113
  514. scipy/stats/_new_distributions.py +97 -20
  515. scipy/stats/_page_trend_test.py +12 -5
  516. scipy/stats/_probability_distribution.py +265 -43
  517. scipy/stats/_qmc.py +14 -9
  518. scipy/stats/_qmc_cy.cpython-312-darwin.so +0 -0
  519. scipy/stats/_qmvnt.py +16 -95
  520. scipy/stats/_qmvnt_cy.cpython-312-darwin.so +0 -0
  521. scipy/stats/_quantile.py +335 -0
  522. scipy/stats/_rcont/rcont.cpython-312-darwin.so +0 -0
  523. scipy/stats/_resampling.py +4 -29
  524. scipy/stats/_sampling.py +1 -1
  525. scipy/stats/_sobol.cpython-312-darwin.so +0 -0
  526. scipy/stats/_stats.cpython-312-darwin.so +0 -0
  527. scipy/stats/_stats_mstats_common.py +21 -2
  528. scipy/stats/_stats_py.py +550 -476
  529. scipy/stats/_stats_pythran.cpython-312-darwin.so +0 -0
  530. scipy/stats/_unuran/unuran_wrapper.cpython-312-darwin.so +0 -0
  531. scipy/stats/_unuran/unuran_wrapper.pyi +2 -1
  532. scipy/stats/_variation.py +6 -8
  533. scipy/stats/_wilcoxon.py +13 -7
  534. scipy/stats/tests/common_tests.py +6 -4
  535. scipy/stats/tests/test_axis_nan_policy.py +62 -24
  536. scipy/stats/tests/test_continued_fraction.py +173 -0
  537. scipy/stats/tests/test_continuous.py +379 -60
  538. scipy/stats/tests/test_continuous_basic.py +18 -12
  539. scipy/stats/tests/test_discrete_basic.py +14 -8
  540. scipy/stats/tests/test_discrete_distns.py +16 -16
  541. scipy/stats/tests/test_distributions.py +95 -75
  542. scipy/stats/tests/test_entropy.py +40 -48
  543. scipy/stats/tests/test_fit.py +4 -3
  544. scipy/stats/tests/test_hypotests.py +153 -24
  545. scipy/stats/tests/test_kdeoth.py +109 -41
  546. scipy/stats/tests/test_marray.py +289 -0
  547. scipy/stats/tests/test_morestats.py +79 -47
  548. scipy/stats/tests/test_mstats_basic.py +3 -3
  549. scipy/stats/tests/test_multivariate.py +434 -83
  550. scipy/stats/tests/test_qmc.py +13 -10
  551. scipy/stats/tests/test_quantile.py +199 -0
  552. scipy/stats/tests/test_rank.py +119 -112
  553. scipy/stats/tests/test_resampling.py +47 -56
  554. scipy/stats/tests/test_sampling.py +9 -4
  555. scipy/stats/tests/test_stats.py +799 -939
  556. scipy/stats/tests/test_variation.py +8 -6
  557. scipy/version.py +2 -2
  558. {scipy-1.15.3.dist-info → scipy-1.16.0rc2.dist-info}/LICENSE.txt +4 -4
  559. {scipy-1.15.3.dist-info → scipy-1.16.0rc2.dist-info}/METADATA +11 -11
  560. {scipy-1.15.3.dist-info → scipy-1.16.0rc2.dist-info}/RECORD +561 -568
  561. scipy-1.16.0rc2.dist-info/WHEEL +6 -0
  562. scipy/_lib/array_api_extra/_funcs.py +0 -484
  563. scipy/_lib/array_api_extra/_typing.py +0 -8
  564. scipy/interpolate/_bspl.cpython-312-darwin.so +0 -0
  565. scipy/optimize/_cobyla.cpython-312-darwin.so +0 -0
  566. scipy/optimize/_cython_nnls.cpython-312-darwin.so +0 -0
  567. scipy/optimize/_slsqp.cpython-312-darwin.so +0 -0
  568. scipy/spatial/qhull_src/COPYING.txt +0 -38
  569. scipy/special/libsf_error_state.dylib +0 -0
  570. scipy/special/tests/test_log_softmax.py +0 -109
  571. scipy/special/tests/test_xsf_cuda.py +0 -114
  572. scipy/special/xsf/binom.h +0 -89
  573. scipy/special/xsf/cdflib.h +0 -100
  574. scipy/special/xsf/cephes/airy.h +0 -307
  575. scipy/special/xsf/cephes/besselpoly.h +0 -51
  576. scipy/special/xsf/cephes/beta.h +0 -257
  577. scipy/special/xsf/cephes/cbrt.h +0 -131
  578. scipy/special/xsf/cephes/chbevl.h +0 -85
  579. scipy/special/xsf/cephes/chdtr.h +0 -193
  580. scipy/special/xsf/cephes/const.h +0 -87
  581. scipy/special/xsf/cephes/ellie.h +0 -293
  582. scipy/special/xsf/cephes/ellik.h +0 -251
  583. scipy/special/xsf/cephes/ellpe.h +0 -107
  584. scipy/special/xsf/cephes/ellpk.h +0 -117
  585. scipy/special/xsf/cephes/expn.h +0 -260
  586. scipy/special/xsf/cephes/gamma.h +0 -398
  587. scipy/special/xsf/cephes/hyp2f1.h +0 -596
  588. scipy/special/xsf/cephes/hyperg.h +0 -361
  589. scipy/special/xsf/cephes/i0.h +0 -149
  590. scipy/special/xsf/cephes/i1.h +0 -158
  591. scipy/special/xsf/cephes/igam.h +0 -421
  592. scipy/special/xsf/cephes/igam_asymp_coeff.h +0 -195
  593. scipy/special/xsf/cephes/igami.h +0 -313
  594. scipy/special/xsf/cephes/j0.h +0 -225
  595. scipy/special/xsf/cephes/j1.h +0 -198
  596. scipy/special/xsf/cephes/jv.h +0 -715
  597. scipy/special/xsf/cephes/k0.h +0 -164
  598. scipy/special/xsf/cephes/k1.h +0 -163
  599. scipy/special/xsf/cephes/kn.h +0 -243
  600. scipy/special/xsf/cephes/lanczos.h +0 -112
  601. scipy/special/xsf/cephes/ndtr.h +0 -275
  602. scipy/special/xsf/cephes/poch.h +0 -85
  603. scipy/special/xsf/cephes/polevl.h +0 -167
  604. scipy/special/xsf/cephes/psi.h +0 -194
  605. scipy/special/xsf/cephes/rgamma.h +0 -111
  606. scipy/special/xsf/cephes/scipy_iv.h +0 -811
  607. scipy/special/xsf/cephes/shichi.h +0 -248
  608. scipy/special/xsf/cephes/sici.h +0 -224
  609. scipy/special/xsf/cephes/sindg.h +0 -221
  610. scipy/special/xsf/cephes/tandg.h +0 -139
  611. scipy/special/xsf/cephes/trig.h +0 -58
  612. scipy/special/xsf/cephes/unity.h +0 -186
  613. scipy/special/xsf/cephes/zeta.h +0 -172
  614. scipy/special/xsf/config.h +0 -304
  615. scipy/special/xsf/digamma.h +0 -205
  616. scipy/special/xsf/error.h +0 -57
  617. scipy/special/xsf/evalpoly.h +0 -47
  618. scipy/special/xsf/expint.h +0 -266
  619. scipy/special/xsf/hyp2f1.h +0 -694
  620. scipy/special/xsf/iv_ratio.h +0 -173
  621. scipy/special/xsf/lambertw.h +0 -150
  622. scipy/special/xsf/loggamma.h +0 -163
  623. scipy/special/xsf/sici.h +0 -200
  624. scipy/special/xsf/tools.h +0 -427
  625. scipy/special/xsf/trig.h +0 -164
  626. scipy/special/xsf/wright_bessel.h +0 -843
  627. scipy/special/xsf/zlog1.h +0 -35
  628. scipy/stats/_mvn.cpython-312-darwin.so +0 -0
  629. 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])]