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