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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (759) hide show
  1. scipy/__config__.py +8 -8
  2. scipy/__init__.py +3 -6
  3. scipy/_cyutility.cp312-win_amd64.dll.a +0 -0
  4. scipy/_cyutility.cp312-win_amd64.pyd +0 -0
  5. scipy/_lib/_array_api.py +486 -161
  6. scipy/_lib/_array_api_compat_vendor.py +9 -0
  7. scipy/_lib/_bunch.py +4 -0
  8. scipy/_lib/_ccallback_c.cp312-win_amd64.dll.a +0 -0
  9. scipy/_lib/_ccallback_c.cp312-win_amd64.pyd +0 -0
  10. scipy/_lib/_docscrape.py +1 -1
  11. scipy/_lib/_elementwise_iterative_method.py +15 -26
  12. scipy/_lib/_fpumode.cp312-win_amd64.dll.a +0 -0
  13. scipy/_lib/_fpumode.cp312-win_amd64.pyd +0 -0
  14. scipy/_lib/_sparse.py +41 -0
  15. scipy/_lib/_test_ccallback.cp312-win_amd64.dll.a +0 -0
  16. scipy/_lib/_test_ccallback.cp312-win_amd64.pyd +0 -0
  17. scipy/_lib/_test_deprecation_call.cp312-win_amd64.dll.a +0 -0
  18. scipy/_lib/_test_deprecation_call.cp312-win_amd64.pyd +0 -0
  19. scipy/_lib/_test_deprecation_def.cp312-win_amd64.dll.a +0 -0
  20. scipy/_lib/_test_deprecation_def.cp312-win_amd64.pyd +0 -0
  21. scipy/_lib/_testutils.py +6 -2
  22. scipy/_lib/_uarray/_uarray.cp312-win_amd64.dll.a +0 -0
  23. scipy/_lib/_uarray/_uarray.cp312-win_amd64.pyd +0 -0
  24. scipy/_lib/_util.py +222 -125
  25. scipy/_lib/array_api_compat/__init__.py +4 -4
  26. scipy/_lib/array_api_compat/_internal.py +19 -6
  27. scipy/_lib/array_api_compat/common/__init__.py +1 -1
  28. scipy/_lib/array_api_compat/common/_aliases.py +365 -193
  29. scipy/_lib/array_api_compat/common/_fft.py +94 -64
  30. scipy/_lib/array_api_compat/common/_helpers.py +413 -180
  31. scipy/_lib/array_api_compat/common/_linalg.py +116 -40
  32. scipy/_lib/array_api_compat/common/_typing.py +179 -10
  33. scipy/_lib/array_api_compat/cupy/__init__.py +1 -4
  34. scipy/_lib/array_api_compat/cupy/_aliases.py +61 -41
  35. scipy/_lib/array_api_compat/cupy/_info.py +16 -6
  36. scipy/_lib/array_api_compat/cupy/_typing.py +24 -39
  37. scipy/_lib/array_api_compat/dask/array/__init__.py +6 -3
  38. scipy/_lib/array_api_compat/dask/array/_aliases.py +267 -108
  39. scipy/_lib/array_api_compat/dask/array/_info.py +105 -34
  40. scipy/_lib/array_api_compat/dask/array/fft.py +5 -8
  41. scipy/_lib/array_api_compat/dask/array/linalg.py +21 -22
  42. scipy/_lib/array_api_compat/numpy/__init__.py +13 -15
  43. scipy/_lib/array_api_compat/numpy/_aliases.py +98 -49
  44. scipy/_lib/array_api_compat/numpy/_info.py +36 -16
  45. scipy/_lib/array_api_compat/numpy/_typing.py +27 -43
  46. scipy/_lib/array_api_compat/numpy/fft.py +11 -5
  47. scipy/_lib/array_api_compat/numpy/linalg.py +75 -22
  48. scipy/_lib/array_api_compat/torch/__init__.py +3 -5
  49. scipy/_lib/array_api_compat/torch/_aliases.py +262 -159
  50. scipy/_lib/array_api_compat/torch/_info.py +27 -16
  51. scipy/_lib/array_api_compat/torch/_typing.py +3 -0
  52. scipy/_lib/array_api_compat/torch/fft.py +17 -18
  53. scipy/_lib/array_api_compat/torch/linalg.py +16 -16
  54. scipy/_lib/array_api_extra/__init__.py +26 -3
  55. scipy/_lib/array_api_extra/_delegation.py +171 -0
  56. scipy/_lib/array_api_extra/_lib/__init__.py +1 -0
  57. scipy/_lib/array_api_extra/_lib/_at.py +463 -0
  58. scipy/_lib/array_api_extra/_lib/_backends.py +46 -0
  59. scipy/_lib/array_api_extra/_lib/_funcs.py +937 -0
  60. scipy/_lib/array_api_extra/_lib/_lazy.py +357 -0
  61. scipy/_lib/array_api_extra/_lib/_testing.py +278 -0
  62. scipy/_lib/array_api_extra/_lib/_utils/__init__.py +1 -0
  63. scipy/_lib/array_api_extra/_lib/_utils/_compat.py +74 -0
  64. scipy/_lib/array_api_extra/_lib/_utils/_compat.pyi +45 -0
  65. scipy/_lib/array_api_extra/_lib/_utils/_helpers.py +559 -0
  66. scipy/_lib/array_api_extra/_lib/_utils/_typing.py +10 -0
  67. scipy/_lib/array_api_extra/_lib/_utils/_typing.pyi +105 -0
  68. scipy/_lib/array_api_extra/testing.py +359 -0
  69. scipy/_lib/decorator.py +2 -2
  70. scipy/_lib/doccer.py +1 -7
  71. scipy/_lib/messagestream.cp312-win_amd64.dll.a +0 -0
  72. scipy/_lib/messagestream.cp312-win_amd64.pyd +0 -0
  73. scipy/_lib/pyprima/__init__.py +212 -0
  74. scipy/_lib/pyprima/cobyla/__init__.py +0 -0
  75. scipy/_lib/pyprima/cobyla/cobyla.py +559 -0
  76. scipy/_lib/pyprima/cobyla/cobylb.py +714 -0
  77. scipy/_lib/pyprima/cobyla/geometry.py +226 -0
  78. scipy/_lib/pyprima/cobyla/initialize.py +215 -0
  79. scipy/_lib/pyprima/cobyla/trustregion.py +492 -0
  80. scipy/_lib/pyprima/cobyla/update.py +289 -0
  81. scipy/_lib/pyprima/common/__init__.py +0 -0
  82. scipy/_lib/pyprima/common/_bounds.py +34 -0
  83. scipy/_lib/pyprima/common/_linear_constraints.py +46 -0
  84. scipy/_lib/pyprima/common/_nonlinear_constraints.py +54 -0
  85. scipy/_lib/pyprima/common/_project.py +173 -0
  86. scipy/_lib/pyprima/common/checkbreak.py +93 -0
  87. scipy/_lib/pyprima/common/consts.py +47 -0
  88. scipy/_lib/pyprima/common/evaluate.py +99 -0
  89. scipy/_lib/pyprima/common/history.py +38 -0
  90. scipy/_lib/pyprima/common/infos.py +30 -0
  91. scipy/_lib/pyprima/common/linalg.py +435 -0
  92. scipy/_lib/pyprima/common/message.py +290 -0
  93. scipy/_lib/pyprima/common/powalg.py +131 -0
  94. scipy/_lib/pyprima/common/preproc.py +277 -0
  95. scipy/_lib/pyprima/common/present.py +5 -0
  96. scipy/_lib/pyprima/common/ratio.py +54 -0
  97. scipy/_lib/pyprima/common/redrho.py +47 -0
  98. scipy/_lib/pyprima/common/selectx.py +296 -0
  99. scipy/_lib/tests/test__util.py +105 -121
  100. scipy/_lib/tests/test_array_api.py +166 -35
  101. scipy/_lib/tests/test_bunch.py +7 -0
  102. scipy/_lib/tests/test_ccallback.py +2 -10
  103. scipy/_lib/tests/test_public_api.py +13 -0
  104. scipy/cluster/_hierarchy.cp312-win_amd64.dll.a +0 -0
  105. scipy/cluster/_hierarchy.cp312-win_amd64.pyd +0 -0
  106. scipy/cluster/_optimal_leaf_ordering.cp312-win_amd64.dll.a +0 -0
  107. scipy/cluster/_optimal_leaf_ordering.cp312-win_amd64.pyd +0 -0
  108. scipy/cluster/_vq.cp312-win_amd64.dll.a +0 -0
  109. scipy/cluster/_vq.cp312-win_amd64.pyd +0 -0
  110. scipy/cluster/hierarchy.py +393 -223
  111. scipy/cluster/tests/test_hierarchy.py +273 -335
  112. scipy/cluster/tests/test_vq.py +45 -61
  113. scipy/cluster/vq.py +39 -35
  114. scipy/conftest.py +263 -157
  115. scipy/constants/_constants.py +4 -1
  116. scipy/constants/tests/test_codata.py +2 -2
  117. scipy/constants/tests/test_constants.py +11 -18
  118. scipy/datasets/_download_all.py +15 -1
  119. scipy/datasets/_fetchers.py +7 -1
  120. scipy/datasets/_utils.py +1 -1
  121. scipy/differentiate/_differentiate.py +25 -25
  122. scipy/differentiate/tests/test_differentiate.py +24 -25
  123. scipy/fft/_basic.py +20 -0
  124. scipy/fft/_helper.py +3 -34
  125. scipy/fft/_pocketfft/helper.py +29 -1
  126. scipy/fft/_pocketfft/pypocketfft.cp312-win_amd64.dll.a +0 -0
  127. scipy/fft/_pocketfft/pypocketfft.cp312-win_amd64.pyd +0 -0
  128. scipy/fft/_pocketfft/tests/test_basic.py +2 -4
  129. scipy/fft/_pocketfft/tests/test_real_transforms.py +4 -4
  130. scipy/fft/_realtransforms.py +13 -0
  131. scipy/fft/tests/test_basic.py +27 -25
  132. scipy/fft/tests/test_fftlog.py +16 -7
  133. scipy/fft/tests/test_helper.py +18 -34
  134. scipy/fft/tests/test_real_transforms.py +8 -10
  135. scipy/fftpack/convolve.cp312-win_amd64.dll.a +0 -0
  136. scipy/fftpack/convolve.cp312-win_amd64.pyd +0 -0
  137. scipy/fftpack/tests/test_basic.py +2 -4
  138. scipy/fftpack/tests/test_real_transforms.py +8 -9
  139. scipy/integrate/_bvp.py +9 -3
  140. scipy/integrate/_cubature.py +3 -2
  141. scipy/integrate/_dop.cp312-win_amd64.dll.a +0 -0
  142. scipy/integrate/_dop.cp312-win_amd64.pyd +0 -0
  143. scipy/integrate/_lsoda.cp312-win_amd64.dll.a +0 -0
  144. scipy/integrate/_lsoda.cp312-win_amd64.pyd +0 -0
  145. scipy/integrate/_ode.py +9 -2
  146. scipy/integrate/_odepack.cp312-win_amd64.dll.a +0 -0
  147. scipy/integrate/_odepack.cp312-win_amd64.pyd +0 -0
  148. scipy/integrate/_quad_vec.py +21 -29
  149. scipy/integrate/_quadpack.cp312-win_amd64.dll.a +0 -0
  150. scipy/integrate/_quadpack.cp312-win_amd64.pyd +0 -0
  151. scipy/integrate/_quadpack_py.py +11 -7
  152. scipy/integrate/_quadrature.py +3 -3
  153. scipy/integrate/_rules/_base.py +2 -2
  154. scipy/integrate/_tanhsinh.py +48 -47
  155. scipy/integrate/_test_multivariate.cp312-win_amd64.dll.a +0 -0
  156. scipy/integrate/_test_multivariate.cp312-win_amd64.pyd +0 -0
  157. scipy/integrate/_test_odeint_banded.cp312-win_amd64.dll.a +0 -0
  158. scipy/integrate/_test_odeint_banded.cp312-win_amd64.pyd +0 -0
  159. scipy/integrate/_vode.cp312-win_amd64.dll.a +0 -0
  160. scipy/integrate/_vode.cp312-win_amd64.pyd +0 -0
  161. scipy/integrate/tests/test__quad_vec.py +0 -6
  162. scipy/integrate/tests/test_banded_ode_solvers.py +85 -0
  163. scipy/integrate/tests/test_cubature.py +21 -35
  164. scipy/integrate/tests/test_quadrature.py +6 -8
  165. scipy/integrate/tests/test_tanhsinh.py +56 -48
  166. scipy/interpolate/__init__.py +70 -58
  167. scipy/interpolate/_bary_rational.py +22 -22
  168. scipy/interpolate/_bsplines.py +119 -66
  169. scipy/interpolate/_cubic.py +65 -50
  170. scipy/interpolate/_dfitpack.cp312-win_amd64.dll.a +0 -0
  171. scipy/interpolate/_dfitpack.cp312-win_amd64.pyd +0 -0
  172. scipy/interpolate/_dierckx.cp312-win_amd64.dll.a +0 -0
  173. scipy/interpolate/_dierckx.cp312-win_amd64.pyd +0 -0
  174. scipy/interpolate/_fitpack.cp312-win_amd64.dll.a +0 -0
  175. scipy/interpolate/_fitpack.cp312-win_amd64.pyd +0 -0
  176. scipy/interpolate/_fitpack2.py +9 -6
  177. scipy/interpolate/_fitpack_impl.py +32 -26
  178. scipy/interpolate/_fitpack_repro.py +23 -19
  179. scipy/interpolate/_interpnd.cp312-win_amd64.dll.a +0 -0
  180. scipy/interpolate/_interpnd.cp312-win_amd64.pyd +0 -0
  181. scipy/interpolate/_interpolate.py +30 -12
  182. scipy/interpolate/_ndbspline.py +13 -18
  183. scipy/interpolate/_ndgriddata.py +5 -8
  184. scipy/interpolate/_polyint.py +95 -31
  185. scipy/interpolate/_ppoly.cp312-win_amd64.dll.a +0 -0
  186. scipy/interpolate/_ppoly.cp312-win_amd64.pyd +0 -0
  187. scipy/interpolate/_rbf.py +2 -2
  188. scipy/interpolate/_rbfinterp.py +1 -1
  189. scipy/interpolate/_rbfinterp_pythran.cp312-win_amd64.dll.a +0 -0
  190. scipy/interpolate/_rbfinterp_pythran.cp312-win_amd64.pyd +0 -0
  191. scipy/interpolate/_rgi.py +31 -26
  192. scipy/interpolate/_rgi_cython.cp312-win_amd64.dll.a +0 -0
  193. scipy/interpolate/_rgi_cython.cp312-win_amd64.pyd +0 -0
  194. scipy/interpolate/dfitpack.py +0 -20
  195. scipy/interpolate/interpnd.py +1 -2
  196. scipy/interpolate/tests/test_bary_rational.py +2 -2
  197. scipy/interpolate/tests/test_bsplines.py +97 -1
  198. scipy/interpolate/tests/test_fitpack2.py +39 -1
  199. scipy/interpolate/tests/test_interpnd.py +32 -20
  200. scipy/interpolate/tests/test_interpolate.py +48 -4
  201. scipy/interpolate/tests/test_rgi.py +2 -1
  202. scipy/io/_fast_matrix_market/__init__.py +2 -0
  203. scipy/io/_fast_matrix_market/_fmm_core.cp312-win_amd64.dll.a +0 -0
  204. scipy/io/_fast_matrix_market/_fmm_core.cp312-win_amd64.pyd +0 -0
  205. scipy/io/_harwell_boeing/_fortran_format_parser.py +19 -16
  206. scipy/io/_harwell_boeing/hb.py +7 -11
  207. scipy/io/_idl.py +5 -7
  208. scipy/io/_netcdf.py +15 -5
  209. scipy/io/_test_fortran.cp312-win_amd64.dll.a +0 -0
  210. scipy/io/_test_fortran.cp312-win_amd64.pyd +0 -0
  211. scipy/io/arff/tests/test_arffread.py +3 -3
  212. scipy/io/matlab/__init__.py +5 -3
  213. scipy/io/matlab/_mio.py +4 -1
  214. scipy/io/matlab/_mio5.py +19 -13
  215. scipy/io/matlab/_mio5_utils.cp312-win_amd64.dll.a +0 -0
  216. scipy/io/matlab/_mio5_utils.cp312-win_amd64.pyd +0 -0
  217. scipy/io/matlab/_mio_utils.cp312-win_amd64.dll.a +0 -0
  218. scipy/io/matlab/_mio_utils.cp312-win_amd64.pyd +0 -0
  219. scipy/io/matlab/_miobase.py +4 -1
  220. scipy/io/matlab/_streams.cp312-win_amd64.dll.a +0 -0
  221. scipy/io/matlab/_streams.cp312-win_amd64.pyd +0 -0
  222. scipy/io/matlab/tests/test_mio.py +46 -18
  223. scipy/io/matlab/tests/test_mio_funcs.py +1 -1
  224. scipy/io/tests/test_mmio.py +7 -1
  225. scipy/io/tests/test_wavfile.py +41 -0
  226. scipy/io/wavfile.py +57 -10
  227. scipy/linalg/_basic.py +113 -86
  228. scipy/linalg/_cythonized_array_utils.cp312-win_amd64.dll.a +0 -0
  229. scipy/linalg/_cythonized_array_utils.cp312-win_amd64.pyd +0 -0
  230. scipy/linalg/_decomp.py +22 -9
  231. scipy/linalg/_decomp_cholesky.py +28 -13
  232. scipy/linalg/_decomp_cossin.py +45 -30
  233. scipy/linalg/_decomp_interpolative.cp312-win_amd64.dll.a +0 -0
  234. scipy/linalg/_decomp_interpolative.cp312-win_amd64.pyd +0 -0
  235. scipy/linalg/_decomp_ldl.py +4 -1
  236. scipy/linalg/_decomp_lu.py +18 -6
  237. scipy/linalg/_decomp_lu_cython.cp312-win_amd64.dll.a +0 -0
  238. scipy/linalg/_decomp_lu_cython.cp312-win_amd64.pyd +0 -0
  239. scipy/linalg/_decomp_polar.py +2 -0
  240. scipy/linalg/_decomp_qr.py +6 -2
  241. scipy/linalg/_decomp_qz.py +3 -0
  242. scipy/linalg/_decomp_schur.py +3 -1
  243. scipy/linalg/_decomp_svd.py +13 -2
  244. scipy/linalg/_decomp_update.cp312-win_amd64.dll.a +0 -0
  245. scipy/linalg/_decomp_update.cp312-win_amd64.pyd +0 -0
  246. scipy/linalg/_expm_frechet.py +4 -0
  247. scipy/linalg/_fblas.cp312-win_amd64.dll.a +0 -0
  248. scipy/linalg/_fblas.cp312-win_amd64.pyd +0 -0
  249. scipy/linalg/_flapack.cp312-win_amd64.dll.a +0 -0
  250. scipy/linalg/_flapack.cp312-win_amd64.pyd +0 -0
  251. scipy/linalg/_linalg_pythran.cp312-win_amd64.dll.a +0 -0
  252. scipy/linalg/_linalg_pythran.cp312-win_amd64.pyd +0 -0
  253. scipy/linalg/_matfuncs.py +187 -4
  254. scipy/linalg/_matfuncs_expm.cp312-win_amd64.dll.a +0 -0
  255. scipy/linalg/_matfuncs_expm.cp312-win_amd64.pyd +0 -0
  256. scipy/linalg/_matfuncs_schur_sqrtm.cp312-win_amd64.dll.a +0 -0
  257. scipy/linalg/_matfuncs_schur_sqrtm.cp312-win_amd64.pyd +0 -0
  258. scipy/linalg/_matfuncs_sqrtm.py +1 -99
  259. scipy/linalg/_matfuncs_sqrtm_triu.cp312-win_amd64.dll.a +0 -0
  260. scipy/linalg/_matfuncs_sqrtm_triu.cp312-win_amd64.pyd +0 -0
  261. scipy/linalg/_procrustes.py +2 -0
  262. scipy/linalg/_sketches.py +17 -6
  263. scipy/linalg/_solve_toeplitz.cp312-win_amd64.dll.a +0 -0
  264. scipy/linalg/_solve_toeplitz.cp312-win_amd64.pyd +0 -0
  265. scipy/linalg/_solvers.py +7 -2
  266. scipy/linalg/_special_matrices.py +26 -36
  267. scipy/linalg/cython_blas.cp312-win_amd64.dll.a +0 -0
  268. scipy/linalg/cython_blas.cp312-win_amd64.pyd +0 -0
  269. scipy/linalg/cython_lapack.cp312-win_amd64.dll.a +0 -0
  270. scipy/linalg/cython_lapack.cp312-win_amd64.pyd +0 -0
  271. scipy/linalg/lapack.py +22 -2
  272. scipy/linalg/tests/_cython_examples/meson.build +7 -0
  273. scipy/linalg/tests/test_basic.py +31 -16
  274. scipy/linalg/tests/test_batch.py +588 -0
  275. scipy/linalg/tests/test_cythonized_array_utils.py +0 -2
  276. scipy/linalg/tests/test_decomp.py +40 -3
  277. scipy/linalg/tests/test_decomp_cossin.py +14 -0
  278. scipy/linalg/tests/test_decomp_ldl.py +1 -1
  279. scipy/linalg/tests/test_lapack.py +115 -7
  280. scipy/linalg/tests/test_matfuncs.py +157 -102
  281. scipy/linalg/tests/test_procrustes.py +0 -7
  282. scipy/linalg/tests/test_solve_toeplitz.py +1 -1
  283. scipy/linalg/tests/test_special_matrices.py +1 -5
  284. scipy/ndimage/__init__.py +1 -0
  285. scipy/ndimage/_ctest.cp312-win_amd64.dll.a +0 -0
  286. scipy/ndimage/_ctest.cp312-win_amd64.pyd +0 -0
  287. scipy/ndimage/_cytest.cp312-win_amd64.dll.a +0 -0
  288. scipy/ndimage/_cytest.cp312-win_amd64.pyd +0 -0
  289. scipy/ndimage/_delegators.py +8 -2
  290. scipy/ndimage/_filters.py +453 -5
  291. scipy/ndimage/_interpolation.py +36 -6
  292. scipy/ndimage/_measurements.py +4 -2
  293. scipy/ndimage/_morphology.py +5 -0
  294. scipy/ndimage/_nd_image.cp312-win_amd64.dll.a +0 -0
  295. scipy/ndimage/_nd_image.cp312-win_amd64.pyd +0 -0
  296. scipy/ndimage/_ni_docstrings.py +5 -1
  297. scipy/ndimage/_ni_label.cp312-win_amd64.dll.a +0 -0
  298. scipy/ndimage/_ni_label.cp312-win_amd64.pyd +0 -0
  299. scipy/ndimage/_ni_support.py +1 -5
  300. scipy/ndimage/_rank_filter_1d.cp312-win_amd64.dll.a +0 -0
  301. scipy/ndimage/_rank_filter_1d.cp312-win_amd64.pyd +0 -0
  302. scipy/ndimage/_support_alternative_backends.py +18 -6
  303. scipy/ndimage/tests/test_filters.py +370 -259
  304. scipy/ndimage/tests/test_fourier.py +7 -9
  305. scipy/ndimage/tests/test_interpolation.py +68 -61
  306. scipy/ndimage/tests/test_measurements.py +18 -35
  307. scipy/ndimage/tests/test_morphology.py +143 -131
  308. scipy/ndimage/tests/test_splines.py +1 -3
  309. scipy/odr/__odrpack.cp312-win_amd64.dll.a +0 -0
  310. scipy/odr/__odrpack.cp312-win_amd64.pyd +0 -0
  311. scipy/optimize/_basinhopping.py +13 -7
  312. scipy/optimize/_bglu_dense.cp312-win_amd64.dll.a +0 -0
  313. scipy/optimize/_bglu_dense.cp312-win_amd64.pyd +0 -0
  314. scipy/optimize/_bracket.py +17 -24
  315. scipy/optimize/_chandrupatla.py +9 -10
  316. scipy/optimize/_cobyla_py.py +104 -123
  317. scipy/optimize/_constraints.py +14 -10
  318. scipy/optimize/_differentiable_functions.py +371 -230
  319. scipy/optimize/_differentialevolution.py +4 -3
  320. scipy/optimize/_direct.cp312-win_amd64.dll.a +0 -0
  321. scipy/optimize/_direct.cp312-win_amd64.pyd +0 -0
  322. scipy/optimize/_dual_annealing.py +1 -1
  323. scipy/optimize/_elementwise.py +1 -4
  324. scipy/optimize/_group_columns.cp312-win_amd64.dll.a +0 -0
  325. scipy/optimize/_group_columns.cp312-win_amd64.pyd +0 -0
  326. scipy/optimize/_highspy/_core.cp312-win_amd64.dll.a +0 -0
  327. scipy/optimize/_highspy/_core.cp312-win_amd64.pyd +0 -0
  328. scipy/optimize/_highspy/_highs_options.cp312-win_amd64.dll.a +0 -0
  329. scipy/optimize/_highspy/_highs_options.cp312-win_amd64.pyd +0 -0
  330. scipy/optimize/_lbfgsb.cp312-win_amd64.dll.a +0 -0
  331. scipy/optimize/_lbfgsb.cp312-win_amd64.pyd +0 -0
  332. scipy/optimize/_lbfgsb_py.py +57 -16
  333. scipy/optimize/_linprog_doc.py +2 -2
  334. scipy/optimize/_linprog_highs.py +2 -2
  335. scipy/optimize/_linprog_ip.py +25 -10
  336. scipy/optimize/_linprog_util.py +14 -16
  337. scipy/optimize/_lsap.cp312-win_amd64.dll.a +0 -0
  338. scipy/optimize/_lsap.cp312-win_amd64.pyd +0 -0
  339. scipy/optimize/_lsq/common.py +3 -3
  340. scipy/optimize/_lsq/dogbox.py +16 -2
  341. scipy/optimize/_lsq/givens_elimination.cp312-win_amd64.dll.a +0 -0
  342. scipy/optimize/_lsq/givens_elimination.cp312-win_amd64.pyd +0 -0
  343. scipy/optimize/_lsq/least_squares.py +198 -126
  344. scipy/optimize/_lsq/lsq_linear.py +6 -6
  345. scipy/optimize/_lsq/trf.py +35 -8
  346. scipy/optimize/_milp.py +3 -1
  347. scipy/optimize/_minimize.py +105 -36
  348. scipy/optimize/_minpack.cp312-win_amd64.dll.a +0 -0
  349. scipy/optimize/_minpack.cp312-win_amd64.pyd +0 -0
  350. scipy/optimize/_minpack_py.py +21 -14
  351. scipy/optimize/_moduleTNC.cp312-win_amd64.dll.a +0 -0
  352. scipy/optimize/_moduleTNC.cp312-win_amd64.pyd +0 -0
  353. scipy/optimize/_nnls.py +20 -21
  354. scipy/optimize/_nonlin.py +34 -3
  355. scipy/optimize/_numdiff.py +288 -110
  356. scipy/optimize/_optimize.py +86 -48
  357. scipy/optimize/_pava_pybind.cp312-win_amd64.dll.a +0 -0
  358. scipy/optimize/_pava_pybind.cp312-win_amd64.pyd +0 -0
  359. scipy/optimize/_remove_redundancy.py +5 -5
  360. scipy/optimize/_root_scalar.py +1 -1
  361. scipy/optimize/_shgo.py +6 -0
  362. scipy/optimize/_shgo_lib/_complex.py +1 -1
  363. scipy/optimize/_slsqp_py.py +216 -124
  364. scipy/optimize/_slsqplib.cp312-win_amd64.dll.a +0 -0
  365. scipy/optimize/_slsqplib.cp312-win_amd64.pyd +0 -0
  366. scipy/optimize/_spectral.py +1 -1
  367. scipy/optimize/_tnc.py +8 -1
  368. scipy/optimize/_trlib/_trlib.cp312-win_amd64.dll.a +0 -0
  369. scipy/optimize/_trlib/_trlib.cp312-win_amd64.pyd +0 -0
  370. scipy/optimize/_trustregion.py +20 -6
  371. scipy/optimize/_trustregion_constr/canonical_constraint.py +7 -7
  372. scipy/optimize/_trustregion_constr/equality_constrained_sqp.py +1 -1
  373. scipy/optimize/_trustregion_constr/minimize_trustregion_constr.py +11 -3
  374. scipy/optimize/_trustregion_constr/projections.py +12 -8
  375. scipy/optimize/_trustregion_constr/qp_subproblem.py +9 -9
  376. scipy/optimize/_trustregion_constr/tests/test_projections.py +7 -7
  377. scipy/optimize/_trustregion_constr/tests/test_qp_subproblem.py +77 -77
  378. scipy/optimize/_trustregion_constr/tr_interior_point.py +5 -5
  379. scipy/optimize/_trustregion_exact.py +0 -1
  380. scipy/optimize/_zeros.cp312-win_amd64.dll.a +0 -0
  381. scipy/optimize/_zeros.cp312-win_amd64.pyd +0 -0
  382. scipy/optimize/_zeros_py.py +97 -17
  383. scipy/optimize/cython_optimize/_zeros.cp312-win_amd64.dll.a +0 -0
  384. scipy/optimize/cython_optimize/_zeros.cp312-win_amd64.pyd +0 -0
  385. scipy/optimize/slsqp.py +0 -1
  386. scipy/optimize/tests/test__basinhopping.py +1 -1
  387. scipy/optimize/tests/test__differential_evolution.py +4 -4
  388. scipy/optimize/tests/test__linprog_clean_inputs.py +5 -3
  389. scipy/optimize/tests/test__numdiff.py +66 -22
  390. scipy/optimize/tests/test__remove_redundancy.py +2 -2
  391. scipy/optimize/tests/test__shgo.py +9 -1
  392. scipy/optimize/tests/test_bracket.py +36 -46
  393. scipy/optimize/tests/test_chandrupatla.py +133 -135
  394. scipy/optimize/tests/test_cobyla.py +74 -45
  395. scipy/optimize/tests/test_constraints.py +1 -1
  396. scipy/optimize/tests/test_differentiable_functions.py +226 -6
  397. scipy/optimize/tests/test_lbfgsb_hessinv.py +22 -0
  398. scipy/optimize/tests/test_least_squares.py +125 -13
  399. scipy/optimize/tests/test_linear_assignment.py +3 -3
  400. scipy/optimize/tests/test_linprog.py +3 -3
  401. scipy/optimize/tests/test_lsq_linear.py +6 -6
  402. scipy/optimize/tests/test_minimize_constrained.py +2 -2
  403. scipy/optimize/tests/test_minpack.py +4 -4
  404. scipy/optimize/tests/test_nnls.py +43 -3
  405. scipy/optimize/tests/test_nonlin.py +36 -0
  406. scipy/optimize/tests/test_optimize.py +95 -17
  407. scipy/optimize/tests/test_slsqp.py +36 -4
  408. scipy/optimize/tests/test_zeros.py +34 -1
  409. scipy/signal/__init__.py +12 -23
  410. scipy/signal/_delegators.py +568 -0
  411. scipy/signal/_filter_design.py +459 -241
  412. scipy/signal/_fir_filter_design.py +262 -90
  413. scipy/signal/_lti_conversion.py +3 -2
  414. scipy/signal/_ltisys.py +118 -91
  415. scipy/signal/_max_len_seq_inner.cp312-win_amd64.dll.a +0 -0
  416. scipy/signal/_max_len_seq_inner.cp312-win_amd64.pyd +0 -0
  417. scipy/signal/_peak_finding_utils.cp312-win_amd64.dll.a +0 -0
  418. scipy/signal/_peak_finding_utils.cp312-win_amd64.pyd +0 -0
  419. scipy/signal/_polyutils.py +172 -0
  420. scipy/signal/_short_time_fft.py +519 -70
  421. scipy/signal/_signal_api.py +30 -0
  422. scipy/signal/_signaltools.py +719 -399
  423. scipy/signal/_sigtools.cp312-win_amd64.dll.a +0 -0
  424. scipy/signal/_sigtools.cp312-win_amd64.pyd +0 -0
  425. scipy/signal/_sosfilt.cp312-win_amd64.dll.a +0 -0
  426. scipy/signal/_sosfilt.cp312-win_amd64.pyd +0 -0
  427. scipy/signal/_spectral_py.py +230 -50
  428. scipy/signal/_spline.cp312-win_amd64.dll.a +0 -0
  429. scipy/signal/_spline.cp312-win_amd64.pyd +0 -0
  430. scipy/signal/_spline_filters.py +108 -68
  431. scipy/signal/_support_alternative_backends.py +73 -0
  432. scipy/signal/_upfirdn.py +4 -1
  433. scipy/signal/_upfirdn_apply.cp312-win_amd64.dll.a +0 -0
  434. scipy/signal/_upfirdn_apply.cp312-win_amd64.pyd +0 -0
  435. scipy/signal/_waveforms.py +2 -11
  436. scipy/signal/_wavelets.py +1 -1
  437. scipy/signal/fir_filter_design.py +1 -0
  438. scipy/signal/spline.py +4 -11
  439. scipy/signal/tests/_scipy_spectral_test_shim.py +2 -171
  440. scipy/signal/tests/test_bsplines.py +114 -79
  441. scipy/signal/tests/test_cont2discrete.py +9 -2
  442. scipy/signal/tests/test_filter_design.py +721 -481
  443. scipy/signal/tests/test_fir_filter_design.py +332 -140
  444. scipy/signal/tests/test_savitzky_golay.py +4 -3
  445. scipy/signal/tests/test_short_time_fft.py +221 -3
  446. scipy/signal/tests/test_signaltools.py +2144 -1348
  447. scipy/signal/tests/test_spectral.py +50 -6
  448. scipy/signal/tests/test_splines.py +161 -96
  449. scipy/signal/tests/test_upfirdn.py +84 -50
  450. scipy/signal/tests/test_waveforms.py +20 -0
  451. scipy/signal/tests/test_windows.py +607 -466
  452. scipy/signal/windows/_windows.py +287 -148
  453. scipy/sparse/__init__.py +23 -4
  454. scipy/sparse/_base.py +270 -108
  455. scipy/sparse/_bsr.py +7 -4
  456. scipy/sparse/_compressed.py +59 -231
  457. scipy/sparse/_construct.py +90 -38
  458. scipy/sparse/_coo.py +115 -181
  459. scipy/sparse/_csc.py +4 -4
  460. scipy/sparse/_csparsetools.cp312-win_amd64.dll.a +0 -0
  461. scipy/sparse/_csparsetools.cp312-win_amd64.pyd +0 -0
  462. scipy/sparse/_csr.py +2 -2
  463. scipy/sparse/_data.py +48 -48
  464. scipy/sparse/_dia.py +105 -18
  465. scipy/sparse/_dok.py +0 -23
  466. scipy/sparse/_index.py +4 -4
  467. scipy/sparse/_matrix.py +23 -0
  468. scipy/sparse/_sparsetools.cp312-win_amd64.dll.a +0 -0
  469. scipy/sparse/_sparsetools.cp312-win_amd64.pyd +0 -0
  470. scipy/sparse/_sputils.py +37 -22
  471. scipy/sparse/base.py +0 -9
  472. scipy/sparse/bsr.py +0 -14
  473. scipy/sparse/compressed.py +0 -23
  474. scipy/sparse/construct.py +0 -6
  475. scipy/sparse/coo.py +0 -14
  476. scipy/sparse/csc.py +0 -3
  477. scipy/sparse/csgraph/_flow.cp312-win_amd64.dll.a +0 -0
  478. scipy/sparse/csgraph/_flow.cp312-win_amd64.pyd +0 -0
  479. scipy/sparse/csgraph/_matching.cp312-win_amd64.dll.a +0 -0
  480. scipy/sparse/csgraph/_matching.cp312-win_amd64.pyd +0 -0
  481. scipy/sparse/csgraph/_min_spanning_tree.cp312-win_amd64.dll.a +0 -0
  482. scipy/sparse/csgraph/_min_spanning_tree.cp312-win_amd64.pyd +0 -0
  483. scipy/sparse/csgraph/_reordering.cp312-win_amd64.dll.a +0 -0
  484. scipy/sparse/csgraph/_reordering.cp312-win_amd64.pyd +0 -0
  485. scipy/sparse/csgraph/_shortest_path.cp312-win_amd64.dll.a +0 -0
  486. scipy/sparse/csgraph/_shortest_path.cp312-win_amd64.pyd +0 -0
  487. scipy/sparse/csgraph/_tools.cp312-win_amd64.dll.a +0 -0
  488. scipy/sparse/csgraph/_tools.cp312-win_amd64.pyd +0 -0
  489. scipy/sparse/csgraph/_traversal.cp312-win_amd64.dll.a +0 -0
  490. scipy/sparse/csgraph/_traversal.cp312-win_amd64.pyd +0 -0
  491. scipy/sparse/csgraph/tests/test_matching.py +14 -2
  492. scipy/sparse/csgraph/tests/test_pydata_sparse.py +4 -1
  493. scipy/sparse/csgraph/tests/test_shortest_path.py +83 -27
  494. scipy/sparse/csr.py +0 -5
  495. scipy/sparse/data.py +1 -6
  496. scipy/sparse/dia.py +0 -7
  497. scipy/sparse/dok.py +0 -10
  498. scipy/sparse/linalg/_dsolve/_superlu.cp312-win_amd64.dll.a +0 -0
  499. scipy/sparse/linalg/_dsolve/_superlu.cp312-win_amd64.pyd +0 -0
  500. scipy/sparse/linalg/_dsolve/linsolve.py +9 -0
  501. scipy/sparse/linalg/_dsolve/tests/test_linsolve.py +35 -28
  502. scipy/sparse/linalg/_eigen/arpack/_arpack.cp312-win_amd64.dll.a +0 -0
  503. scipy/sparse/linalg/_eigen/arpack/_arpack.cp312-win_amd64.pyd +0 -0
  504. scipy/sparse/linalg/_eigen/arpack/arpack.py +23 -17
  505. scipy/sparse/linalg/_eigen/lobpcg/lobpcg.py +6 -6
  506. scipy/sparse/linalg/_interface.py +17 -18
  507. scipy/sparse/linalg/_isolve/_gcrotmk.py +4 -4
  508. scipy/sparse/linalg/_isolve/iterative.py +51 -45
  509. scipy/sparse/linalg/_isolve/lgmres.py +6 -6
  510. scipy/sparse/linalg/_isolve/minres.py +5 -5
  511. scipy/sparse/linalg/_isolve/tfqmr.py +7 -7
  512. scipy/sparse/linalg/_isolve/utils.py +2 -8
  513. scipy/sparse/linalg/_matfuncs.py +1 -1
  514. scipy/sparse/linalg/_norm.py +1 -1
  515. scipy/sparse/linalg/_propack/_cpropack.cp312-win_amd64.dll.a +0 -0
  516. scipy/sparse/linalg/_propack/_cpropack.cp312-win_amd64.pyd +0 -0
  517. scipy/sparse/linalg/_propack/_dpropack.cp312-win_amd64.dll.a +0 -0
  518. scipy/sparse/linalg/_propack/_dpropack.cp312-win_amd64.pyd +0 -0
  519. scipy/sparse/linalg/_propack/_spropack.cp312-win_amd64.dll.a +0 -0
  520. scipy/sparse/linalg/_propack/_spropack.cp312-win_amd64.pyd +0 -0
  521. scipy/sparse/linalg/_propack/_zpropack.cp312-win_amd64.dll.a +0 -0
  522. scipy/sparse/linalg/_propack/_zpropack.cp312-win_amd64.pyd +0 -0
  523. scipy/sparse/linalg/_special_sparse_arrays.py +39 -38
  524. scipy/sparse/linalg/tests/test_pydata_sparse.py +14 -0
  525. scipy/sparse/tests/test_arithmetic1d.py +5 -2
  526. scipy/sparse/tests/test_base.py +214 -42
  527. scipy/sparse/tests/test_common1d.py +7 -7
  528. scipy/sparse/tests/test_construct.py +1 -1
  529. scipy/sparse/tests/test_coo.py +272 -4
  530. scipy/sparse/tests/test_sparsetools.py +5 -0
  531. scipy/sparse/tests/test_sputils.py +36 -7
  532. scipy/spatial/_ckdtree.cp312-win_amd64.dll.a +0 -0
  533. scipy/spatial/_ckdtree.cp312-win_amd64.pyd +0 -0
  534. scipy/spatial/_distance_pybind.cp312-win_amd64.dll.a +0 -0
  535. scipy/spatial/_distance_pybind.cp312-win_amd64.pyd +0 -0
  536. scipy/spatial/_distance_wrap.cp312-win_amd64.dll.a +0 -0
  537. scipy/spatial/_distance_wrap.cp312-win_amd64.pyd +0 -0
  538. scipy/spatial/_hausdorff.cp312-win_amd64.dll.a +0 -0
  539. scipy/spatial/_hausdorff.cp312-win_amd64.pyd +0 -0
  540. scipy/spatial/_qhull.cp312-win_amd64.dll.a +0 -0
  541. scipy/spatial/_qhull.cp312-win_amd64.pyd +0 -0
  542. scipy/spatial/_voronoi.cp312-win_amd64.dll.a +0 -0
  543. scipy/spatial/_voronoi.cp312-win_amd64.pyd +0 -0
  544. scipy/spatial/distance.py +49 -42
  545. scipy/spatial/tests/test_distance.py +15 -1
  546. scipy/spatial/tests/test_kdtree.py +1 -0
  547. scipy/spatial/tests/test_qhull.py +7 -2
  548. scipy/spatial/transform/__init__.py +5 -3
  549. scipy/spatial/transform/_rigid_transform.cp312-win_amd64.dll.a +0 -0
  550. scipy/spatial/transform/_rigid_transform.cp312-win_amd64.pyd +0 -0
  551. scipy/spatial/transform/_rotation.cp312-win_amd64.dll.a +0 -0
  552. scipy/spatial/transform/_rotation.cp312-win_amd64.pyd +0 -0
  553. scipy/spatial/transform/tests/test_rigid_transform.py +1221 -0
  554. scipy/spatial/transform/tests/test_rotation.py +1213 -832
  555. scipy/spatial/transform/tests/test_rotation_groups.py +3 -3
  556. scipy/spatial/transform/tests/test_rotation_spline.py +29 -8
  557. scipy/special/__init__.py +1 -47
  558. scipy/special/_add_newdocs.py +34 -772
  559. scipy/special/_basic.py +22 -25
  560. scipy/special/_comb.cp312-win_amd64.dll.a +0 -0
  561. scipy/special/_comb.cp312-win_amd64.pyd +0 -0
  562. scipy/special/_ellip_harm_2.cp312-win_amd64.dll.a +0 -0
  563. scipy/special/_ellip_harm_2.cp312-win_amd64.pyd +0 -0
  564. scipy/special/_gufuncs.cp312-win_amd64.dll.a +0 -0
  565. scipy/special/_gufuncs.cp312-win_amd64.pyd +0 -0
  566. scipy/special/_logsumexp.py +67 -58
  567. scipy/special/_orthogonal.pyi +1 -1
  568. scipy/special/_specfun.cp312-win_amd64.dll.a +0 -0
  569. scipy/special/_specfun.cp312-win_amd64.pyd +0 -0
  570. scipy/special/_special_ufuncs.cp312-win_amd64.dll.a +0 -0
  571. scipy/special/_special_ufuncs.cp312-win_amd64.pyd +0 -0
  572. scipy/special/_spherical_bessel.py +4 -4
  573. scipy/special/_support_alternative_backends.py +212 -119
  574. scipy/special/_test_internal.cp312-win_amd64.dll.a +0 -0
  575. scipy/special/_test_internal.cp312-win_amd64.pyd +0 -0
  576. scipy/special/_testutils.py +4 -4
  577. scipy/special/_ufuncs.cp312-win_amd64.dll.a +0 -0
  578. scipy/special/_ufuncs.cp312-win_amd64.pyd +0 -0
  579. scipy/special/_ufuncs.pyi +1 -0
  580. scipy/special/_ufuncs.pyx +215 -1400
  581. scipy/special/_ufuncs_cxx.cp312-win_amd64.dll.a +0 -0
  582. scipy/special/_ufuncs_cxx.cp312-win_amd64.pyd +0 -0
  583. scipy/special/_ufuncs_cxx.pxd +2 -15
  584. scipy/special/_ufuncs_cxx.pyx +5 -44
  585. scipy/special/_ufuncs_cxx_defs.h +2 -16
  586. scipy/special/_ufuncs_defs.h +0 -8
  587. scipy/special/cython_special.cp312-win_amd64.dll.a +0 -0
  588. scipy/special/cython_special.cp312-win_amd64.pyd +0 -0
  589. scipy/special/cython_special.pxd +1 -1
  590. scipy/special/tests/_cython_examples/meson.build +10 -1
  591. scipy/special/tests/test_basic.py +153 -20
  592. scipy/special/tests/test_boost_ufuncs.py +3 -0
  593. scipy/special/tests/test_cdflib.py +35 -11
  594. scipy/special/tests/test_gammainc.py +16 -0
  595. scipy/special/tests/test_hyp2f1.py +2 -2
  596. scipy/special/tests/test_log1mexp.py +85 -0
  597. scipy/special/tests/test_logsumexp.py +206 -64
  598. scipy/special/tests/test_mpmath.py +1 -0
  599. scipy/special/tests/test_nan_inputs.py +1 -1
  600. scipy/special/tests/test_orthogonal.py +17 -18
  601. scipy/special/tests/test_sf_error.py +3 -2
  602. scipy/special/tests/test_sph_harm.py +6 -7
  603. scipy/special/tests/test_support_alternative_backends.py +211 -76
  604. scipy/stats/__init__.py +4 -1
  605. scipy/stats/_ansari_swilk_statistics.cp312-win_amd64.dll.a +0 -0
  606. scipy/stats/_ansari_swilk_statistics.cp312-win_amd64.pyd +0 -0
  607. scipy/stats/_axis_nan_policy.py +5 -12
  608. scipy/stats/_biasedurn.cp312-win_amd64.dll.a +0 -0
  609. scipy/stats/_biasedurn.cp312-win_amd64.pyd +0 -0
  610. scipy/stats/_continued_fraction.py +387 -0
  611. scipy/stats/_continuous_distns.py +277 -310
  612. scipy/stats/_correlation.py +1 -1
  613. scipy/stats/_covariance.py +6 -3
  614. scipy/stats/_discrete_distns.py +39 -32
  615. scipy/stats/_distn_infrastructure.py +39 -12
  616. scipy/stats/_distribution_infrastructure.py +900 -238
  617. scipy/stats/_entropy.py +9 -10
  618. scipy/{_lib → stats}/_finite_differences.py +1 -1
  619. scipy/stats/_hypotests.py +83 -50
  620. scipy/stats/_kde.py +53 -49
  621. scipy/stats/_ksstats.py +1 -1
  622. scipy/stats/_levy_stable/__init__.py +7 -15
  623. scipy/stats/_levy_stable/levyst.cp312-win_amd64.dll.a +0 -0
  624. scipy/stats/_levy_stable/levyst.cp312-win_amd64.pyd +0 -0
  625. scipy/stats/_morestats.py +118 -73
  626. scipy/stats/_mstats_basic.py +13 -17
  627. scipy/stats/_mstats_extras.py +8 -8
  628. scipy/stats/_multivariate.py +89 -113
  629. scipy/stats/_new_distributions.py +97 -20
  630. scipy/stats/_page_trend_test.py +12 -5
  631. scipy/stats/_probability_distribution.py +265 -43
  632. scipy/stats/_qmc.py +14 -9
  633. scipy/stats/_qmc_cy.cp312-win_amd64.dll.a +0 -0
  634. scipy/stats/_qmc_cy.cp312-win_amd64.pyd +0 -0
  635. scipy/stats/_qmvnt.py +16 -95
  636. scipy/stats/_qmvnt_cy.cp312-win_amd64.dll.a +0 -0
  637. scipy/stats/_qmvnt_cy.cp312-win_amd64.pyd +0 -0
  638. scipy/stats/_quantile.py +335 -0
  639. scipy/stats/_rcont/rcont.cp312-win_amd64.dll.a +0 -0
  640. scipy/stats/_rcont/rcont.cp312-win_amd64.pyd +0 -0
  641. scipy/stats/_resampling.py +4 -29
  642. scipy/stats/_sampling.py +1 -1
  643. scipy/stats/_sobol.cp312-win_amd64.dll.a +0 -0
  644. scipy/stats/_sobol.cp312-win_amd64.pyd +0 -0
  645. scipy/stats/_stats.cp312-win_amd64.dll.a +0 -0
  646. scipy/stats/_stats.cp312-win_amd64.pyd +0 -0
  647. scipy/stats/_stats_mstats_common.py +21 -2
  648. scipy/stats/_stats_py.py +550 -476
  649. scipy/stats/_stats_pythran.cp312-win_amd64.dll.a +0 -0
  650. scipy/stats/_stats_pythran.cp312-win_amd64.pyd +0 -0
  651. scipy/stats/_unuran/unuran_wrapper.cp312-win_amd64.dll.a +0 -0
  652. scipy/stats/_unuran/unuran_wrapper.cp312-win_amd64.pyd +0 -0
  653. scipy/stats/_unuran/unuran_wrapper.pyi +2 -1
  654. scipy/stats/_variation.py +6 -8
  655. scipy/stats/_wilcoxon.py +13 -7
  656. scipy/stats/tests/common_tests.py +6 -4
  657. scipy/stats/tests/test_axis_nan_policy.py +62 -24
  658. scipy/stats/tests/test_continued_fraction.py +173 -0
  659. scipy/stats/tests/test_continuous.py +379 -60
  660. scipy/stats/tests/test_continuous_basic.py +18 -12
  661. scipy/stats/tests/test_discrete_basic.py +14 -8
  662. scipy/stats/tests/test_discrete_distns.py +16 -16
  663. scipy/stats/tests/test_distributions.py +95 -75
  664. scipy/stats/tests/test_entropy.py +40 -48
  665. scipy/stats/tests/test_fit.py +4 -3
  666. scipy/stats/tests/test_hypotests.py +153 -24
  667. scipy/stats/tests/test_kdeoth.py +109 -41
  668. scipy/stats/tests/test_marray.py +289 -0
  669. scipy/stats/tests/test_morestats.py +79 -47
  670. scipy/stats/tests/test_mstats_basic.py +3 -3
  671. scipy/stats/tests/test_multivariate.py +434 -83
  672. scipy/stats/tests/test_qmc.py +13 -10
  673. scipy/stats/tests/test_quantile.py +199 -0
  674. scipy/stats/tests/test_rank.py +119 -112
  675. scipy/stats/tests/test_resampling.py +47 -56
  676. scipy/stats/tests/test_sampling.py +9 -4
  677. scipy/stats/tests/test_stats.py +799 -939
  678. scipy/stats/tests/test_variation.py +8 -6
  679. scipy/version.py +2 -2
  680. scipy-1.16.0rc2.dist-info/DELVEWHEEL +2 -0
  681. {scipy-1.15.3.dist-info → scipy-1.16.0rc2.dist-info}/LICENSE.txt +4 -4
  682. {scipy-1.15.3.dist-info → scipy-1.16.0rc2.dist-info}/METADATA +11 -11
  683. {scipy-1.15.3.dist-info → scipy-1.16.0rc2.dist-info}/RECORD +685 -693
  684. scipy/_lib/array_api_extra/_funcs.py +0 -484
  685. scipy/_lib/array_api_extra/_typing.py +0 -8
  686. scipy/interpolate/_bspl.cp312-win_amd64.dll.a +0 -0
  687. scipy/interpolate/_bspl.cp312-win_amd64.pyd +0 -0
  688. scipy/optimize/_cobyla.cp312-win_amd64.dll.a +0 -0
  689. scipy/optimize/_cobyla.cp312-win_amd64.pyd +0 -0
  690. scipy/optimize/_cython_nnls.cp312-win_amd64.dll.a +0 -0
  691. scipy/optimize/_cython_nnls.cp312-win_amd64.pyd +0 -0
  692. scipy/optimize/_slsqp.cp312-win_amd64.dll.a +0 -0
  693. scipy/optimize/_slsqp.cp312-win_amd64.pyd +0 -0
  694. scipy/spatial/qhull_src/COPYING.txt +0 -38
  695. scipy/special/libsf_error_state.dll +0 -0
  696. scipy/special/libsf_error_state.dll.a +0 -0
  697. scipy/special/tests/test_log_softmax.py +0 -109
  698. scipy/special/tests/test_xsf_cuda.py +0 -114
  699. scipy/special/xsf/binom.h +0 -89
  700. scipy/special/xsf/cdflib.h +0 -100
  701. scipy/special/xsf/cephes/airy.h +0 -307
  702. scipy/special/xsf/cephes/besselpoly.h +0 -51
  703. scipy/special/xsf/cephes/beta.h +0 -257
  704. scipy/special/xsf/cephes/cbrt.h +0 -131
  705. scipy/special/xsf/cephes/chbevl.h +0 -85
  706. scipy/special/xsf/cephes/chdtr.h +0 -193
  707. scipy/special/xsf/cephes/const.h +0 -87
  708. scipy/special/xsf/cephes/ellie.h +0 -293
  709. scipy/special/xsf/cephes/ellik.h +0 -251
  710. scipy/special/xsf/cephes/ellpe.h +0 -107
  711. scipy/special/xsf/cephes/ellpk.h +0 -117
  712. scipy/special/xsf/cephes/expn.h +0 -260
  713. scipy/special/xsf/cephes/gamma.h +0 -398
  714. scipy/special/xsf/cephes/hyp2f1.h +0 -596
  715. scipy/special/xsf/cephes/hyperg.h +0 -361
  716. scipy/special/xsf/cephes/i0.h +0 -149
  717. scipy/special/xsf/cephes/i1.h +0 -158
  718. scipy/special/xsf/cephes/igam.h +0 -421
  719. scipy/special/xsf/cephes/igam_asymp_coeff.h +0 -195
  720. scipy/special/xsf/cephes/igami.h +0 -313
  721. scipy/special/xsf/cephes/j0.h +0 -225
  722. scipy/special/xsf/cephes/j1.h +0 -198
  723. scipy/special/xsf/cephes/jv.h +0 -715
  724. scipy/special/xsf/cephes/k0.h +0 -164
  725. scipy/special/xsf/cephes/k1.h +0 -163
  726. scipy/special/xsf/cephes/kn.h +0 -243
  727. scipy/special/xsf/cephes/lanczos.h +0 -112
  728. scipy/special/xsf/cephes/ndtr.h +0 -275
  729. scipy/special/xsf/cephes/poch.h +0 -85
  730. scipy/special/xsf/cephes/polevl.h +0 -167
  731. scipy/special/xsf/cephes/psi.h +0 -194
  732. scipy/special/xsf/cephes/rgamma.h +0 -111
  733. scipy/special/xsf/cephes/scipy_iv.h +0 -811
  734. scipy/special/xsf/cephes/shichi.h +0 -248
  735. scipy/special/xsf/cephes/sici.h +0 -224
  736. scipy/special/xsf/cephes/sindg.h +0 -221
  737. scipy/special/xsf/cephes/tandg.h +0 -139
  738. scipy/special/xsf/cephes/trig.h +0 -58
  739. scipy/special/xsf/cephes/unity.h +0 -186
  740. scipy/special/xsf/cephes/zeta.h +0 -172
  741. scipy/special/xsf/config.h +0 -304
  742. scipy/special/xsf/digamma.h +0 -205
  743. scipy/special/xsf/error.h +0 -57
  744. scipy/special/xsf/evalpoly.h +0 -47
  745. scipy/special/xsf/expint.h +0 -266
  746. scipy/special/xsf/hyp2f1.h +0 -694
  747. scipy/special/xsf/iv_ratio.h +0 -173
  748. scipy/special/xsf/lambertw.h +0 -150
  749. scipy/special/xsf/loggamma.h +0 -163
  750. scipy/special/xsf/sici.h +0 -200
  751. scipy/special/xsf/tools.h +0 -427
  752. scipy/special/xsf/trig.h +0 -164
  753. scipy/special/xsf/wright_bessel.h +0 -843
  754. scipy/special/xsf/zlog1.h +0 -35
  755. scipy/stats/_mvn.cp312-win_amd64.dll.a +0 -0
  756. scipy/stats/_mvn.cp312-win_amd64.pyd +0 -0
  757. scipy-1.15.3.dist-info/DELVEWHEEL +0 -2
  758. /scipy-1.15.3-cp312-cp312-win_amd64.whl → /scipy-1.16.0rc2-cp312-cp312-win_amd64.whl +0 -0
  759. {scipy-1.15.3.dist-info → scipy-1.16.0rc2.dist-info}/WHEEL +0 -0
