scipy 1.15.3__cp312-cp312-musllinux_1_2_aarch64.whl → 1.16.0rc2__cp312-cp312-musllinux_1_2_aarch64.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/__config__.py +10 -10
- scipy/__init__.py +3 -6
- scipy/_cyutility.cpython-312-aarch64-linux-musl.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-312-aarch64-linux-musl.so +0 -0
- scipy/_lib/_docscrape.py +1 -1
- scipy/_lib/_elementwise_iterative_method.py +15 -26
- scipy/_lib/_fpumode.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/_lib/_sparse.py +41 -0
- scipy/_lib/_test_ccallback.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/_lib/_test_deprecation_call.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/_lib/_test_deprecation_def.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/_lib/_testutils.py +6 -2
- scipy/_lib/_uarray/_uarray.cpython-312-aarch64-linux-musl.so +0 -0
- 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-312-aarch64-linux-musl.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-312-aarch64-linux-musl.so +0 -0
- scipy/cluster/_optimal_leaf_ordering.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/cluster/_vq.cpython-312-aarch64-linux-musl.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/pypocketfft.cpython-312-aarch64-linux-musl.so +0 -0
- 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-312-aarch64-linux-musl.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-312-aarch64-linux-musl.so +0 -0
- scipy/integrate/_lsoda.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/integrate/_ode.py +9 -2
- scipy/integrate/_odepack.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/integrate/_quad_vec.py +21 -29
- scipy/integrate/_quadpack.cpython-312-aarch64-linux-musl.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_multivariate.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/integrate/_test_odeint_banded.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/integrate/_vode.cpython-312-aarch64-linux-musl.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-312-aarch64-linux-musl.so +0 -0
- scipy/interpolate/_dierckx.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/interpolate/_fitpack.cpython-312-aarch64-linux-musl.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-312-aarch64-linux-musl.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-312-aarch64-linux-musl.so +0 -0
- scipy/interpolate/_rbf.py +2 -2
- scipy/interpolate/_rbfinterp.py +1 -1
- scipy/interpolate/_rbfinterp_pythran.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/interpolate/_rgi.py +31 -26
- scipy/interpolate/_rgi_cython.cpython-312-aarch64-linux-musl.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/_fast_matrix_market/_fmm_core.cpython-312-aarch64-linux-musl.so +0 -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-312-aarch64-linux-musl.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-312-aarch64-linux-musl.so +0 -0
- scipy/io/matlab/_mio_utils.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/io/matlab/_miobase.py +4 -1
- scipy/io/matlab/_streams.cpython-312-aarch64-linux-musl.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-312-aarch64-linux-musl.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-312-aarch64-linux-musl.so +0 -0
- scipy/linalg/_decomp_ldl.py +4 -1
- scipy/linalg/_decomp_lu.py +18 -6
- scipy/linalg/_decomp_lu_cython.cpython-312-aarch64-linux-musl.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-312-aarch64-linux-musl.so +0 -0
- scipy/linalg/_expm_frechet.py +4 -0
- scipy/linalg/_fblas.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/linalg/_flapack.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/linalg/_linalg_pythran.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/linalg/_matfuncs.py +187 -4
- scipy/linalg/_matfuncs_expm.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/linalg/_matfuncs_schur_sqrtm.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/linalg/_matfuncs_sqrtm.py +1 -99
- scipy/linalg/_matfuncs_sqrtm_triu.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/linalg/_procrustes.py +2 -0
- scipy/linalg/_sketches.py +17 -6
- scipy/linalg/_solve_toeplitz.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/linalg/_solvers.py +7 -2
- scipy/linalg/_special_matrices.py +26 -36
- scipy/linalg/cython_blas.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/linalg/cython_lapack.cpython-312-aarch64-linux-musl.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/_ctest.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/ndimage/_cytest.cpython-312-aarch64-linux-musl.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-312-aarch64-linux-musl.so +0 -0
- scipy/ndimage/_ni_docstrings.py +5 -1
- scipy/ndimage/_ni_label.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/ndimage/_ni_support.py +1 -5
- scipy/ndimage/_rank_filter_1d.cpython-312-aarch64-linux-musl.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-312-aarch64-linux-musl.so +0 -0
- scipy/optimize/_basinhopping.py +13 -7
- scipy/optimize/_bglu_dense.cpython-312-aarch64-linux-musl.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-312-aarch64-linux-musl.so +0 -0
- scipy/optimize/_dual_annealing.py +1 -1
- scipy/optimize/_elementwise.py +1 -4
- scipy/optimize/_group_columns.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/optimize/_highspy/_core.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/optimize/_highspy/_highs_options.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/optimize/_lbfgsb.cpython-312-aarch64-linux-musl.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-312-aarch64-linux-musl.so +0 -0
- scipy/optimize/_lsq/common.py +3 -3
- scipy/optimize/_lsq/dogbox.py +16 -2
- scipy/optimize/_lsq/givens_elimination.cpython-312-aarch64-linux-musl.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-312-aarch64-linux-musl.so +0 -0
- scipy/optimize/_minpack_py.py +21 -14
- scipy/optimize/_moduleTNC.cpython-312-aarch64-linux-musl.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-312-aarch64-linux-musl.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-312-aarch64-linux-musl.so +0 -0
- scipy/optimize/_spectral.py +1 -1
- scipy/optimize/_tnc.py +8 -1
- scipy/optimize/_trlib/_trlib.cpython-312-aarch64-linux-musl.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-312-aarch64-linux-musl.so +0 -0
- scipy/optimize/_zeros_py.py +97 -17
- scipy/optimize/cython_optimize/_zeros.cpython-312-aarch64-linux-musl.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-312-aarch64-linux-musl.so +0 -0
- scipy/signal/_peak_finding_utils.cpython-312-aarch64-linux-musl.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-312-aarch64-linux-musl.so +0 -0
- scipy/signal/_sosfilt.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/signal/_spectral_py.py +230 -50
- scipy/signal/_spline.cpython-312-aarch64-linux-musl.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-312-aarch64-linux-musl.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-312-aarch64-linux-musl.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-312-aarch64-linux-musl.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-312-aarch64-linux-musl.so +0 -0
- scipy/sparse/csgraph/_matching.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/sparse/csgraph/_min_spanning_tree.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/sparse/csgraph/_reordering.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/sparse/csgraph/_shortest_path.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/sparse/csgraph/_tools.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/sparse/csgraph/_traversal.cpython-312-aarch64-linux-musl.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-312-aarch64-linux-musl.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-312-aarch64-linux-musl.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-312-aarch64-linux-musl.so +0 -0
- scipy/sparse/linalg/_propack/_dpropack.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/sparse/linalg/_propack/_spropack.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/sparse/linalg/_propack/_zpropack.cpython-312-aarch64-linux-musl.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-312-aarch64-linux-musl.so +0 -0
- scipy/spatial/_distance_pybind.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/spatial/_distance_wrap.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/spatial/_hausdorff.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/spatial/_qhull.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/spatial/_voronoi.cpython-312-aarch64-linux-musl.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-312-aarch64-linux-musl.so +0 -0
- scipy/spatial/transform/_rotation.cpython-312-aarch64-linux-musl.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-312-aarch64-linux-musl.so +0 -0
- scipy/special/_ellip_harm_2.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/special/_gufuncs.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/special/_logsumexp.py +67 -58
- scipy/special/_orthogonal.pyi +1 -1
- scipy/special/_specfun.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/special/_special_ufuncs.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/special/_spherical_bessel.py +4 -4
- scipy/special/_support_alternative_backends.py +212 -119
- scipy/special/_test_internal.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/special/_testutils.py +4 -4
- scipy/special/_ufuncs.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/special/_ufuncs.pyi +1 -0
- scipy/special/_ufuncs.pyx +215 -1400
- scipy/special/_ufuncs_cxx.cpython-312-aarch64-linux-musl.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-312-aarch64-linux-musl.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-312-aarch64-linux-musl.so +0 -0
- scipy/stats/_axis_nan_policy.py +5 -12
- scipy/stats/_biasedurn.cpython-312-aarch64-linux-musl.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-312-aarch64-linux-musl.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-312-aarch64-linux-musl.so +0 -0
- scipy/stats/_qmvnt.py +16 -95
- scipy/stats/_qmvnt_cy.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/stats/_quantile.py +335 -0
- scipy/stats/_rcont/rcont.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/stats/_resampling.py +4 -29
- scipy/stats/_sampling.py +1 -1
- scipy/stats/_sobol.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/stats/_stats.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/stats/_stats_mstats_common.py +21 -2
- scipy/stats/_stats_py.py +550 -476
- scipy/stats/_stats_pythran.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/stats/_unuran/unuran_wrapper.cpython-312-aarch64-linux-musl.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 +1262 -1269
- scipy.libs/libgcc_s-69c45f16.so.1 +0 -0
- scipy.libs/libgfortran-db0b6589.so.5.0.0 +0 -0
- scipy.libs/{libstdc++-1b614e01.so.6.0.32 → libstdc++-1f1a71be.so.6.0.33} +0 -0
- scipy/_lib/array_api_extra/_funcs.py +0 -484
- scipy/_lib/array_api_extra/_typing.py +0 -8
- scipy/interpolate/_bspl.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/optimize/_cobyla.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/optimize/_cython_nnls.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/optimize/_slsqp.cpython-312-aarch64-linux-musl.so +0 -0
- scipy/spatial/qhull_src/COPYING.txt +0 -38
- scipy/special/libsf_error_state.so +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-312-aarch64-linux-musl.so +0 -0
- scipy.libs/libgcc_s-7393e603.so.1 +0 -0
- scipy.libs/libgfortran-eb933d8e.so.5.0.0 +0 -0
- {scipy-1.15.3.dist-info → scipy-1.16.0rc2.dist-info}/WHEEL +0 -0
scipy/cluster/hierarchy.py
CHANGED
@@ -134,8 +134,10 @@ from collections import deque
|
|
134
134
|
import numpy as np
|
135
135
|
from . import _hierarchy, _optimal_leaf_ordering
|
136
136
|
import scipy.spatial.distance as distance
|
137
|
-
from scipy._lib._array_api import
|
137
|
+
from scipy._lib._array_api import (_asarray, array_namespace, is_dask,
|
138
|
+
is_lazy_array, xp_capabilities, xp_copy)
|
138
139
|
from scipy._lib._disjoint_set import DisjointSet
|
140
|
+
import scipy._lib.array_api_extra as xpx
|
139
141
|
|
140
142
|
|
141
143
|
_LINKAGE_METHODS = {'single': 0, 'complete': 1, 'average': 2, 'centroid': 3,
|
@@ -163,9 +165,15 @@ def _warning(s):
|
|
163
165
|
def int_floor(arr, xp):
|
164
166
|
# array_api_strict is strict about not allowing `int()` on a float array.
|
165
167
|
# That's typically not needed, here it is - so explicitly convert
|
166
|
-
return int(xp.
|
168
|
+
return int(xp.asarray(arr, dtype=xp.int64))
|
167
169
|
|
168
170
|
|
171
|
+
lazy_cython = xp_capabilities(
|
172
|
+
cpu_only=True, reason="Cython code",
|
173
|
+
warnings=[("dask.array", "merges chunks")])
|
174
|
+
|
175
|
+
|
176
|
+
@lazy_cython
|
169
177
|
def single(y):
|
170
178
|
"""
|
171
179
|
Perform single/min/nearest linkage on the condensed distance matrix ``y``.
|
@@ -244,6 +252,7 @@ def single(y):
|
|
244
252
|
return linkage(y, method='single', metric='euclidean')
|
245
253
|
|
246
254
|
|
255
|
+
@lazy_cython
|
247
256
|
def complete(y):
|
248
257
|
"""
|
249
258
|
Perform complete/max/farthest point linkage on a condensed distance matrix.
|
@@ -326,6 +335,7 @@ def complete(y):
|
|
326
335
|
return linkage(y, method='complete', metric='euclidean')
|
327
336
|
|
328
337
|
|
338
|
+
@lazy_cython
|
329
339
|
def average(y):
|
330
340
|
"""
|
331
341
|
Perform average/UPGMA linkage on a condensed distance matrix.
|
@@ -408,6 +418,7 @@ def average(y):
|
|
408
418
|
return linkage(y, method='average', metric='euclidean')
|
409
419
|
|
410
420
|
|
421
|
+
@lazy_cython
|
411
422
|
def weighted(y):
|
412
423
|
"""
|
413
424
|
Perform weighted/WPGMA linkage on the condensed distance matrix.
|
@@ -493,6 +504,7 @@ def weighted(y):
|
|
493
504
|
return linkage(y, method='weighted', metric='euclidean')
|
494
505
|
|
495
506
|
|
507
|
+
@lazy_cython
|
496
508
|
def centroid(y):
|
497
509
|
"""
|
498
510
|
Perform centroid/UPGMC linkage.
|
@@ -595,6 +607,7 @@ def centroid(y):
|
|
595
607
|
return linkage(y, method='centroid', metric='euclidean')
|
596
608
|
|
597
609
|
|
610
|
+
@lazy_cython
|
598
611
|
def median(y):
|
599
612
|
"""
|
600
613
|
Perform median/WPGMC linkage.
|
@@ -697,6 +710,7 @@ def median(y):
|
|
697
710
|
return linkage(y, method='median', metric='euclidean')
|
698
711
|
|
699
712
|
|
713
|
+
@lazy_cython
|
700
714
|
def ward(y):
|
701
715
|
"""
|
702
716
|
Perform Ward's linkage on a condensed distance matrix.
|
@@ -796,6 +810,7 @@ def ward(y):
|
|
796
810
|
return linkage(y, method='ward', metric='euclidean')
|
797
811
|
|
798
812
|
|
813
|
+
@lazy_cython
|
799
814
|
def linkage(y, method='single', metric='euclidean', optimal_ordering=False):
|
800
815
|
"""
|
801
816
|
Perform hierarchical/agglomerative clustering.
|
@@ -1004,6 +1019,7 @@ def linkage(y, method='single', metric='euclidean', optimal_ordering=False):
|
|
1004
1019
|
"""
|
1005
1020
|
xp = array_namespace(y)
|
1006
1021
|
y = _asarray(y, order='C', dtype=xp.float64, xp=xp)
|
1022
|
+
lazy = is_lazy_array(y)
|
1007
1023
|
|
1008
1024
|
if method not in _LINKAGE_METHODS:
|
1009
1025
|
raise ValueError(f"Invalid method: {method}")
|
@@ -1015,35 +1031,41 @@ def linkage(y, method='single', metric='euclidean', optimal_ordering=False):
|
|
1015
1031
|
if y.ndim == 1:
|
1016
1032
|
distance.is_valid_y(y, throw=True, name='y')
|
1017
1033
|
elif y.ndim == 2:
|
1018
|
-
if (y.shape[0] == y.shape[1]
|
1019
|
-
|
1034
|
+
if (not lazy and y.shape[0] == y.shape[1]
|
1035
|
+
and xp.all(xpx.isclose(xp.linalg.diagonal(y), 0))
|
1036
|
+
and xp.all(y >= 0) and xp.all(xpx.isclose(y, y.T))):
|
1020
1037
|
warnings.warn('The symmetric non-negative hollow observation '
|
1021
1038
|
'matrix looks suspiciously like an uncondensed '
|
1022
1039
|
'distance matrix',
|
1023
1040
|
ClusterWarning, stacklevel=2)
|
1024
1041
|
y = distance.pdist(y, metric)
|
1025
|
-
y = xp.asarray(y)
|
1026
1042
|
else:
|
1027
1043
|
raise ValueError("`y` must be 1 or 2 dimensional.")
|
1028
1044
|
|
1029
|
-
if not xp.all(xp.isfinite(y)):
|
1045
|
+
if not lazy and not xp.all(xp.isfinite(y)):
|
1030
1046
|
raise ValueError("The condensed distance matrix must contain only "
|
1031
1047
|
"finite values.")
|
1032
1048
|
|
1033
|
-
n =
|
1049
|
+
n = distance.num_obs_y(y)
|
1034
1050
|
method_code = _LINKAGE_METHODS[method]
|
1035
1051
|
|
1036
|
-
|
1037
|
-
|
1038
|
-
|
1039
|
-
|
1040
|
-
|
1041
|
-
|
1042
|
-
|
1043
|
-
|
1052
|
+
def cy_linkage(y, validate):
|
1053
|
+
if validate and not np.all(np.isfinite(y)):
|
1054
|
+
raise ValueError("The condensed distance matrix must contain only "
|
1055
|
+
"finite values.")
|
1056
|
+
|
1057
|
+
if method == 'single':
|
1058
|
+
return _hierarchy.mst_single_linkage(y, n)
|
1059
|
+
elif method in ('complete', 'average', 'weighted', 'ward'):
|
1060
|
+
return _hierarchy.nn_chain(y, n, method_code)
|
1061
|
+
else:
|
1062
|
+
return _hierarchy.fast_linkage(y, n, method_code)
|
1063
|
+
|
1064
|
+
result = xpx.lazy_apply(cy_linkage, y, validate=lazy,
|
1065
|
+
shape=(n - 1, 4), dtype=xp.float64,
|
1066
|
+
as_numpy=True, xp=xp)
|
1044
1067
|
|
1045
1068
|
if optimal_ordering:
|
1046
|
-
y = xp.asarray(y)
|
1047
1069
|
return optimal_leaf_ordering(result, y)
|
1048
1070
|
else:
|
1049
1071
|
return result
|
@@ -1282,6 +1304,7 @@ def _order_cluster_tree(Z):
|
|
1282
1304
|
return nodes
|
1283
1305
|
|
1284
1306
|
|
1307
|
+
@xp_capabilities(np_only=True, reason="non-standard indexing")
|
1285
1308
|
def cut_tree(Z, n_clusters=None, height=None):
|
1286
1309
|
"""
|
1287
1310
|
Given a linkage matrix Z, return the cut tree.
|
@@ -1369,6 +1392,7 @@ def cut_tree(Z, n_clusters=None, height=None):
|
|
1369
1392
|
return groups.T
|
1370
1393
|
|
1371
1394
|
|
1395
|
+
@xp_capabilities(jax_jit=False, allow_dask_compute=True)
|
1372
1396
|
def to_tree(Z, rd=False):
|
1373
1397
|
"""
|
1374
1398
|
Convert a linkage matrix into an easy-to-use tree object.
|
@@ -1430,8 +1454,8 @@ def to_tree(Z, rd=False):
|
|
1430
1454
|
|
1431
1455
|
"""
|
1432
1456
|
xp = array_namespace(Z)
|
1433
|
-
Z = _asarray(Z, order='
|
1434
|
-
|
1457
|
+
Z = _asarray(Z, order='C', xp=xp)
|
1458
|
+
_is_valid_linkage(Z, throw=True, name='Z', materialize=True, xp=xp)
|
1435
1459
|
|
1436
1460
|
# Number of original objects is equal to the number of rows plus 1.
|
1437
1461
|
n = Z.shape[0] + 1
|
@@ -1451,19 +1475,19 @@ def to_tree(Z, rd=False):
|
|
1451
1475
|
fi = int_floor(row[0], xp)
|
1452
1476
|
fj = int_floor(row[1], xp)
|
1453
1477
|
if fi > i + n:
|
1454
|
-
raise ValueError(
|
1455
|
-
'is used before it is formed. See row
|
1456
|
-
'column 0')
|
1478
|
+
raise ValueError('Corrupt matrix Z. Index to derivative cluster '
|
1479
|
+
f'is used before it is formed. See row {fi}, '
|
1480
|
+
'column 0')
|
1457
1481
|
if fj > i + n:
|
1458
|
-
raise ValueError(
|
1459
|
-
'is used before it is formed. See row
|
1460
|
-
'column 1')
|
1482
|
+
raise ValueError('Corrupt matrix Z. Index to derivative cluster '
|
1483
|
+
f'is used before it is formed. See row {fj}, '
|
1484
|
+
'column 1')
|
1461
1485
|
|
1462
1486
|
nd = ClusterNode(i + n, d[fi], d[fj], row[2])
|
1463
1487
|
# ^ id ^ left ^ right ^ dist
|
1464
1488
|
if row[3] != nd.count:
|
1465
|
-
raise ValueError(
|
1466
|
-
'incorrect.')
|
1489
|
+
raise ValueError(f'Corrupt matrix Z. The count Z[{i},3] is '
|
1490
|
+
'incorrect.')
|
1467
1491
|
d[n + i] = nd
|
1468
1492
|
|
1469
1493
|
if rd:
|
@@ -1472,6 +1496,7 @@ def to_tree(Z, rd=False):
|
|
1472
1496
|
return nd
|
1473
1497
|
|
1474
1498
|
|
1499
|
+
@lazy_cython
|
1475
1500
|
def optimal_leaf_ordering(Z, y, metric='euclidean'):
|
1476
1501
|
"""
|
1477
1502
|
Given a linkage matrix Z and distance, reorder the cut tree.
|
@@ -1513,33 +1538,42 @@ def optimal_leaf_ordering(Z, y, metric='euclidean'):
|
|
1513
1538
|
"""
|
1514
1539
|
xp = array_namespace(Z, y)
|
1515
1540
|
Z = _asarray(Z, order='C', xp=xp)
|
1516
|
-
is_valid_linkage(Z, throw=True, name='Z')
|
1517
|
-
|
1518
1541
|
y = _asarray(y, order='C', dtype=xp.float64, xp=xp)
|
1542
|
+
lazy = is_lazy_array(Z)
|
1543
|
+
_is_valid_linkage(Z, throw=True, name='Z', xp=xp)
|
1519
1544
|
|
1520
1545
|
if y.ndim == 1:
|
1521
1546
|
distance.is_valid_y(y, throw=True, name='y')
|
1522
1547
|
elif y.ndim == 2:
|
1523
|
-
if (y.shape[0] == y.shape[1]
|
1524
|
-
|
1548
|
+
if (not lazy and y.shape[0] == y.shape[1]
|
1549
|
+
and xp.all(xpx.isclose(xp.linalg.diagonal(y), 0))
|
1550
|
+
and xp.all(y >= 0) and xp.all(xpx.isclose(y, y.T))):
|
1525
1551
|
warnings.warn('The symmetric non-negative hollow observation '
|
1526
1552
|
'matrix looks suspiciously like an uncondensed '
|
1527
1553
|
'distance matrix',
|
1528
1554
|
ClusterWarning, stacklevel=2)
|
1529
1555
|
y = distance.pdist(y, metric)
|
1530
|
-
y = xp.asarray(y)
|
1531
1556
|
else:
|
1532
1557
|
raise ValueError("`y` must be 1 or 2 dimensional.")
|
1533
|
-
|
1534
|
-
if not xp.all(xp.isfinite(y)):
|
1558
|
+
if not lazy and not xp.all(xp.isfinite(y)):
|
1535
1559
|
raise ValueError("The condensed distance matrix must contain only "
|
1536
1560
|
"finite values.")
|
1537
1561
|
|
1538
|
-
|
1539
|
-
|
1540
|
-
|
1562
|
+
# The function name is prominently visible on the user-facing Dask dashboard;
|
1563
|
+
# make sure it is meaningful.
|
1564
|
+
def cy_optimal_leaf_ordering(Z, y, validate):
|
1565
|
+
if validate:
|
1566
|
+
_is_valid_linkage(Z, throw=True, name='Z', xp=np)
|
1567
|
+
if not np.all(np.isfinite(y)):
|
1568
|
+
raise ValueError("The condensed distance matrix must contain only "
|
1569
|
+
"finite values.")
|
1570
|
+
return _optimal_leaf_ordering.optimal_leaf_ordering(Z, y)
|
1571
|
+
|
1572
|
+
return xpx.lazy_apply(cy_optimal_leaf_ordering, Z, y, validate=lazy,
|
1573
|
+
shape=Z.shape, dtype=Z.dtype, as_numpy=True, xp=xp)
|
1541
1574
|
|
1542
1575
|
|
1576
|
+
@lazy_cython
|
1543
1577
|
def cophenet(Z, Y=None):
|
1544
1578
|
"""
|
1545
1579
|
Calculate the cophenetic distances between each observation in
|
@@ -1650,13 +1684,21 @@ def cophenet(Z, Y=None):
|
|
1650
1684
|
xp = array_namespace(Z, Y)
|
1651
1685
|
# Ensure float64 C-contiguous array. Cython code doesn't deal with striding.
|
1652
1686
|
Z = _asarray(Z, order='C', dtype=xp.float64, xp=xp)
|
1653
|
-
|
1687
|
+
_is_valid_linkage(Z, throw=True, name='Z', xp=xp)
|
1688
|
+
|
1689
|
+
def cy_cophenet(Z, validate):
|
1690
|
+
if validate:
|
1691
|
+
_is_valid_linkage(Z, throw=True, name='Z', xp=np)
|
1692
|
+
n = Z.shape[0] + 1
|
1693
|
+
zz = np.zeros((n * (n-1)) // 2, dtype=np.float64)
|
1694
|
+
_hierarchy.cophenetic_distances(Z, zz, n)
|
1695
|
+
return zz
|
1696
|
+
|
1654
1697
|
n = Z.shape[0] + 1
|
1655
|
-
zz =
|
1656
|
-
|
1657
|
-
|
1658
|
-
|
1659
|
-
zz = xp.asarray(zz)
|
1698
|
+
zz = xpx.lazy_apply(cy_cophenet, Z, validate=is_lazy_array(Z),
|
1699
|
+
shape=((n * (n-1)) // 2, ), dtype=xp.float64,
|
1700
|
+
as_numpy=True, xp=xp)
|
1701
|
+
|
1660
1702
|
if Y is None:
|
1661
1703
|
return zz
|
1662
1704
|
|
@@ -1674,6 +1716,7 @@ def cophenet(Z, Y=None):
|
|
1674
1716
|
return (c, zz)
|
1675
1717
|
|
1676
1718
|
|
1719
|
+
@lazy_cython
|
1677
1720
|
def inconsistent(Z, d=2):
|
1678
1721
|
r"""
|
1679
1722
|
Calculate inconsistency statistics on a linkage matrix.
|
@@ -1731,21 +1774,26 @@ def inconsistent(Z, d=2):
|
|
1731
1774
|
"""
|
1732
1775
|
xp = array_namespace(Z)
|
1733
1776
|
Z = _asarray(Z, order='C', dtype=xp.float64, xp=xp)
|
1734
|
-
|
1777
|
+
_is_valid_linkage(Z, throw=True, name='Z', xp=xp)
|
1735
1778
|
|
1736
|
-
if
|
1779
|
+
if d != np.floor(d) or d < 0:
|
1737
1780
|
raise ValueError('The second argument d must be a nonnegative '
|
1738
1781
|
'integer value.')
|
1739
1782
|
|
1740
|
-
|
1741
|
-
|
1783
|
+
def cy_inconsistent(Z, d, validate):
|
1784
|
+
if validate:
|
1785
|
+
_is_valid_linkage(Z, throw=True, name='Z', xp=np)
|
1786
|
+
R = np.zeros((Z.shape[0], 4), dtype=np.float64)
|
1787
|
+
n = Z.shape[0] + 1
|
1788
|
+
_hierarchy.inconsistent(Z, R, n, d)
|
1789
|
+
return R
|
1742
1790
|
|
1743
|
-
Z =
|
1744
|
-
|
1745
|
-
|
1746
|
-
return R
|
1791
|
+
return xpx.lazy_apply(cy_inconsistent, Z, d=int(d), validate=is_lazy_array(Z),
|
1792
|
+
shape=(Z.shape[0], 4), dtype=xp.float64,
|
1793
|
+
as_numpy=True, xp=xp)
|
1747
1794
|
|
1748
1795
|
|
1796
|
+
@lazy_cython
|
1749
1797
|
def from_mlab_linkage(Z):
|
1750
1798
|
"""
|
1751
1799
|
Convert a linkage matrix generated by MATLAB(TM) to a new
|
@@ -1818,34 +1866,48 @@ def from_mlab_linkage(Z):
|
|
1818
1866
|
"""
|
1819
1867
|
xp = array_namespace(Z)
|
1820
1868
|
Z = _asarray(Z, dtype=xp.float64, order='C', xp=xp)
|
1821
|
-
Zs = Z.shape
|
1822
1869
|
|
1823
1870
|
# If it's empty, return it.
|
1824
|
-
if
|
1871
|
+
if Z.shape in ((), (0, )):
|
1825
1872
|
return xp_copy(Z, xp=xp)
|
1826
1873
|
|
1827
|
-
if
|
1874
|
+
if Z.ndim != 2:
|
1828
1875
|
raise ValueError("The linkage array must be rectangular.")
|
1829
1876
|
|
1830
1877
|
# If it contains no rows, return it.
|
1831
|
-
|
1878
|
+
n = Z.shape[0]
|
1879
|
+
if n == 0:
|
1832
1880
|
return xp_copy(Z, xp=xp)
|
1833
1881
|
|
1834
|
-
|
1882
|
+
lazy = is_lazy_array(Z)
|
1883
|
+
|
1884
|
+
if not lazy and xp.min(Z[:, :2]) != 1.0 and xp.max(Z[:, :2]) != 2 * n:
|
1835
1885
|
raise ValueError('The format of the indices is not 1..N')
|
1836
1886
|
|
1837
|
-
|
1838
|
-
|
1839
|
-
|
1840
|
-
# calculate_cluster_sizes doesn't accept read-only arrays
|
1841
|
-
Zpart = np.array(Zpart, copy=True)
|
1842
|
-
else:
|
1843
|
-
Zpart = np.asarray(Zpart)
|
1844
|
-
_hierarchy.calculate_cluster_sizes(Zpart, CS, int(Zs[0]) + 1)
|
1845
|
-
res = np.hstack([Zpart, CS.reshape(Zs[0], 1)])
|
1846
|
-
return xp.asarray(res)
|
1887
|
+
res = xp.empty((Z.shape[0], Z.shape[1] + 1), dtype=Z.dtype)
|
1888
|
+
res = xpx.at(res)[:, :2].set(Z[:, :2] - 1.0)
|
1889
|
+
res = xpx.at(res)[:, 2:-1].set(Z[:, 2:])
|
1847
1890
|
|
1891
|
+
def cy_from_mlab_linkage(Zpart, validate):
|
1892
|
+
n = Zpart.shape[0]
|
1893
|
+
if validate and np.min(Zpart[:, :2]) != 0.0 and np.max(Zpart[:, :2]) != 2 * n:
|
1894
|
+
raise ValueError('The format of the indices is not 1..N')
|
1848
1895
|
|
1896
|
+
if not Zpart.flags.writeable:
|
1897
|
+
Zpart = Zpart.copy() # xp=jax.numpy
|
1898
|
+
|
1899
|
+
CS = np.zeros((n,))
|
1900
|
+
_hierarchy.calculate_cluster_sizes(Zpart, CS, n + 1)
|
1901
|
+
return CS
|
1902
|
+
|
1903
|
+
CS = xpx.lazy_apply(cy_from_mlab_linkage, res[:, :-1], validate=lazy,
|
1904
|
+
shape=(res.shape[0],), dtype=xp.float64,
|
1905
|
+
as_numpy=True, xp=xp)
|
1906
|
+
|
1907
|
+
return xpx.at(res)[:, -1].set(CS)
|
1908
|
+
|
1909
|
+
|
1910
|
+
@xp_capabilities()
|
1849
1911
|
def to_mlab_linkage(Z):
|
1850
1912
|
"""
|
1851
1913
|
Convert a linkage matrix to a MATLAB(TM) compatible one.
|
@@ -1922,15 +1984,15 @@ def to_mlab_linkage(Z):
|
|
1922
1984
|
|
1923
1985
|
"""
|
1924
1986
|
xp = array_namespace(Z)
|
1925
|
-
Z = _asarray(Z,
|
1926
|
-
|
1927
|
-
if len(Zs) == 0 or (len(Zs) == 1 and Zs[0] == 0):
|
1987
|
+
Z = _asarray(Z, dtype=xp.float64, xp=xp)
|
1988
|
+
if Z.shape in ((), (0, )):
|
1928
1989
|
return xp_copy(Z, xp=xp)
|
1929
|
-
|
1990
|
+
_is_valid_linkage(Z, throw=True, name='Z', xp=xp)
|
1930
1991
|
|
1931
1992
|
return xp.concat((Z[:, :2] + 1.0, Z[:, 2:3]), axis=1)
|
1932
1993
|
|
1933
1994
|
|
1995
|
+
@xp_capabilities()
|
1934
1996
|
def is_monotonic(Z):
|
1935
1997
|
"""
|
1936
1998
|
Return True if the linkage passed is monotonic.
|
@@ -2010,13 +2072,14 @@ def is_monotonic(Z):
|
|
2010
2072
|
|
2011
2073
|
"""
|
2012
2074
|
xp = array_namespace(Z)
|
2013
|
-
Z = _asarray(Z,
|
2014
|
-
|
2075
|
+
Z = _asarray(Z, xp=xp)
|
2076
|
+
_is_valid_linkage(Z, throw=True, name='Z', xp=xp)
|
2015
2077
|
|
2016
2078
|
# We expect the i'th value to be greater than its successor.
|
2017
2079
|
return xp.all(Z[1:, 2] >= Z[:-1, 2])
|
2018
2080
|
|
2019
2081
|
|
2082
|
+
@xp_capabilities(warnings=[("dask.array", "see notes"), ("jax.numpy", "see notes")])
|
2020
2083
|
def is_valid_im(R, warning=False, throw=False, name=None):
|
2021
2084
|
"""Return True if the inconsistency matrix passed is valid.
|
2022
2085
|
|
@@ -2041,7 +2104,13 @@ def is_valid_im(R, warning=False, throw=False, name=None):
|
|
2041
2104
|
Returns
|
2042
2105
|
-------
|
2043
2106
|
b : bool
|
2044
|
-
True if the inconsistency matrix is valid.
|
2107
|
+
True if the inconsistency matrix is valid; False otherwise.
|
2108
|
+
|
2109
|
+
Notes
|
2110
|
+
-----
|
2111
|
+
*Array API support (experimental):* If the input is a lazy Array (e.g. Dask
|
2112
|
+
or JAX), the return value may be a 0-dimensional bool Array. When warning=True
|
2113
|
+
or throw=True, calling this function materializes the array.
|
2045
2114
|
|
2046
2115
|
See Also
|
2047
2116
|
--------
|
@@ -2105,8 +2174,16 @@ def is_valid_im(R, warning=False, throw=False, name=None):
|
|
2105
2174
|
|
2106
2175
|
"""
|
2107
2176
|
xp = array_namespace(R)
|
2108
|
-
R = _asarray(R,
|
2109
|
-
|
2177
|
+
R = _asarray(R, xp=xp)
|
2178
|
+
return _is_valid_im(R, warning=warning, throw=throw, name=name,
|
2179
|
+
materialize=True, xp=xp)
|
2180
|
+
|
2181
|
+
|
2182
|
+
def _is_valid_im(R, warning=False, throw=False, name=None, materialize=False, *, xp):
|
2183
|
+
"""Variant of `is_valid_im` to be called internally by other scipy functions,
|
2184
|
+
which by default does not materialize lazy input arrays (Dask, JAX, etc.) when
|
2185
|
+
warning=True or throw=True.
|
2186
|
+
"""
|
2110
2187
|
name_str = f"{name!r} " if name else ''
|
2111
2188
|
try:
|
2112
2189
|
if R.dtype != xp.float64:
|
@@ -2121,25 +2198,26 @@ def is_valid_im(R, warning=False, throw=False, name=None):
|
|
2121
2198
|
if R.shape[0] < 1:
|
2122
2199
|
raise ValueError(f'Inconsistency matrix {name_str}'
|
2123
2200
|
'must have at least one row.')
|
2124
|
-
|
2125
|
-
raise ValueError(f'Inconsistency matrix {name_str}'
|
2126
|
-
'contains negative link height means.')
|
2127
|
-
if xp.any(R[:, 1] < 0):
|
2128
|
-
raise ValueError(f'Inconsistency matrix {name_str}'
|
2129
|
-
'contains negative link height standard deviations.')
|
2130
|
-
if xp.any(R[:, 2] < 0):
|
2131
|
-
raise ValueError(f'Inconsistency matrix {name_str}'
|
2132
|
-
'contains negative link counts.')
|
2133
|
-
except Exception as e:
|
2201
|
+
except (TypeError, ValueError) as e:
|
2134
2202
|
if throw:
|
2135
2203
|
raise
|
2136
2204
|
if warning:
|
2137
2205
|
_warning(str(e))
|
2138
|
-
|
2206
|
+
return False
|
2139
2207
|
|
2140
|
-
return
|
2208
|
+
return _lazy_valid_checks(
|
2209
|
+
(xp.any(R[:, 0] < 0),
|
2210
|
+
f'Inconsistency matrix {name_str} contains negative link height means.'),
|
2211
|
+
(xp.any(R[:, 1] < 0),
|
2212
|
+
f'Inconsistency matrix {name_str} contains negative link height standard '
|
2213
|
+
'deviations.'),
|
2214
|
+
(xp.any(R[:, 2] < 0),
|
2215
|
+
f'Inconsistency matrix {name_str} contains negative link counts.'),
|
2216
|
+
throw=throw, warning=warning, materialize=materialize, xp=xp
|
2217
|
+
)
|
2141
2218
|
|
2142
2219
|
|
2220
|
+
@xp_capabilities(warnings=[("dask.array", "see notes"), ("jax.numpy", "see notes")])
|
2143
2221
|
def is_valid_linkage(Z, warning=False, throw=False, name=None):
|
2144
2222
|
"""
|
2145
2223
|
Check the validity of a linkage matrix.
|
@@ -2178,7 +2256,13 @@ def is_valid_linkage(Z, warning=False, throw=False, name=None):
|
|
2178
2256
|
Returns
|
2179
2257
|
-------
|
2180
2258
|
b : bool
|
2181
|
-
True if the inconsistency matrix is valid.
|
2259
|
+
True if the inconsistency matrix is valid; False otherwise.
|
2260
|
+
|
2261
|
+
Notes
|
2262
|
+
-----
|
2263
|
+
*Array API support (experimental):* If the input is a lazy Array (e.g. Dask
|
2264
|
+
or JAX), the return value may be a 0-dimensional bool Array. When warning=True
|
2265
|
+
or throw=True, calling this function materializes the array.
|
2182
2266
|
|
2183
2267
|
See Also
|
2184
2268
|
--------
|
@@ -2226,8 +2310,17 @@ def is_valid_linkage(Z, warning=False, throw=False, name=None):
|
|
2226
2310
|
|
2227
2311
|
"""
|
2228
2312
|
xp = array_namespace(Z)
|
2229
|
-
Z = _asarray(Z,
|
2230
|
-
|
2313
|
+
Z = _asarray(Z, xp=xp)
|
2314
|
+
return _is_valid_linkage(Z, warning=warning, throw=throw,
|
2315
|
+
name=name, materialize=True, xp=xp)
|
2316
|
+
|
2317
|
+
|
2318
|
+
def _is_valid_linkage(Z, warning=False, throw=False, name=None,
|
2319
|
+
materialize=False, *, xp):
|
2320
|
+
"""Variant of `is_valid_linkage` to be called internally by other scipy functions,
|
2321
|
+
which by default does not materialize lazy input arrays (Dask, JAX, etc.) when
|
2322
|
+
warning=True or throw=True.
|
2323
|
+
"""
|
2231
2324
|
name_str = f"{name!r} " if name else ''
|
2232
2325
|
try:
|
2233
2326
|
if Z.dtype != xp.float64:
|
@@ -2240,66 +2333,88 @@ def is_valid_linkage(Z, warning=False, throw=False, name=None):
|
|
2240
2333
|
if Z.shape[0] == 0:
|
2241
2334
|
raise ValueError('Linkage must be computed on at least two '
|
2242
2335
|
'observations.')
|
2243
|
-
|
2244
|
-
if n > 1:
|
2245
|
-
if (xp.any(Z[:, 0] < 0) or xp.any(Z[:, 1] < 0)):
|
2246
|
-
raise ValueError(f'Linkage {name_str}contains negative indices.')
|
2247
|
-
if xp.any(Z[:, 2] < 0):
|
2248
|
-
raise ValueError(f'Linkage {name_str}contains negative distances.')
|
2249
|
-
if xp.any(Z[:, 3] < 0):
|
2250
|
-
raise ValueError(f'Linkage {name_str}contains negative counts.')
|
2251
|
-
if xp.any(Z[:, 3] > (Z.shape[0] + 1)):
|
2252
|
-
raise ValueError('Linkage matrix contains excessive observations'
|
2253
|
-
'in a cluster')
|
2254
|
-
if _check_hierarchy_uses_cluster_before_formed(Z):
|
2255
|
-
raise ValueError(f'Linkage {name_str}uses non-singleton cluster before'
|
2256
|
-
' it is formed.')
|
2257
|
-
if _check_hierarchy_uses_cluster_more_than_once(Z):
|
2258
|
-
raise ValueError(f'Linkage {name_str}uses the same cluster more than once.')
|
2259
|
-
except Exception as e:
|
2336
|
+
except (TypeError, ValueError) as e:
|
2260
2337
|
if throw:
|
2261
2338
|
raise
|
2262
2339
|
if warning:
|
2263
2340
|
_warning(str(e))
|
2264
|
-
|
2341
|
+
return False
|
2342
|
+
|
2343
|
+
n = Z.shape[0]
|
2344
|
+
if n < 2:
|
2345
|
+
return True
|
2346
|
+
|
2347
|
+
return _lazy_valid_checks(
|
2348
|
+
(xp.any(Z[:, :2] < 0),
|
2349
|
+
f'Linkage {name_str}contains negative indices.'),
|
2350
|
+
(xp.any(Z[:, 2] < 0),
|
2351
|
+
f'Linkage {name_str}contains negative distances.'),
|
2352
|
+
(xp.any(Z[:, 3] < 0),
|
2353
|
+
f'Linkage {name_str}contains negative counts.'),
|
2354
|
+
(xp.any(Z[:, 3] > n + 1),
|
2355
|
+
f'Linkage {name_str}contains excessive observations in a cluster'),
|
2356
|
+
(xp.any(xp.max(Z[:, :2], axis=1) >= xp.arange(n + 1, 2 * n + 1, dtype=Z.dtype)),
|
2357
|
+
f'Linkage {name_str}uses non-singleton cluster before it is formed.'),
|
2358
|
+
(xpx.nunique(Z[:, :2]) < n * 2,
|
2359
|
+
f'Linkage {name_str}uses the same cluster more than once.'),
|
2360
|
+
throw=throw, warning=warning, materialize=materialize, xp=xp
|
2361
|
+
)
|
2362
|
+
|
2363
|
+
|
2364
|
+
def _lazy_valid_checks(*args, throw=False, warning=False, materialize=False, xp):
|
2365
|
+
"""Validate a set of conditions on the contents of possibly lazy arrays.
|
2265
2366
|
|
2266
|
-
|
2367
|
+
Parameters
|
2368
|
+
----------
|
2369
|
+
args : tuples of (Array, str)
|
2370
|
+
The first element of each tuple must be a 0-dimensional Array
|
2371
|
+
that evaluates to bool; the second element must be the message to convey
|
2372
|
+
if the first element evaluates to True.
|
2373
|
+
throw: bool
|
2374
|
+
Set to True to `raise ValueError(args[i][1])` if `args[i][0]` is True.
|
2375
|
+
warning: bool
|
2376
|
+
Set to True to issue a warning with message `args[i][1]` if `args[i][0]`
|
2377
|
+
is True.
|
2378
|
+
materialize: bool
|
2379
|
+
Set to True to force materialization of lazy arrays when throw=True or
|
2380
|
+
warning=True. If the inputs are lazy and materialize=False, ignore the
|
2381
|
+
`throw` and `warning` flags.
|
2382
|
+
xp: module
|
2383
|
+
Array API namespace
|
2267
2384
|
|
2385
|
+
Returns
|
2386
|
+
-------
|
2387
|
+
If xp is an eager backend (e.g. numpy) and all conditions are False, return True.
|
2388
|
+
If throw is True, raise. Otherwise, return False.
|
2268
2389
|
|
2269
|
-
|
2270
|
-
|
2271
|
-
|
2272
|
-
if Z[i, 0] >= n + i or Z[i, 1] >= n + i:
|
2273
|
-
return True
|
2274
|
-
return False
|
2390
|
+
If xp is a lazy backend (e.g. Dask or JAX), return a 0-dimensional bool Array.
|
2391
|
+
"""
|
2392
|
+
conds = xp.concat([xp.reshape(cond, (1, )) for cond, _ in args])
|
2275
2393
|
|
2394
|
+
lazy = is_lazy_array(conds)
|
2395
|
+
if not throw and not warning or (lazy and not materialize):
|
2396
|
+
out = ~xp.any(conds)
|
2397
|
+
return out if lazy else bool(out)
|
2276
2398
|
|
2277
|
-
|
2278
|
-
|
2279
|
-
|
2280
|
-
|
2281
|
-
|
2282
|
-
|
2283
|
-
|
2284
|
-
|
2285
|
-
|
2286
|
-
|
2287
|
-
|
2288
|
-
|
2289
|
-
|
2290
|
-
|
2291
|
-
|
2292
|
-
|
2293
|
-
def _check_hierarchy_not_all_clusters_used(Z):
|
2294
|
-
n = Z.shape[0] + 1
|
2295
|
-
chosen = set()
|
2296
|
-
for i in range(0, n - 1):
|
2297
|
-
chosen.add(int(Z[i, 0]))
|
2298
|
-
chosen.add(int(Z[i, 1]))
|
2299
|
-
must_chosen = set(range(0, 2 * n - 2))
|
2300
|
-
return len(must_chosen.difference(chosen)) > 0
|
2399
|
+
if is_dask(xp):
|
2400
|
+
# Only materialize the graph once, instead of once per check
|
2401
|
+
conds = conds.compute()
|
2402
|
+
|
2403
|
+
# Don't call np.asarray(conds), as it would be blocked by the device transfer
|
2404
|
+
# guard on CuPy and PyTorch and the densification guard on Sparse, whereas
|
2405
|
+
# bool() will not.
|
2406
|
+
conds = [bool(cond) for cond in conds]
|
2407
|
+
|
2408
|
+
for cond, (_, msg) in zip(conds, args):
|
2409
|
+
if throw and cond:
|
2410
|
+
raise ValueError(msg)
|
2411
|
+
elif warning and cond:
|
2412
|
+
warnings.warn(msg, ClusterWarning, stacklevel=3)
|
2301
2413
|
|
2414
|
+
return not any(conds)
|
2302
2415
|
|
2416
|
+
|
2417
|
+
@xp_capabilities()
|
2303
2418
|
def num_obs_linkage(Z):
|
2304
2419
|
"""
|
2305
2420
|
Return the number of original observations of the linkage matrix passed.
|
@@ -2334,11 +2449,12 @@ def num_obs_linkage(Z):
|
|
2334
2449
|
|
2335
2450
|
"""
|
2336
2451
|
xp = array_namespace(Z)
|
2337
|
-
Z = _asarray(Z,
|
2338
|
-
|
2339
|
-
return
|
2452
|
+
Z = _asarray(Z, xp=xp)
|
2453
|
+
_is_valid_linkage(Z, throw=True, name='Z', xp=xp)
|
2454
|
+
return Z.shape[0] + 1
|
2340
2455
|
|
2341
2456
|
|
2457
|
+
@xp_capabilities()
|
2342
2458
|
def correspond(Z, Y):
|
2343
2459
|
"""
|
2344
2460
|
Check for correspondence between linkage and condensed distance matrices.
|
@@ -2388,14 +2504,16 @@ def correspond(Z, Y):
|
|
2388
2504
|
True
|
2389
2505
|
|
2390
2506
|
"""
|
2391
|
-
is_valid_linkage(Z, throw=True)
|
2392
|
-
distance.is_valid_y(Y, throw=True)
|
2393
2507
|
xp = array_namespace(Z, Y)
|
2394
|
-
Z = _asarray(Z,
|
2395
|
-
Y = _asarray(Y,
|
2508
|
+
Z = _asarray(Z, xp=xp)
|
2509
|
+
Y = _asarray(Y, xp=xp)
|
2510
|
+
_is_valid_linkage(Z, throw=True, xp=xp)
|
2511
|
+
distance.is_valid_y(Y, throw=True)
|
2396
2512
|
return distance.num_obs_y(Y) == num_obs_linkage(Z)
|
2397
2513
|
|
2398
2514
|
|
2515
|
+
@xp_capabilities(cpu_only=True, reason="Cython code",
|
2516
|
+
jax_jit=False, allow_dask_compute=True)
|
2399
2517
|
def fcluster(Z, t, criterion='inconsistent', depth=2, R=None, monocrit=None):
|
2400
2518
|
"""
|
2401
2519
|
Form flat clusters from the hierarchical clustering defined by
|
@@ -2549,7 +2667,7 @@ def fcluster(Z, t, criterion='inconsistent', depth=2, R=None, monocrit=None):
|
|
2549
2667
|
"""
|
2550
2668
|
xp = array_namespace(Z)
|
2551
2669
|
Z = _asarray(Z, order='C', dtype=xp.float64, xp=xp)
|
2552
|
-
|
2670
|
+
_is_valid_linkage(Z, throw=True, name='Z', materialize=True, xp=xp)
|
2553
2671
|
|
2554
2672
|
n = Z.shape[0] + 1
|
2555
2673
|
T = np.zeros((n,), dtype='i')
|
@@ -2564,7 +2682,7 @@ def fcluster(Z, t, criterion='inconsistent', depth=2, R=None, monocrit=None):
|
|
2564
2682
|
R = inconsistent(Z, depth)
|
2565
2683
|
else:
|
2566
2684
|
R = _asarray(R, order='C', dtype=xp.float64, xp=xp)
|
2567
|
-
|
2685
|
+
_is_valid_im(R, throw=True, name='R', materialize=True, xp=xp)
|
2568
2686
|
# Since the C code does not support striding using strides.
|
2569
2687
|
# The dimensions are used instead.
|
2570
2688
|
R = np.asarray(R)
|
@@ -2582,6 +2700,8 @@ def fcluster(Z, t, criterion='inconsistent', depth=2, R=None, monocrit=None):
|
|
2582
2700
|
return xp.asarray(T)
|
2583
2701
|
|
2584
2702
|
|
2703
|
+
@xp_capabilities(cpu_only=True, reason="Cython code",
|
2704
|
+
jax_jit=False, allow_dask_compute=True)
|
2585
2705
|
def fclusterdata(X, t, criterion='inconsistent',
|
2586
2706
|
metric='euclidean', depth=2, method='single', R=None):
|
2587
2707
|
"""
|
@@ -2671,20 +2791,19 @@ def fclusterdata(X, t, criterion='inconsistent',
|
|
2671
2791
|
X = _asarray(X, order='C', dtype=xp.float64, xp=xp)
|
2672
2792
|
|
2673
2793
|
if X.ndim != 2:
|
2674
|
-
raise TypeError('The observation matrix X must be an n by m '
|
2675
|
-
'array.')
|
2794
|
+
raise TypeError('The observation matrix X must be an n by m array.')
|
2676
2795
|
|
2677
2796
|
Y = distance.pdist(X, metric=metric)
|
2678
|
-
Y = xp.asarray(Y)
|
2679
2797
|
Z = linkage(Y, method=method)
|
2680
2798
|
if R is None:
|
2681
2799
|
R = inconsistent(Z, d=depth)
|
2682
2800
|
else:
|
2683
|
-
R = _asarray(R, order='
|
2801
|
+
R = _asarray(R, order='C', xp=xp)
|
2684
2802
|
T = fcluster(Z, criterion=criterion, depth=depth, R=R, t=t)
|
2685
2803
|
return T
|
2686
2804
|
|
2687
2805
|
|
2806
|
+
@lazy_cython
|
2688
2807
|
def leaves_list(Z):
|
2689
2808
|
"""
|
2690
2809
|
Return a list of leaf node ids.
|
@@ -2735,12 +2854,20 @@ def leaves_list(Z):
|
|
2735
2854
|
"""
|
2736
2855
|
xp = array_namespace(Z)
|
2737
2856
|
Z = _asarray(Z, order='C', xp=xp)
|
2738
|
-
|
2857
|
+
_is_valid_linkage(Z, throw=True, name='Z', xp=xp)
|
2858
|
+
|
2859
|
+
def cy_leaves_list(Z, validate):
|
2860
|
+
if validate:
|
2861
|
+
_is_valid_linkage(Z, throw=True, name='Z', xp=np)
|
2862
|
+
n = Z.shape[0] + 1
|
2863
|
+
ML = np.zeros((n,), dtype=np.int32)
|
2864
|
+
_hierarchy.prelist(Z, ML, n)
|
2865
|
+
return ML
|
2866
|
+
|
2739
2867
|
n = Z.shape[0] + 1
|
2740
|
-
|
2741
|
-
|
2742
|
-
|
2743
|
-
return xp.asarray(ML)
|
2868
|
+
return xpx.lazy_apply(cy_leaves_list, Z, validate=is_lazy_array(Z),
|
2869
|
+
shape=(n, ), dtype=xp.int32,
|
2870
|
+
as_numpy=True, xp=xp)
|
2744
2871
|
|
2745
2872
|
|
2746
2873
|
# Maps number of leaves to text size.
|
@@ -2987,7 +3114,7 @@ def set_link_color_palette(palette):
|
|
2987
3114
|
if palette is None:
|
2988
3115
|
# reset to its default
|
2989
3116
|
palette = _link_line_colors_default
|
2990
|
-
elif not isinstance(palette,
|
3117
|
+
elif not isinstance(palette, list | tuple):
|
2991
3118
|
raise TypeError("palette must be a list or tuple")
|
2992
3119
|
_ptypes = [isinstance(p, str) for p in palette]
|
2993
3120
|
|
@@ -2998,6 +3125,7 @@ def set_link_color_palette(palette):
|
|
2998
3125
|
_link_line_colors = palette
|
2999
3126
|
|
3000
3127
|
|
3128
|
+
@xp_capabilities(cpu_only=True, jax_jit=False, allow_dask_compute=True)
|
3001
3129
|
def dendrogram(Z, p=30, truncate_mode=None, color_threshold=None,
|
3002
3130
|
get_leaves=True, orientation='top', labels=None,
|
3003
3131
|
count_sort=False, distance_sort=False, show_leaf_counts=True,
|
@@ -3274,7 +3402,7 @@ def dendrogram(Z, p=30, truncate_mode=None, color_threshold=None,
|
|
3274
3402
|
# None orders leaf nodes based on the order they appear in the
|
3275
3403
|
# pre-order traversal.
|
3276
3404
|
xp = array_namespace(Z)
|
3277
|
-
Z = _asarray(Z, order='
|
3405
|
+
Z = _asarray(Z, order='C', xp=xp)
|
3278
3406
|
|
3279
3407
|
if orientation not in ["top", "left", "bottom", "right"]:
|
3280
3408
|
raise ValueError("orientation must be one of 'top', 'left', "
|
@@ -3288,10 +3416,10 @@ def dendrogram(Z, p=30, truncate_mode=None, color_threshold=None,
|
|
3288
3416
|
if Z.shape[0] + 1 != len_labels:
|
3289
3417
|
raise ValueError("Dimensions of Z and labels must be consistent.")
|
3290
3418
|
|
3291
|
-
|
3419
|
+
_is_valid_linkage(Z, throw=True, name='Z', materialize=True, xp=xp)
|
3292
3420
|
Zs = Z.shape
|
3293
3421
|
n = Zs[0] + 1
|
3294
|
-
if isinstance(p,
|
3422
|
+
if isinstance(p, int | float):
|
3295
3423
|
p = int(p)
|
3296
3424
|
else:
|
3297
3425
|
raise TypeError('The second argument must be a number')
|
@@ -3681,6 +3809,8 @@ def _dendrogram_calculate_info(Z, p, truncate_mode,
|
|
3681
3809
|
return (((uiva + uivb) / 2), uwa + uwb, h, max_dist)
|
3682
3810
|
|
3683
3811
|
|
3812
|
+
@xp_capabilities(cpu_only=True,
|
3813
|
+
warnings=[("dask.array", "see notes"), ("jax.numpy", "see notes")])
|
3684
3814
|
def is_isomorphic(T1, T2):
|
3685
3815
|
"""
|
3686
3816
|
Determine if two different cluster assignments are equivalent.
|
@@ -3698,6 +3828,11 @@ def is_isomorphic(T1, T2):
|
|
3698
3828
|
Whether the flat cluster assignments `T1` and `T2` are
|
3699
3829
|
equivalent.
|
3700
3830
|
|
3831
|
+
Notes
|
3832
|
+
-----
|
3833
|
+
*Array API support (experimental):* If the input is a lazy Array (e.g. Dask
|
3834
|
+
or JAX), the return value will be a 0-dimensional bool Array.
|
3835
|
+
|
3701
3836
|
See Also
|
3702
3837
|
--------
|
3703
3838
|
linkage : for a description of what a linkage matrix is.
|
@@ -3712,7 +3847,7 @@ def is_isomorphic(T1, T2):
|
|
3712
3847
|
Two flat cluster assignments can be isomorphic if they represent the same
|
3713
3848
|
cluster assignment, with different labels.
|
3714
3849
|
|
3715
|
-
For example, we can use the `scipy.cluster.hierarchy.single
|
3850
|
+
For example, we can use the `scipy.cluster.hierarchy.single` method
|
3716
3851
|
and flatten the output to four clusters:
|
3717
3852
|
|
3718
3853
|
>>> X = [[0, 0], [0, 1], [1, 0],
|
@@ -3742,35 +3877,40 @@ def is_isomorphic(T1, T2):
|
|
3742
3877
|
True
|
3743
3878
|
|
3744
3879
|
"""
|
3745
|
-
|
3746
|
-
|
3880
|
+
xp = array_namespace(T1, T2)
|
3881
|
+
T1 = _asarray(T1, xp=xp)
|
3882
|
+
T2 = _asarray(T2, xp=xp)
|
3747
3883
|
|
3748
|
-
|
3749
|
-
T2S = T2.shape
|
3750
|
-
|
3751
|
-
if len(T1S) != 1:
|
3884
|
+
if T1.ndim != 1:
|
3752
3885
|
raise ValueError('T1 must be one-dimensional.')
|
3753
|
-
if
|
3886
|
+
if T2.ndim != 1:
|
3754
3887
|
raise ValueError('T2 must be one-dimensional.')
|
3755
|
-
if
|
3888
|
+
if T1.shape != T2.shape:
|
3756
3889
|
raise ValueError('T1 and T2 must have the same number of elements.')
|
3757
|
-
|
3758
|
-
|
3759
|
-
|
3760
|
-
|
3761
|
-
|
3762
|
-
if
|
3763
|
-
|
3764
|
-
|
3890
|
+
|
3891
|
+
def py_is_isomorphic(T1, T2):
|
3892
|
+
d1 = {}
|
3893
|
+
d2 = {}
|
3894
|
+
for t1, t2 in zip(T1, T2):
|
3895
|
+
if t1 in d1:
|
3896
|
+
if t2 not in d2:
|
3897
|
+
return False
|
3898
|
+
if d1[t1] != t2 or d2[t2] != t1:
|
3899
|
+
return False
|
3900
|
+
elif t2 in d2:
|
3765
3901
|
return False
|
3766
|
-
|
3767
|
-
|
3768
|
-
|
3769
|
-
|
3770
|
-
|
3771
|
-
|
3902
|
+
else:
|
3903
|
+
d1[t1] = t2
|
3904
|
+
d2[t2] = t1
|
3905
|
+
return True
|
3906
|
+
|
3907
|
+
res = xpx.lazy_apply(py_is_isomorphic, T1, T2,
|
3908
|
+
shape=(), dtype=xp.bool,
|
3909
|
+
as_numpy=True, xp=xp)
|
3910
|
+
return res if is_lazy_array(res) else bool(res)
|
3772
3911
|
|
3773
3912
|
|
3913
|
+
@lazy_cython
|
3774
3914
|
def maxdists(Z):
|
3775
3915
|
"""
|
3776
3916
|
Return the maximum distance between any non-singleton cluster.
|
@@ -3846,16 +3986,21 @@ def maxdists(Z):
|
|
3846
3986
|
"""
|
3847
3987
|
xp = array_namespace(Z)
|
3848
3988
|
Z = _asarray(Z, order='C', dtype=xp.float64, xp=xp)
|
3849
|
-
|
3989
|
+
_is_valid_linkage(Z, throw=True, name='Z', xp=xp)
|
3850
3990
|
|
3851
|
-
|
3852
|
-
|
3853
|
-
|
3854
|
-
|
3855
|
-
|
3856
|
-
|
3991
|
+
def cy_maxdists(Z, validate):
|
3992
|
+
if validate:
|
3993
|
+
_is_valid_linkage(Z, throw=True, name='Z', xp=np)
|
3994
|
+
MD = np.zeros((Z.shape[0],))
|
3995
|
+
_hierarchy.get_max_dist_for_each_cluster(Z, MD, Z.shape[0] + 1)
|
3996
|
+
return MD
|
3997
|
+
|
3998
|
+
return xpx.lazy_apply(cy_maxdists, Z, validate=is_lazy_array(Z),
|
3999
|
+
shape=(Z.shape[0], ), dtype=xp.float64,
|
4000
|
+
as_numpy=True, xp=xp)
|
3857
4001
|
|
3858
4002
|
|
4003
|
+
@lazy_cython
|
3859
4004
|
def maxinconsts(Z, R):
|
3860
4005
|
"""
|
3861
4006
|
Return the maximum inconsistency coefficient for each
|
@@ -3934,21 +4079,28 @@ def maxinconsts(Z, R):
|
|
3934
4079
|
xp = array_namespace(Z, R)
|
3935
4080
|
Z = _asarray(Z, order='C', dtype=xp.float64, xp=xp)
|
3936
4081
|
R = _asarray(R, order='C', dtype=xp.float64, xp=xp)
|
3937
|
-
|
3938
|
-
|
4082
|
+
_is_valid_linkage(Z, throw=True, name='Z', xp=xp)
|
4083
|
+
_is_valid_im(R, throw=True, name='R', xp=xp)
|
3939
4084
|
|
3940
|
-
n = Z.shape[0] + 1
|
3941
4085
|
if Z.shape[0] != R.shape[0]:
|
3942
4086
|
raise ValueError("The inconsistency matrix and linkage matrix each "
|
3943
4087
|
"have a different number of rows.")
|
3944
|
-
|
3945
|
-
Z
|
3946
|
-
|
3947
|
-
|
3948
|
-
|
3949
|
-
|
3950
|
-
|
3951
|
-
|
4088
|
+
|
4089
|
+
def cy_maxinconsts(Z, R, validate):
|
4090
|
+
if validate:
|
4091
|
+
_is_valid_linkage(Z, throw=True, name='Z', xp=np)
|
4092
|
+
_is_valid_im(R, throw=True, name='R', xp=np)
|
4093
|
+
n = Z.shape[0] + 1
|
4094
|
+
MI = np.zeros((n - 1,))
|
4095
|
+
_hierarchy.get_max_Rfield_for_each_cluster(Z, R, MI, n, 3)
|
4096
|
+
return MI
|
4097
|
+
|
4098
|
+
return xpx.lazy_apply(cy_maxinconsts, Z, R, validate=is_lazy_array(Z),
|
4099
|
+
shape=(Z.shape[0], ), dtype=xp.float64,
|
4100
|
+
as_numpy=True, xp=xp)
|
4101
|
+
|
4102
|
+
|
4103
|
+
@lazy_cython
|
3952
4104
|
def maxRstat(Z, R, i):
|
3953
4105
|
"""
|
3954
4106
|
Return the maximum statistic for each non-singleton cluster and its
|
@@ -4029,8 +4181,8 @@ def maxRstat(Z, R, i):
|
|
4029
4181
|
xp = array_namespace(Z, R)
|
4030
4182
|
Z = _asarray(Z, order='C', dtype=xp.float64, xp=xp)
|
4031
4183
|
R = _asarray(R, order='C', dtype=xp.float64, xp=xp)
|
4032
|
-
|
4033
|
-
|
4184
|
+
_is_valid_linkage(Z, throw=True, name='Z', xp=xp)
|
4185
|
+
_is_valid_im(R, throw=True, name='R', xp=xp)
|
4034
4186
|
|
4035
4187
|
if not isinstance(i, int):
|
4036
4188
|
raise TypeError('The third argument must be an integer.')
|
@@ -4042,15 +4194,23 @@ def maxRstat(Z, R, i):
|
|
4042
4194
|
raise ValueError("The inconsistency matrix and linkage matrix each "
|
4043
4195
|
"have a different number of rows.")
|
4044
4196
|
|
4045
|
-
|
4046
|
-
|
4047
|
-
|
4048
|
-
|
4049
|
-
|
4050
|
-
|
4051
|
-
|
4197
|
+
def cy_maxRstat(Z, R, i, validate):
|
4198
|
+
if validate:
|
4199
|
+
_is_valid_linkage(Z, throw=True, name='Z', xp=np)
|
4200
|
+
_is_valid_im(R, throw=True, name='R', xp=np)
|
4201
|
+
MR = np.zeros((Z.shape[0],))
|
4202
|
+
n = Z.shape[0] + 1
|
4203
|
+
_hierarchy.get_max_Rfield_for_each_cluster(Z, R, MR, n, i)
|
4204
|
+
return MR
|
4205
|
+
|
4206
|
+
return xpx.lazy_apply(cy_maxRstat, Z, R, i=i, validate=is_lazy_array(Z),
|
4207
|
+
shape=(Z.shape[0], ), dtype=xp.float64,
|
4208
|
+
as_numpy=True, xp=xp)
|
4052
4209
|
|
4053
4210
|
|
4211
|
+
# Data-dependent output shape makes it impossible to use jax.jit
|
4212
|
+
@xp_capabilities(cpu_only=True, reason="Cython code", jax_jit=False,
|
4213
|
+
warnings=[("dask.array", "merges chunks")])
|
4054
4214
|
def leaders(Z, T):
|
4055
4215
|
"""
|
4056
4216
|
Return the root nodes in a hierarchical clustering.
|
@@ -4152,11 +4312,16 @@ def leaders(Z, T):
|
|
4152
4312
|
>>> M
|
4153
4313
|
array([1, 2, 3, 4], dtype=int32)
|
4154
4314
|
|
4315
|
+
Notes
|
4316
|
+
-----
|
4317
|
+
*Array API support (experimental):* This function returns arrays
|
4318
|
+
with data-dependent shape. In JAX, at the moment of writing this makes it
|
4319
|
+
impossible to execute it inside `@jax.jit`.
|
4155
4320
|
"""
|
4156
4321
|
xp = array_namespace(Z, T)
|
4157
4322
|
Z = _asarray(Z, order='C', dtype=xp.float64, xp=xp)
|
4158
4323
|
T = _asarray(T, order='C', xp=xp)
|
4159
|
-
|
4324
|
+
_is_valid_linkage(Z, throw=True, name='Z', xp=xp)
|
4160
4325
|
|
4161
4326
|
if T.dtype != xp.int32:
|
4162
4327
|
raise TypeError('T must be a 1-D array of dtype int32.')
|
@@ -4164,15 +4329,20 @@ def leaders(Z, T):
|
|
4164
4329
|
if T.shape[0] != Z.shape[0] + 1:
|
4165
4330
|
raise ValueError('Mismatch: len(T)!=Z.shape[0] + 1.')
|
4166
4331
|
|
4167
|
-
|
4168
|
-
|
4169
|
-
|
4170
|
-
|
4171
|
-
|
4172
|
-
|
4173
|
-
|
4174
|
-
|
4175
|
-
|
4176
|
-
|
4177
|
-
|
4178
|
-
|
4332
|
+
n_obs = Z.shape[0] + 1
|
4333
|
+
|
4334
|
+
def cy_leaders(Z, T, validate):
|
4335
|
+
if validate:
|
4336
|
+
_is_valid_linkage(Z, throw=True, name='Z', xp=np)
|
4337
|
+
n_clusters = int(xpx.nunique(T))
|
4338
|
+
L = np.zeros(n_clusters, dtype=np.int32)
|
4339
|
+
M = np.zeros(n_clusters, dtype=np.int32)
|
4340
|
+
s = _hierarchy.leaders(Z, T, L, M, n_clusters, n_obs)
|
4341
|
+
if s >= 0:
|
4342
|
+
raise ValueError('T is not a valid assignment vector. Error found '
|
4343
|
+
f'when examining linkage node {s} (< 2n-1).')
|
4344
|
+
return L, M
|
4345
|
+
|
4346
|
+
return xpx.lazy_apply(cy_leaders, Z, T, validate=is_lazy_array(Z),
|
4347
|
+
shape=((None,), (None, )), dtype=(xp.int32, xp.int32),
|
4348
|
+
as_numpy=True, xp=xp)
|