@@ -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 array_namespace, _asarray, xp_copy, is_jax
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.astype(xp.asarray(arr), xp.int64))
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] and np.allclose(np.diag(y), 0) and
1019
- xp.all(y >= 0) and np.allclose(y, y.T)):
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 = int(distance.num_obs_y(y))
1049
+ n = distance.num_obs_y(y)
1034
1050
  method_code = _LINKAGE_METHODS[method]
1035
1051
 
1036
- y = np.asarray(y)
1037
- if method == 'single':
1038
- result = _hierarchy.mst_single_linkage(y, n)
1039
- elif method in ['complete', 'average', 'weighted', 'ward']:
1040
- result = _hierarchy.nn_chain(y, n, method_code)
1041
- else:
1042
- result = _hierarchy.fast_linkage(y, n, method_code)
1043
- result = xp.asarray(result)
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='c', xp=xp)
1434
- is_valid_linkage(Z, throw=True, name='Z')
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(('Corrupt matrix Z. Index to derivative cluster '
1455
- 'is used before it is formed. See row %d, '
1456
- 'column 0') % fi)
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(('Corrupt matrix Z. Index to derivative cluster '
1459
- 'is used before it is formed. See row %d, '
1460
- 'column 1') % fj)
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(('Corrupt matrix Z. The count Z[%d,3] is '
1466
- 'incorrect.') % i)
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] and np.allclose(np.diag(y), 0) and
1524
- np.all(y >= 0) and np.allclose(y, y.T)):
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
- Z = np.asarray(Z)
1539
- y = np.asarray(y)
1540
- return xp.asarray(_optimal_leaf_ordering.optimal_leaf_ordering(Z, y))
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
- is_valid_linkage(Z, throw=True, name='Z')
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 = np.zeros((n * (n-1)) // 2, dtype=np.float64)
1656
-
1657
- Z = np.asarray(Z)
1658
- _hierarchy.cophenetic_distances(Z, zz, int(n))
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
- is_valid_linkage(Z, throw=True, name='Z')
1777
+ _is_valid_linkage(Z, throw=True, name='Z', xp=xp)
1735
1778
 
1736
- if (not d == np.floor(d)) or d < 0:
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
- n = Z.shape[0] + 1
1741
- R = np.zeros((n - 1, 4), dtype=np.float64)
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 = np.asarray(Z)
1744
- _hierarchy.inconsistent(Z, R, int(n), int(d))
1745
- R = xp.asarray(R)
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 len(Zs) == 0 or (len(Zs) == 1 and Zs[0] == 0):
1871
+ if Z.shape in ((), (0, )):
1825
1872
  return xp_copy(Z, xp=xp)
1826
1873
 
1827
- if len(Zs) != 2:
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
- if Zs[0] == 0:
1878
+ n = Z.shape[0]
1879
+ if n == 0:
1832
1880
  return xp_copy(Z, xp=xp)
1833
1881
 
1834
- if xp.min(Z[:, 0:2]) != 1.0 and xp.max(Z[:, 0:2]) != 2 * Zs[0]:
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
- Zpart = xp.concat((Z[:, 0:2] - 1.0, Z[:, 2:]), axis=1)
1838
- CS = np.zeros((Zs[0],), dtype=np.float64)
1839
- if is_jax(xp):
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, order='C', dtype=xp.float64, xp=xp)
1926
- Zs = Z.shape
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
- is_valid_linkage(Z, throw=True, name='Z')
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, order='c', xp=xp)
2014
- is_valid_linkage(Z, throw=True, name='Z')
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, order='c', xp=xp)
2109
- valid = True
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
- if xp.any(R[:, 0] < 0):
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
- valid = False
2206
+ return False
2139
2207
 
2140
- return valid
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, order='c', xp=xp)
2230
- valid = True
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
- n = Z.shape[0]
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
- valid = False
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
- return valid
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
- def _check_hierarchy_uses_cluster_before_formed(Z):
2270
- n = Z.shape[0] + 1
2271
- for i in range(0, n - 1):
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
- def _check_hierarchy_uses_cluster_more_than_once(Z):
2278
- n = Z.shape[0] + 1
2279
- chosen = set()
2280
- for i in range(0, n - 1):
2281
- used_more_than_once = (
2282
- (float(Z[i, 0]) in chosen)
2283
- or (float(Z[i, 1]) in chosen)
2284
- or Z[i, 0] == Z[i, 1]
2285
- )
2286
- if used_more_than_once:
2287
- return True
2288
- chosen.add(float(Z[i, 0]))
2289
- chosen.add(float(Z[i, 1]))
2290
- return False
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, order='c', xp=xp)
2338
- is_valid_linkage(Z, throw=True, name='Z')
2339
- return (Z.shape[0] + 1)
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, order='c', xp=xp)
2395
- Y = _asarray(Y, order='c', xp=xp)
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
- is_valid_linkage(Z, throw=True, name='Z')
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
- is_valid_im(R, throw=True, name='R')
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='c', xp=xp)
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
- is_valid_linkage(Z, throw=True, name='Z')
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
- ML = np.zeros((n,), dtype='i')
2741
- Z = np.asarray(Z)
2742
- _hierarchy.prelist(Z, ML, n)
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, (list, tuple)):
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='c', xp=xp)
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
- is_valid_linkage(Z, throw=True, name='Z')
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, (int, float)):
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`: method
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
- T1 = np.asarray(T1, order='c')
3746
- T2 = np.asarray(T2, order='c')
3880
+ xp = array_namespace(T1, T2)
3881
+ T1 = _asarray(T1, xp=xp)
3882
+ T2 = _asarray(T2, xp=xp)
3747
3883
 
3748
- T1S = T1.shape
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 len(T2S) != 1:
3886
+ if T2.ndim != 1:
3754
3887
  raise ValueError('T2 must be one-dimensional.')
3755
- if T1S[0] != T2S[0]:
3888
+ if T1.shape != T2.shape:
3756
3889
  raise ValueError('T1 and T2 must have the same number of elements.')
3757
- n = T1S[0]
3758
- d1 = {}
3759
- d2 = {}
3760
- for i in range(0, n):
3761
- if T1[i] in d1:
3762
- if T2[i] not in d2:
3763
- return False
3764
- if d1[T1[i]] != T2[i] or d2[T2[i]] != T1[i]:
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
- elif T2[i] in d2:
3767
- return False
3768
- else:
3769
- d1[T1[i]] = T2[i]
3770
- d2[T2[i]] = T1[i]
3771
- return True
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
- is_valid_linkage(Z, throw=True, name='Z')
3989
+ _is_valid_linkage(Z, throw=True, name='Z', xp=xp)
3850
3990
 
3851
- n = Z.shape[0] + 1
3852
- MD = np.zeros((n - 1,))
3853
- Z = np.asarray(Z)
3854
- _hierarchy.get_max_dist_for_each_cluster(Z, MD, int(n))
3855
- MD = xp.asarray(MD)
3856
- return MD
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
- is_valid_linkage(Z, throw=True, name='Z')
3938
- is_valid_im(R, throw=True, name='R')
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
- MI = np.zeros((n - 1,))
3945
- Z = np.asarray(Z)
3946
- R = np.asarray(R)
3947
- _hierarchy.get_max_Rfield_for_each_cluster(Z, R, MI, int(n), 3)
3948
- MI = xp.asarray(MI)
3949
- return MI
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
- is_valid_linkage(Z, throw=True, name='Z')
4033
- is_valid_im(R, throw=True, name='R')
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
- n = Z.shape[0] + 1
4046
- MR = np.zeros((n - 1,))
4047
- Z = np.asarray(Z)
4048
- R = np.asarray(R)
4049
- _hierarchy.get_max_Rfield_for_each_cluster(Z, R, MR, int(n), i)
4050
- MR = xp.asarray(MR)
4051
- return MR
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
- is_valid_linkage(Z, throw=True, name='Z')
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
- n_clusters = int(xp.unique_values(T).shape[0])
4168
- n_obs = int(Z.shape[0] + 1)
4169
- L = np.zeros(n_clusters, dtype=np.int32)
4170
- M = np.zeros(n_clusters, dtype=np.int32)
4171
- Z = np.asarray(Z)
4172
- T = np.asarray(T, dtype=np.int32)
4173
- s = _hierarchy.leaders(Z, T, L, M, n_clusters, n_obs)
4174
- if s >= 0:
4175
- raise ValueError(('T is not a valid assignment vector. Error found '
4176
- 'when examining linkage node %d (< 2n-1).') % s)
4177
- L, M = xp.asarray(L), xp.asarray(M)
4178
- return (L, M)
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)