scipy 1.15.3__cp313-cp313-macosx_12_0_arm64.whl → 1.16.0rc2__cp313-cp313-macosx_12_0_arm64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (629) hide show
  1. scipy/.dylibs/libscipy_openblas.dylib +0 -0
  2. scipy/__config__.py +8 -8
  3. scipy/__init__.py +3 -6
  4. scipy/_cyutility.cpython-313-darwin.so +0 -0
  5. scipy/_lib/_array_api.py +486 -161
  6. scipy/_lib/_array_api_compat_vendor.py +9 -0
  7. scipy/_lib/_bunch.py +4 -0
  8. scipy/_lib/_ccallback_c.cpython-313-darwin.so +0 -0
  9. scipy/_lib/_docscrape.py +1 -1
  10. scipy/_lib/_elementwise_iterative_method.py +15 -26
  11. scipy/_lib/_sparse.py +41 -0
  12. scipy/_lib/_test_deprecation_call.cpython-313-darwin.so +0 -0
  13. scipy/_lib/_test_deprecation_def.cpython-313-darwin.so +0 -0
  14. scipy/_lib/_testutils.py +6 -2
  15. scipy/_lib/_util.py +222 -125
  16. scipy/_lib/array_api_compat/__init__.py +4 -4
  17. scipy/_lib/array_api_compat/_internal.py +19 -6
  18. scipy/_lib/array_api_compat/common/__init__.py +1 -1
  19. scipy/_lib/array_api_compat/common/_aliases.py +365 -193
  20. scipy/_lib/array_api_compat/common/_fft.py +94 -64
  21. scipy/_lib/array_api_compat/common/_helpers.py +413 -180
  22. scipy/_lib/array_api_compat/common/_linalg.py +116 -40
  23. scipy/_lib/array_api_compat/common/_typing.py +179 -10
  24. scipy/_lib/array_api_compat/cupy/__init__.py +1 -4
  25. scipy/_lib/array_api_compat/cupy/_aliases.py +61 -41
  26. scipy/_lib/array_api_compat/cupy/_info.py +16 -6
  27. scipy/_lib/array_api_compat/cupy/_typing.py +24 -39
  28. scipy/_lib/array_api_compat/dask/array/__init__.py +6 -3
  29. scipy/_lib/array_api_compat/dask/array/_aliases.py +267 -108
  30. scipy/_lib/array_api_compat/dask/array/_info.py +105 -34
  31. scipy/_lib/array_api_compat/dask/array/fft.py +5 -8
  32. scipy/_lib/array_api_compat/dask/array/linalg.py +21 -22
  33. scipy/_lib/array_api_compat/numpy/__init__.py +13 -15
  34. scipy/_lib/array_api_compat/numpy/_aliases.py +98 -49
  35. scipy/_lib/array_api_compat/numpy/_info.py +36 -16
  36. scipy/_lib/array_api_compat/numpy/_typing.py +27 -43
  37. scipy/_lib/array_api_compat/numpy/fft.py +11 -5
  38. scipy/_lib/array_api_compat/numpy/linalg.py +75 -22
  39. scipy/_lib/array_api_compat/torch/__init__.py +3 -5
  40. scipy/_lib/array_api_compat/torch/_aliases.py +262 -159
  41. scipy/_lib/array_api_compat/torch/_info.py +27 -16
  42. scipy/_lib/array_api_compat/torch/_typing.py +3 -0
  43. scipy/_lib/array_api_compat/torch/fft.py +17 -18
  44. scipy/_lib/array_api_compat/torch/linalg.py +16 -16
  45. scipy/_lib/array_api_extra/__init__.py +26 -3
  46. scipy/_lib/array_api_extra/_delegation.py +171 -0
  47. scipy/_lib/array_api_extra/_lib/__init__.py +1 -0
  48. scipy/_lib/array_api_extra/_lib/_at.py +463 -0
  49. scipy/_lib/array_api_extra/_lib/_backends.py +46 -0
  50. scipy/_lib/array_api_extra/_lib/_funcs.py +937 -0
  51. scipy/_lib/array_api_extra/_lib/_lazy.py +357 -0
  52. scipy/_lib/array_api_extra/_lib/_testing.py +278 -0
  53. scipy/_lib/array_api_extra/_lib/_utils/__init__.py +1 -0
  54. scipy/_lib/array_api_extra/_lib/_utils/_compat.py +74 -0
  55. scipy/_lib/array_api_extra/_lib/_utils/_compat.pyi +45 -0
  56. scipy/_lib/array_api_extra/_lib/_utils/_helpers.py +559 -0
  57. scipy/_lib/array_api_extra/_lib/_utils/_typing.py +10 -0
  58. scipy/_lib/array_api_extra/_lib/_utils/_typing.pyi +105 -0
  59. scipy/_lib/array_api_extra/testing.py +359 -0
  60. scipy/_lib/decorator.py +2 -2
  61. scipy/_lib/doccer.py +1 -7
  62. scipy/_lib/messagestream.cpython-313-darwin.so +0 -0
  63. scipy/_lib/pyprima/__init__.py +212 -0
  64. scipy/_lib/pyprima/cobyla/__init__.py +0 -0
  65. scipy/_lib/pyprima/cobyla/cobyla.py +559 -0
  66. scipy/_lib/pyprima/cobyla/cobylb.py +714 -0
  67. scipy/_lib/pyprima/cobyla/geometry.py +226 -0
  68. scipy/_lib/pyprima/cobyla/initialize.py +215 -0
  69. scipy/_lib/pyprima/cobyla/trustregion.py +492 -0
  70. scipy/_lib/pyprima/cobyla/update.py +289 -0
  71. scipy/_lib/pyprima/common/__init__.py +0 -0
  72. scipy/_lib/pyprima/common/_bounds.py +34 -0
  73. scipy/_lib/pyprima/common/_linear_constraints.py +46 -0
  74. scipy/_lib/pyprima/common/_nonlinear_constraints.py +54 -0
  75. scipy/_lib/pyprima/common/_project.py +173 -0
  76. scipy/_lib/pyprima/common/checkbreak.py +93 -0
  77. scipy/_lib/pyprima/common/consts.py +47 -0
  78. scipy/_lib/pyprima/common/evaluate.py +99 -0
  79. scipy/_lib/pyprima/common/history.py +38 -0
  80. scipy/_lib/pyprima/common/infos.py +30 -0
  81. scipy/_lib/pyprima/common/linalg.py +435 -0
  82. scipy/_lib/pyprima/common/message.py +290 -0
  83. scipy/_lib/pyprima/common/powalg.py +131 -0
  84. scipy/_lib/pyprima/common/preproc.py +277 -0
  85. scipy/_lib/pyprima/common/present.py +5 -0
  86. scipy/_lib/pyprima/common/ratio.py +54 -0
  87. scipy/_lib/pyprima/common/redrho.py +47 -0
  88. scipy/_lib/pyprima/common/selectx.py +296 -0
  89. scipy/_lib/tests/test__util.py +105 -121
  90. scipy/_lib/tests/test_array_api.py +166 -35
  91. scipy/_lib/tests/test_bunch.py +7 -0
  92. scipy/_lib/tests/test_ccallback.py +2 -10
  93. scipy/_lib/tests/test_public_api.py +13 -0
  94. scipy/cluster/_hierarchy.cpython-313-darwin.so +0 -0
  95. scipy/cluster/_optimal_leaf_ordering.cpython-313-darwin.so +0 -0
  96. scipy/cluster/_vq.cpython-313-darwin.so +0 -0
  97. scipy/cluster/hierarchy.py +393 -223
  98. scipy/cluster/tests/test_hierarchy.py +273 -335
  99. scipy/cluster/tests/test_vq.py +45 -61
  100. scipy/cluster/vq.py +39 -35
  101. scipy/conftest.py +263 -157
  102. scipy/constants/_constants.py +4 -1
  103. scipy/constants/tests/test_codata.py +2 -2
  104. scipy/constants/tests/test_constants.py +11 -18
  105. scipy/datasets/_download_all.py +15 -1
  106. scipy/datasets/_fetchers.py +7 -1
  107. scipy/datasets/_utils.py +1 -1
  108. scipy/differentiate/_differentiate.py +25 -25
  109. scipy/differentiate/tests/test_differentiate.py +24 -25
  110. scipy/fft/_basic.py +20 -0
  111. scipy/fft/_helper.py +3 -34
  112. scipy/fft/_pocketfft/helper.py +29 -1
  113. scipy/fft/_pocketfft/tests/test_basic.py +2 -4
  114. scipy/fft/_pocketfft/tests/test_real_transforms.py +4 -4
  115. scipy/fft/_realtransforms.py +13 -0
  116. scipy/fft/tests/test_basic.py +27 -25
  117. scipy/fft/tests/test_fftlog.py +16 -7
  118. scipy/fft/tests/test_helper.py +18 -34
  119. scipy/fft/tests/test_real_transforms.py +8 -10
  120. scipy/fftpack/convolve.cpython-313-darwin.so +0 -0
  121. scipy/fftpack/tests/test_basic.py +2 -4
  122. scipy/fftpack/tests/test_real_transforms.py +8 -9
  123. scipy/integrate/_bvp.py +9 -3
  124. scipy/integrate/_cubature.py +3 -2
  125. scipy/integrate/_dop.cpython-313-darwin.so +0 -0
  126. scipy/integrate/_lsoda.cpython-313-darwin.so +0 -0
  127. scipy/integrate/_ode.py +9 -2
  128. scipy/integrate/_odepack.cpython-313-darwin.so +0 -0
  129. scipy/integrate/_quad_vec.py +21 -29
  130. scipy/integrate/_quadpack.cpython-313-darwin.so +0 -0
  131. scipy/integrate/_quadpack_py.py +11 -7
  132. scipy/integrate/_quadrature.py +3 -3
  133. scipy/integrate/_rules/_base.py +2 -2
  134. scipy/integrate/_tanhsinh.py +48 -47
  135. scipy/integrate/_test_odeint_banded.cpython-313-darwin.so +0 -0
  136. scipy/integrate/_vode.cpython-313-darwin.so +0 -0
  137. scipy/integrate/tests/test__quad_vec.py +0 -6
  138. scipy/integrate/tests/test_banded_ode_solvers.py +85 -0
  139. scipy/integrate/tests/test_cubature.py +21 -35
  140. scipy/integrate/tests/test_quadrature.py +6 -8
  141. scipy/integrate/tests/test_tanhsinh.py +56 -48
  142. scipy/interpolate/__init__.py +70 -58
  143. scipy/interpolate/_bary_rational.py +22 -22
  144. scipy/interpolate/_bsplines.py +119 -66
  145. scipy/interpolate/_cubic.py +65 -50
  146. scipy/interpolate/_dfitpack.cpython-313-darwin.so +0 -0
  147. scipy/interpolate/_dierckx.cpython-313-darwin.so +0 -0
  148. scipy/interpolate/_fitpack.cpython-313-darwin.so +0 -0
  149. scipy/interpolate/_fitpack2.py +9 -6
  150. scipy/interpolate/_fitpack_impl.py +32 -26
  151. scipy/interpolate/_fitpack_repro.py +23 -19
  152. scipy/interpolate/_interpnd.cpython-313-darwin.so +0 -0
  153. scipy/interpolate/_interpolate.py +30 -12
  154. scipy/interpolate/_ndbspline.py +13 -18
  155. scipy/interpolate/_ndgriddata.py +5 -8
  156. scipy/interpolate/_polyint.py +95 -31
  157. scipy/interpolate/_ppoly.cpython-313-darwin.so +0 -0
  158. scipy/interpolate/_rbf.py +2 -2
  159. scipy/interpolate/_rbfinterp.py +1 -1
  160. scipy/interpolate/_rbfinterp_pythran.cpython-313-darwin.so +0 -0
  161. scipy/interpolate/_rgi.py +31 -26
  162. scipy/interpolate/_rgi_cython.cpython-313-darwin.so +0 -0
  163. scipy/interpolate/dfitpack.py +0 -20
  164. scipy/interpolate/interpnd.py +1 -2
  165. scipy/interpolate/tests/test_bary_rational.py +2 -2
  166. scipy/interpolate/tests/test_bsplines.py +97 -1
  167. scipy/interpolate/tests/test_fitpack2.py +39 -1
  168. scipy/interpolate/tests/test_interpnd.py +32 -20
  169. scipy/interpolate/tests/test_interpolate.py +48 -4
  170. scipy/interpolate/tests/test_rgi.py +2 -1
  171. scipy/io/_fast_matrix_market/__init__.py +2 -0
  172. scipy/io/_harwell_boeing/_fortran_format_parser.py +19 -16
  173. scipy/io/_harwell_boeing/hb.py +7 -11
  174. scipy/io/_idl.py +5 -7
  175. scipy/io/_netcdf.py +15 -5
  176. scipy/io/_test_fortran.cpython-313-darwin.so +0 -0
  177. scipy/io/arff/tests/test_arffread.py +3 -3
  178. scipy/io/matlab/__init__.py +5 -3
  179. scipy/io/matlab/_mio.py +4 -1
  180. scipy/io/matlab/_mio5.py +19 -13
  181. scipy/io/matlab/_mio5_utils.cpython-313-darwin.so +0 -0
  182. scipy/io/matlab/_mio_utils.cpython-313-darwin.so +0 -0
  183. scipy/io/matlab/_miobase.py +4 -1
  184. scipy/io/matlab/_streams.cpython-313-darwin.so +0 -0
  185. scipy/io/matlab/tests/test_mio.py +46 -18
  186. scipy/io/matlab/tests/test_mio_funcs.py +1 -1
  187. scipy/io/tests/test_mmio.py +7 -1
  188. scipy/io/tests/test_wavfile.py +41 -0
  189. scipy/io/wavfile.py +57 -10
  190. scipy/linalg/_basic.py +113 -86
  191. scipy/linalg/_cythonized_array_utils.cpython-313-darwin.so +0 -0
  192. scipy/linalg/_decomp.py +22 -9
  193. scipy/linalg/_decomp_cholesky.py +28 -13
  194. scipy/linalg/_decomp_cossin.py +45 -30
  195. scipy/linalg/_decomp_interpolative.cpython-313-darwin.so +0 -0
  196. scipy/linalg/_decomp_ldl.py +4 -1
  197. scipy/linalg/_decomp_lu.py +18 -6
  198. scipy/linalg/_decomp_lu_cython.cpython-313-darwin.so +0 -0
  199. scipy/linalg/_decomp_polar.py +2 -0
  200. scipy/linalg/_decomp_qr.py +6 -2
  201. scipy/linalg/_decomp_qz.py +3 -0
  202. scipy/linalg/_decomp_schur.py +3 -1
  203. scipy/linalg/_decomp_svd.py +13 -2
  204. scipy/linalg/_decomp_update.cpython-313-darwin.so +0 -0
  205. scipy/linalg/_expm_frechet.py +4 -0
  206. scipy/linalg/_fblas.cpython-313-darwin.so +0 -0
  207. scipy/linalg/_flapack.cpython-313-darwin.so +0 -0
  208. scipy/linalg/_linalg_pythran.cpython-313-darwin.so +0 -0
  209. scipy/linalg/_matfuncs.py +187 -4
  210. scipy/linalg/_matfuncs_expm.cpython-313-darwin.so +0 -0
  211. scipy/linalg/_matfuncs_schur_sqrtm.cpython-313-darwin.so +0 -0
  212. scipy/linalg/_matfuncs_sqrtm.py +1 -99
  213. scipy/linalg/_matfuncs_sqrtm_triu.cpython-313-darwin.so +0 -0
  214. scipy/linalg/_procrustes.py +2 -0
  215. scipy/linalg/_sketches.py +17 -6
  216. scipy/linalg/_solve_toeplitz.cpython-313-darwin.so +0 -0
  217. scipy/linalg/_solvers.py +7 -2
  218. scipy/linalg/_special_matrices.py +26 -36
  219. scipy/linalg/cython_blas.cpython-313-darwin.so +0 -0
  220. scipy/linalg/cython_lapack.cpython-313-darwin.so +0 -0
  221. scipy/linalg/lapack.py +22 -2
  222. scipy/linalg/tests/_cython_examples/meson.build +7 -0
  223. scipy/linalg/tests/test_basic.py +31 -16
  224. scipy/linalg/tests/test_batch.py +588 -0
  225. scipy/linalg/tests/test_cythonized_array_utils.py +0 -2
  226. scipy/linalg/tests/test_decomp.py +40 -3
  227. scipy/linalg/tests/test_decomp_cossin.py +14 -0
  228. scipy/linalg/tests/test_decomp_ldl.py +1 -1
  229. scipy/linalg/tests/test_lapack.py +115 -7
  230. scipy/linalg/tests/test_matfuncs.py +157 -102
  231. scipy/linalg/tests/test_procrustes.py +0 -7
  232. scipy/linalg/tests/test_solve_toeplitz.py +1 -1
  233. scipy/linalg/tests/test_special_matrices.py +1 -5
  234. scipy/ndimage/__init__.py +1 -0
  235. scipy/ndimage/_cytest.cpython-313-darwin.so +0 -0
  236. scipy/ndimage/_delegators.py +8 -2
  237. scipy/ndimage/_filters.py +453 -5
  238. scipy/ndimage/_interpolation.py +36 -6
  239. scipy/ndimage/_measurements.py +4 -2
  240. scipy/ndimage/_morphology.py +5 -0
  241. scipy/ndimage/_nd_image.cpython-313-darwin.so +0 -0
  242. scipy/ndimage/_ni_docstrings.py +5 -1
  243. scipy/ndimage/_ni_label.cpython-313-darwin.so +0 -0
  244. scipy/ndimage/_ni_support.py +1 -5
  245. scipy/ndimage/_rank_filter_1d.cpython-313-darwin.so +0 -0
  246. scipy/ndimage/_support_alternative_backends.py +18 -6
  247. scipy/ndimage/tests/test_filters.py +370 -259
  248. scipy/ndimage/tests/test_fourier.py +7 -9
  249. scipy/ndimage/tests/test_interpolation.py +68 -61
  250. scipy/ndimage/tests/test_measurements.py +18 -35
  251. scipy/ndimage/tests/test_morphology.py +143 -131
  252. scipy/ndimage/tests/test_splines.py +1 -3
  253. scipy/odr/__odrpack.cpython-313-darwin.so +0 -0
  254. scipy/optimize/_basinhopping.py +13 -7
  255. scipy/optimize/_bglu_dense.cpython-313-darwin.so +0 -0
  256. scipy/optimize/_bracket.py +17 -24
  257. scipy/optimize/_chandrupatla.py +9 -10
  258. scipy/optimize/_cobyla_py.py +104 -123
  259. scipy/optimize/_constraints.py +14 -10
  260. scipy/optimize/_differentiable_functions.py +371 -230
  261. scipy/optimize/_differentialevolution.py +4 -3
  262. scipy/optimize/_direct.cpython-313-darwin.so +0 -0
  263. scipy/optimize/_dual_annealing.py +1 -1
  264. scipy/optimize/_elementwise.py +1 -4
  265. scipy/optimize/_group_columns.cpython-313-darwin.so +0 -0
  266. scipy/optimize/_lbfgsb.cpython-313-darwin.so +0 -0
  267. scipy/optimize/_lbfgsb_py.py +57 -16
  268. scipy/optimize/_linprog_doc.py +2 -2
  269. scipy/optimize/_linprog_highs.py +2 -2
  270. scipy/optimize/_linprog_ip.py +25 -10
  271. scipy/optimize/_linprog_util.py +14 -16
  272. scipy/optimize/_lsap.cpython-313-darwin.so +0 -0
  273. scipy/optimize/_lsq/common.py +3 -3
  274. scipy/optimize/_lsq/dogbox.py +16 -2
  275. scipy/optimize/_lsq/givens_elimination.cpython-313-darwin.so +0 -0
  276. scipy/optimize/_lsq/least_squares.py +198 -126
  277. scipy/optimize/_lsq/lsq_linear.py +6 -6
  278. scipy/optimize/_lsq/trf.py +35 -8
  279. scipy/optimize/_milp.py +3 -1
  280. scipy/optimize/_minimize.py +105 -36
  281. scipy/optimize/_minpack.cpython-313-darwin.so +0 -0
  282. scipy/optimize/_minpack_py.py +21 -14
  283. scipy/optimize/_moduleTNC.cpython-313-darwin.so +0 -0
  284. scipy/optimize/_nnls.py +20 -21
  285. scipy/optimize/_nonlin.py +34 -3
  286. scipy/optimize/_numdiff.py +288 -110
  287. scipy/optimize/_optimize.py +86 -48
  288. scipy/optimize/_pava_pybind.cpython-313-darwin.so +0 -0
  289. scipy/optimize/_remove_redundancy.py +5 -5
  290. scipy/optimize/_root_scalar.py +1 -1
  291. scipy/optimize/_shgo.py +6 -0
  292. scipy/optimize/_shgo_lib/_complex.py +1 -1
  293. scipy/optimize/_slsqp_py.py +216 -124
  294. scipy/optimize/_slsqplib.cpython-313-darwin.so +0 -0
  295. scipy/optimize/_spectral.py +1 -1
  296. scipy/optimize/_tnc.py +8 -1
  297. scipy/optimize/_trlib/_trlib.cpython-313-darwin.so +0 -0
  298. scipy/optimize/_trustregion.py +20 -6
  299. scipy/optimize/_trustregion_constr/canonical_constraint.py +7 -7
  300. scipy/optimize/_trustregion_constr/equality_constrained_sqp.py +1 -1
  301. scipy/optimize/_trustregion_constr/minimize_trustregion_constr.py +11 -3
  302. scipy/optimize/_trustregion_constr/projections.py +12 -8
  303. scipy/optimize/_trustregion_constr/qp_subproblem.py +9 -9
  304. scipy/optimize/_trustregion_constr/tests/test_projections.py +7 -7
  305. scipy/optimize/_trustregion_constr/tests/test_qp_subproblem.py +77 -77
  306. scipy/optimize/_trustregion_constr/tr_interior_point.py +5 -5
  307. scipy/optimize/_trustregion_exact.py +0 -1
  308. scipy/optimize/_zeros.cpython-313-darwin.so +0 -0
  309. scipy/optimize/_zeros_py.py +97 -17
  310. scipy/optimize/cython_optimize/_zeros.cpython-313-darwin.so +0 -0
  311. scipy/optimize/slsqp.py +0 -1
  312. scipy/optimize/tests/test__basinhopping.py +1 -1
  313. scipy/optimize/tests/test__differential_evolution.py +4 -4
  314. scipy/optimize/tests/test__linprog_clean_inputs.py +5 -3
  315. scipy/optimize/tests/test__numdiff.py +66 -22
  316. scipy/optimize/tests/test__remove_redundancy.py +2 -2
  317. scipy/optimize/tests/test__shgo.py +9 -1
  318. scipy/optimize/tests/test_bracket.py +36 -46
  319. scipy/optimize/tests/test_chandrupatla.py +133 -135
  320. scipy/optimize/tests/test_cobyla.py +74 -45
  321. scipy/optimize/tests/test_constraints.py +1 -1
  322. scipy/optimize/tests/test_differentiable_functions.py +226 -6
  323. scipy/optimize/tests/test_lbfgsb_hessinv.py +22 -0
  324. scipy/optimize/tests/test_least_squares.py +125 -13
  325. scipy/optimize/tests/test_linear_assignment.py +3 -3
  326. scipy/optimize/tests/test_linprog.py +3 -3
  327. scipy/optimize/tests/test_lsq_linear.py +6 -6
  328. scipy/optimize/tests/test_minimize_constrained.py +2 -2
  329. scipy/optimize/tests/test_minpack.py +4 -4
  330. scipy/optimize/tests/test_nnls.py +43 -3
  331. scipy/optimize/tests/test_nonlin.py +36 -0
  332. scipy/optimize/tests/test_optimize.py +95 -17
  333. scipy/optimize/tests/test_slsqp.py +36 -4
  334. scipy/optimize/tests/test_zeros.py +34 -1
  335. scipy/signal/__init__.py +12 -23
  336. scipy/signal/_delegators.py +568 -0
  337. scipy/signal/_filter_design.py +459 -241
  338. scipy/signal/_fir_filter_design.py +262 -90
  339. scipy/signal/_lti_conversion.py +3 -2
  340. scipy/signal/_ltisys.py +118 -91
  341. scipy/signal/_max_len_seq_inner.cpython-313-darwin.so +0 -0
  342. scipy/signal/_peak_finding_utils.cpython-313-darwin.so +0 -0
  343. scipy/signal/_polyutils.py +172 -0
  344. scipy/signal/_short_time_fft.py +519 -70
  345. scipy/signal/_signal_api.py +30 -0
  346. scipy/signal/_signaltools.py +719 -399
  347. scipy/signal/_sigtools.cpython-313-darwin.so +0 -0
  348. scipy/signal/_sosfilt.cpython-313-darwin.so +0 -0
  349. scipy/signal/_spectral_py.py +230 -50
  350. scipy/signal/_spline.cpython-313-darwin.so +0 -0
  351. scipy/signal/_spline_filters.py +108 -68
  352. scipy/signal/_support_alternative_backends.py +73 -0
  353. scipy/signal/_upfirdn.py +4 -1
  354. scipy/signal/_upfirdn_apply.cpython-313-darwin.so +0 -0
  355. scipy/signal/_waveforms.py +2 -11
  356. scipy/signal/_wavelets.py +1 -1
  357. scipy/signal/fir_filter_design.py +1 -0
  358. scipy/signal/spline.py +4 -11
  359. scipy/signal/tests/_scipy_spectral_test_shim.py +2 -171
  360. scipy/signal/tests/test_bsplines.py +114 -79
  361. scipy/signal/tests/test_cont2discrete.py +9 -2
  362. scipy/signal/tests/test_filter_design.py +721 -481
  363. scipy/signal/tests/test_fir_filter_design.py +332 -140
  364. scipy/signal/tests/test_savitzky_golay.py +4 -3
  365. scipy/signal/tests/test_short_time_fft.py +221 -3
  366. scipy/signal/tests/test_signaltools.py +2144 -1348
  367. scipy/signal/tests/test_spectral.py +50 -6
  368. scipy/signal/tests/test_splines.py +161 -96
  369. scipy/signal/tests/test_upfirdn.py +84 -50
  370. scipy/signal/tests/test_waveforms.py +20 -0
  371. scipy/signal/tests/test_windows.py +607 -466
  372. scipy/signal/windows/_windows.py +287 -148
  373. scipy/sparse/__init__.py +23 -4
  374. scipy/sparse/_base.py +270 -108
  375. scipy/sparse/_bsr.py +7 -4
  376. scipy/sparse/_compressed.py +59 -231
  377. scipy/sparse/_construct.py +90 -38
  378. scipy/sparse/_coo.py +115 -181
  379. scipy/sparse/_csc.py +4 -4
  380. scipy/sparse/_csparsetools.cpython-313-darwin.so +0 -0
  381. scipy/sparse/_csr.py +2 -2
  382. scipy/sparse/_data.py +48 -48
  383. scipy/sparse/_dia.py +105 -18
  384. scipy/sparse/_dok.py +0 -23
  385. scipy/sparse/_index.py +4 -4
  386. scipy/sparse/_matrix.py +23 -0
  387. scipy/sparse/_sparsetools.cpython-313-darwin.so +0 -0
  388. scipy/sparse/_sputils.py +37 -22
  389. scipy/sparse/base.py +0 -9
  390. scipy/sparse/bsr.py +0 -14
  391. scipy/sparse/compressed.py +0 -23
  392. scipy/sparse/construct.py +0 -6
  393. scipy/sparse/coo.py +0 -14
  394. scipy/sparse/csc.py +0 -3
  395. scipy/sparse/csgraph/_flow.cpython-313-darwin.so +0 -0
  396. scipy/sparse/csgraph/_matching.cpython-313-darwin.so +0 -0
  397. scipy/sparse/csgraph/_min_spanning_tree.cpython-313-darwin.so +0 -0
  398. scipy/sparse/csgraph/_reordering.cpython-313-darwin.so +0 -0
  399. scipy/sparse/csgraph/_shortest_path.cpython-313-darwin.so +0 -0
  400. scipy/sparse/csgraph/_tools.cpython-313-darwin.so +0 -0
  401. scipy/sparse/csgraph/_traversal.cpython-313-darwin.so +0 -0
  402. scipy/sparse/csgraph/tests/test_matching.py +14 -2
  403. scipy/sparse/csgraph/tests/test_pydata_sparse.py +4 -1
  404. scipy/sparse/csgraph/tests/test_shortest_path.py +83 -27
  405. scipy/sparse/csr.py +0 -5
  406. scipy/sparse/data.py +1 -6
  407. scipy/sparse/dia.py +0 -7
  408. scipy/sparse/dok.py +0 -10
  409. scipy/sparse/linalg/_dsolve/_superlu.cpython-313-darwin.so +0 -0
  410. scipy/sparse/linalg/_dsolve/linsolve.py +9 -0
  411. scipy/sparse/linalg/_dsolve/tests/test_linsolve.py +35 -28
  412. scipy/sparse/linalg/_eigen/arpack/_arpack.cpython-313-darwin.so +0 -0
  413. scipy/sparse/linalg/_eigen/arpack/arpack.py +23 -17
  414. scipy/sparse/linalg/_eigen/lobpcg/lobpcg.py +6 -6
  415. scipy/sparse/linalg/_interface.py +17 -18
  416. scipy/sparse/linalg/_isolve/_gcrotmk.py +4 -4
  417. scipy/sparse/linalg/_isolve/iterative.py +51 -45
  418. scipy/sparse/linalg/_isolve/lgmres.py +6 -6
  419. scipy/sparse/linalg/_isolve/minres.py +5 -5
  420. scipy/sparse/linalg/_isolve/tfqmr.py +7 -7
  421. scipy/sparse/linalg/_isolve/utils.py +2 -8
  422. scipy/sparse/linalg/_matfuncs.py +1 -1
  423. scipy/sparse/linalg/_norm.py +1 -1
  424. scipy/sparse/linalg/_propack/_cpropack.cpython-313-darwin.so +0 -0
  425. scipy/sparse/linalg/_propack/_dpropack.cpython-313-darwin.so +0 -0
  426. scipy/sparse/linalg/_propack/_spropack.cpython-313-darwin.so +0 -0
  427. scipy/sparse/linalg/_propack/_zpropack.cpython-313-darwin.so +0 -0
  428. scipy/sparse/linalg/_special_sparse_arrays.py +39 -38
  429. scipy/sparse/linalg/tests/test_pydata_sparse.py +14 -0
  430. scipy/sparse/tests/test_arithmetic1d.py +5 -2
  431. scipy/sparse/tests/test_base.py +214 -42
  432. scipy/sparse/tests/test_common1d.py +7 -7
  433. scipy/sparse/tests/test_construct.py +1 -1
  434. scipy/sparse/tests/test_coo.py +272 -4
  435. scipy/sparse/tests/test_sparsetools.py +5 -0
  436. scipy/sparse/tests/test_sputils.py +36 -7
  437. scipy/spatial/_ckdtree.cpython-313-darwin.so +0 -0
  438. scipy/spatial/_distance_pybind.cpython-313-darwin.so +0 -0
  439. scipy/spatial/_distance_wrap.cpython-313-darwin.so +0 -0
  440. scipy/spatial/_hausdorff.cpython-313-darwin.so +0 -0
  441. scipy/spatial/_qhull.cpython-313-darwin.so +0 -0
  442. scipy/spatial/_voronoi.cpython-313-darwin.so +0 -0
  443. scipy/spatial/distance.py +49 -42
  444. scipy/spatial/tests/test_distance.py +15 -1
  445. scipy/spatial/tests/test_kdtree.py +1 -0
  446. scipy/spatial/tests/test_qhull.py +7 -2
  447. scipy/spatial/transform/__init__.py +5 -3
  448. scipy/spatial/transform/_rigid_transform.cpython-313-darwin.so +0 -0
  449. scipy/spatial/transform/_rotation.cpython-313-darwin.so +0 -0
  450. scipy/spatial/transform/tests/test_rigid_transform.py +1221 -0
  451. scipy/spatial/transform/tests/test_rotation.py +1213 -832
  452. scipy/spatial/transform/tests/test_rotation_groups.py +3 -3
  453. scipy/spatial/transform/tests/test_rotation_spline.py +29 -8
  454. scipy/special/__init__.py +1 -47
  455. scipy/special/_add_newdocs.py +34 -772
  456. scipy/special/_basic.py +22 -25
  457. scipy/special/_comb.cpython-313-darwin.so +0 -0
  458. scipy/special/_ellip_harm_2.cpython-313-darwin.so +0 -0
  459. scipy/special/_gufuncs.cpython-313-darwin.so +0 -0
  460. scipy/special/_logsumexp.py +67 -58
  461. scipy/special/_orthogonal.pyi +1 -1
  462. scipy/special/_specfun.cpython-313-darwin.so +0 -0
  463. scipy/special/_special_ufuncs.cpython-313-darwin.so +0 -0
  464. scipy/special/_spherical_bessel.py +4 -4
  465. scipy/special/_support_alternative_backends.py +212 -119
  466. scipy/special/_test_internal.cpython-313-darwin.so +0 -0
  467. scipy/special/_testutils.py +4 -4
  468. scipy/special/_ufuncs.cpython-313-darwin.so +0 -0
  469. scipy/special/_ufuncs.pyi +1 -0
  470. scipy/special/_ufuncs.pyx +215 -1400
  471. scipy/special/_ufuncs_cxx.cpython-313-darwin.so +0 -0
  472. scipy/special/_ufuncs_cxx.pxd +2 -15
  473. scipy/special/_ufuncs_cxx.pyx +5 -44
  474. scipy/special/_ufuncs_cxx_defs.h +2 -16
  475. scipy/special/_ufuncs_defs.h +0 -8
  476. scipy/special/cython_special.cpython-313-darwin.so +0 -0
  477. scipy/special/cython_special.pxd +1 -1
  478. scipy/special/tests/_cython_examples/meson.build +10 -1
  479. scipy/special/tests/test_basic.py +153 -20
  480. scipy/special/tests/test_boost_ufuncs.py +3 -0
  481. scipy/special/tests/test_cdflib.py +35 -11
  482. scipy/special/tests/test_gammainc.py +16 -0
  483. scipy/special/tests/test_hyp2f1.py +2 -2
  484. scipy/special/tests/test_log1mexp.py +85 -0
  485. scipy/special/tests/test_logsumexp.py +206 -64
  486. scipy/special/tests/test_mpmath.py +1 -0
  487. scipy/special/tests/test_nan_inputs.py +1 -1
  488. scipy/special/tests/test_orthogonal.py +17 -18
  489. scipy/special/tests/test_sf_error.py +3 -2
  490. scipy/special/tests/test_sph_harm.py +6 -7
  491. scipy/special/tests/test_support_alternative_backends.py +211 -76
  492. scipy/stats/__init__.py +4 -1
  493. scipy/stats/_ansari_swilk_statistics.cpython-313-darwin.so +0 -0
  494. scipy/stats/_axis_nan_policy.py +5 -12
  495. scipy/stats/_biasedurn.cpython-313-darwin.so +0 -0
  496. scipy/stats/_continued_fraction.py +387 -0
  497. scipy/stats/_continuous_distns.py +277 -310
  498. scipy/stats/_correlation.py +1 -1
  499. scipy/stats/_covariance.py +6 -3
  500. scipy/stats/_discrete_distns.py +39 -32
  501. scipy/stats/_distn_infrastructure.py +39 -12
  502. scipy/stats/_distribution_infrastructure.py +900 -238
  503. scipy/stats/_entropy.py +9 -10
  504. scipy/{_lib → stats}/_finite_differences.py +1 -1
  505. scipy/stats/_hypotests.py +83 -50
  506. scipy/stats/_kde.py +53 -49
  507. scipy/stats/_ksstats.py +1 -1
  508. scipy/stats/_levy_stable/__init__.py +7 -15
  509. scipy/stats/_levy_stable/levyst.cpython-313-darwin.so +0 -0
  510. scipy/stats/_morestats.py +118 -73
  511. scipy/stats/_mstats_basic.py +13 -17
  512. scipy/stats/_mstats_extras.py +8 -8
  513. scipy/stats/_multivariate.py +89 -113
  514. scipy/stats/_new_distributions.py +97 -20
  515. scipy/stats/_page_trend_test.py +12 -5
  516. scipy/stats/_probability_distribution.py +265 -43
  517. scipy/stats/_qmc.py +14 -9
  518. scipy/stats/_qmc_cy.cpython-313-darwin.so +0 -0
  519. scipy/stats/_qmvnt.py +16 -95
  520. scipy/stats/_qmvnt_cy.cpython-313-darwin.so +0 -0
  521. scipy/stats/_quantile.py +335 -0
  522. scipy/stats/_rcont/rcont.cpython-313-darwin.so +0 -0
  523. scipy/stats/_resampling.py +4 -29
  524. scipy/stats/_sampling.py +1 -1
  525. scipy/stats/_sobol.cpython-313-darwin.so +0 -0
  526. scipy/stats/_stats.cpython-313-darwin.so +0 -0
  527. scipy/stats/_stats_mstats_common.py +21 -2
  528. scipy/stats/_stats_py.py +550 -476
  529. scipy/stats/_stats_pythran.cpython-313-darwin.so +0 -0
  530. scipy/stats/_unuran/unuran_wrapper.cpython-313-darwin.so +0 -0
  531. scipy/stats/_unuran/unuran_wrapper.pyi +2 -1
  532. scipy/stats/_variation.py +6 -8
  533. scipy/stats/_wilcoxon.py +13 -7
  534. scipy/stats/tests/common_tests.py +6 -4
  535. scipy/stats/tests/test_axis_nan_policy.py +62 -24
  536. scipy/stats/tests/test_continued_fraction.py +173 -0
  537. scipy/stats/tests/test_continuous.py +379 -60
  538. scipy/stats/tests/test_continuous_basic.py +18 -12
  539. scipy/stats/tests/test_discrete_basic.py +14 -8
  540. scipy/stats/tests/test_discrete_distns.py +16 -16
  541. scipy/stats/tests/test_distributions.py +95 -75
  542. scipy/stats/tests/test_entropy.py +40 -48
  543. scipy/stats/tests/test_fit.py +4 -3
  544. scipy/stats/tests/test_hypotests.py +153 -24
  545. scipy/stats/tests/test_kdeoth.py +109 -41
  546. scipy/stats/tests/test_marray.py +289 -0
  547. scipy/stats/tests/test_morestats.py +79 -47
  548. scipy/stats/tests/test_mstats_basic.py +3 -3
  549. scipy/stats/tests/test_multivariate.py +434 -83
  550. scipy/stats/tests/test_qmc.py +13 -10
  551. scipy/stats/tests/test_quantile.py +199 -0
  552. scipy/stats/tests/test_rank.py +119 -112
  553. scipy/stats/tests/test_resampling.py +47 -56
  554. scipy/stats/tests/test_sampling.py +9 -4
  555. scipy/stats/tests/test_stats.py +799 -939
  556. scipy/stats/tests/test_variation.py +8 -6
  557. scipy/version.py +2 -2
  558. {scipy-1.15.3.dist-info → scipy-1.16.0rc2.dist-info}/LICENSE.txt +4 -4
  559. {scipy-1.15.3.dist-info → scipy-1.16.0rc2.dist-info}/METADATA +11 -11
  560. {scipy-1.15.3.dist-info → scipy-1.16.0rc2.dist-info}/RECORD +561 -568
  561. scipy-1.16.0rc2.dist-info/WHEEL +6 -0
  562. scipy/_lib/array_api_extra/_funcs.py +0 -484
  563. scipy/_lib/array_api_extra/_typing.py +0 -8
  564. scipy/interpolate/_bspl.cpython-313-darwin.so +0 -0
  565. scipy/optimize/_cobyla.cpython-313-darwin.so +0 -0
  566. scipy/optimize/_cython_nnls.cpython-313-darwin.so +0 -0
  567. scipy/optimize/_slsqp.cpython-313-darwin.so +0 -0
  568. scipy/spatial/qhull_src/COPYING.txt +0 -38
  569. scipy/special/libsf_error_state.dylib +0 -0
  570. scipy/special/tests/test_log_softmax.py +0 -109
  571. scipy/special/tests/test_xsf_cuda.py +0 -114
  572. scipy/special/xsf/binom.h +0 -89
  573. scipy/special/xsf/cdflib.h +0 -100
  574. scipy/special/xsf/cephes/airy.h +0 -307
  575. scipy/special/xsf/cephes/besselpoly.h +0 -51
  576. scipy/special/xsf/cephes/beta.h +0 -257
  577. scipy/special/xsf/cephes/cbrt.h +0 -131
  578. scipy/special/xsf/cephes/chbevl.h +0 -85
  579. scipy/special/xsf/cephes/chdtr.h +0 -193
  580. scipy/special/xsf/cephes/const.h +0 -87
  581. scipy/special/xsf/cephes/ellie.h +0 -293
  582. scipy/special/xsf/cephes/ellik.h +0 -251
  583. scipy/special/xsf/cephes/ellpe.h +0 -107
  584. scipy/special/xsf/cephes/ellpk.h +0 -117
  585. scipy/special/xsf/cephes/expn.h +0 -260
  586. scipy/special/xsf/cephes/gamma.h +0 -398
  587. scipy/special/xsf/cephes/hyp2f1.h +0 -596
  588. scipy/special/xsf/cephes/hyperg.h +0 -361
  589. scipy/special/xsf/cephes/i0.h +0 -149
  590. scipy/special/xsf/cephes/i1.h +0 -158
  591. scipy/special/xsf/cephes/igam.h +0 -421
  592. scipy/special/xsf/cephes/igam_asymp_coeff.h +0 -195
  593. scipy/special/xsf/cephes/igami.h +0 -313
  594. scipy/special/xsf/cephes/j0.h +0 -225
  595. scipy/special/xsf/cephes/j1.h +0 -198
  596. scipy/special/xsf/cephes/jv.h +0 -715
  597. scipy/special/xsf/cephes/k0.h +0 -164
  598. scipy/special/xsf/cephes/k1.h +0 -163
  599. scipy/special/xsf/cephes/kn.h +0 -243
  600. scipy/special/xsf/cephes/lanczos.h +0 -112
  601. scipy/special/xsf/cephes/ndtr.h +0 -275
  602. scipy/special/xsf/cephes/poch.h +0 -85
  603. scipy/special/xsf/cephes/polevl.h +0 -167
  604. scipy/special/xsf/cephes/psi.h +0 -194
  605. scipy/special/xsf/cephes/rgamma.h +0 -111
  606. scipy/special/xsf/cephes/scipy_iv.h +0 -811
  607. scipy/special/xsf/cephes/shichi.h +0 -248
  608. scipy/special/xsf/cephes/sici.h +0 -224
  609. scipy/special/xsf/cephes/sindg.h +0 -221
  610. scipy/special/xsf/cephes/tandg.h +0 -139
  611. scipy/special/xsf/cephes/trig.h +0 -58
  612. scipy/special/xsf/cephes/unity.h +0 -186
  613. scipy/special/xsf/cephes/zeta.h +0 -172
  614. scipy/special/xsf/config.h +0 -304
  615. scipy/special/xsf/digamma.h +0 -205
  616. scipy/special/xsf/error.h +0 -57
  617. scipy/special/xsf/evalpoly.h +0 -47
  618. scipy/special/xsf/expint.h +0 -266
  619. scipy/special/xsf/hyp2f1.h +0 -694
  620. scipy/special/xsf/iv_ratio.h +0 -173
  621. scipy/special/xsf/lambertw.h +0 -150
  622. scipy/special/xsf/loggamma.h +0 -163
  623. scipy/special/xsf/sici.h +0 -200
  624. scipy/special/xsf/tools.h +0 -427
  625. scipy/special/xsf/trig.h +0 -164
  626. scipy/special/xsf/wright_bessel.h +0 -843
  627. scipy/special/xsf/zlog1.h +0 -35
  628. scipy/stats/_mvn.cpython-313-darwin.so +0 -0
  629. scipy-1.15.3.dist-info/WHEEL +0 -4
scipy/_lib/_array_api.py CHANGED
@@ -6,8 +6,15 @@ https://data-apis.org/array-api/latest/purpose_and_scope.html
6
6
  The SciPy use case of the Array API is described on the following page:
7
7
  https://data-apis.org/array-api/latest/use_cases.html#use-case-scipy
8
8
  """
9
+ import contextlib
10
+ import dataclasses
11
+ import functools
9
12
  import os
13
+ import textwrap
10
14
 
15
+ from collections.abc import Generator, Iterable, Iterator
16
+ from contextlib import contextmanager
17
+ from contextvars import ContextVar
11
18
  from types import ModuleType
12
19
  from typing import Any, Literal, TypeAlias
13
20
 
@@ -17,6 +24,7 @@ import numpy.typing as npt
17
24
  from scipy._lib import array_api_compat
18
25
  from scipy._lib.array_api_compat import (
19
26
  is_array_api_obj,
27
+ is_lazy_array,
20
28
  size as xp_size,
21
29
  numpy as np_compat,
22
30
  device as xp_device,
@@ -24,18 +32,21 @@ from scipy._lib.array_api_compat import (
24
32
  is_cupy_namespace as is_cupy,
25
33
  is_torch_namespace as is_torch,
26
34
  is_jax_namespace as is_jax,
35
+ is_dask_namespace as is_dask,
27
36
  is_array_api_strict_namespace as is_array_api_strict
28
37
  )
38
+ from scipy._lib._sparse import issparse
39
+ from scipy._lib._docscrape import FunctionDoc
29
40
 
30
41
  __all__ = [
31
42
  '_asarray', 'array_namespace', 'assert_almost_equal', 'assert_array_almost_equal',
32
- 'get_xp_devices',
33
- 'is_array_api_strict', 'is_complex', 'is_cupy', 'is_jax', 'is_numpy', 'is_torch',
43
+ 'default_xp', 'eager_warns', 'is_lazy_array', 'is_marray',
44
+ 'is_array_api_strict', 'is_complex', 'is_cupy', 'is_jax', 'is_numpy', 'is_torch',
34
45
  'SCIPY_ARRAY_API', 'SCIPY_DEVICE', 'scipy_namespace_for',
35
46
  'xp_assert_close', 'xp_assert_equal', 'xp_assert_less',
36
- 'xp_copy', 'xp_copysign', 'xp_device',
37
- 'xp_moveaxis_to_end', 'xp_ravel', 'xp_real', 'xp_sign', 'xp_size',
38
- 'xp_take_along_axis', 'xp_unsupported_param_msg', 'xp_vector_norm',
47
+ 'xp_copy', 'xp_device', 'xp_ravel', 'xp_size',
48
+ 'xp_unsupported_param_msg', 'xp_vector_norm', 'xp_capabilities',
49
+ 'xp_result_type', 'xp_promote'
39
50
  ]
40
51
 
41
52
 
@@ -54,8 +65,9 @@ Array: TypeAlias = Any # To be changed to a Protocol later (see array-api#589)
54
65
  ArrayLike: TypeAlias = Array | npt.ArrayLike
55
66
 
56
67
 
57
- def _compliance_scipy(arrays):
58
- """Raise exceptions on known-bad subclasses.
68
+ def _compliance_scipy(arrays: Iterable[ArrayLike]) -> Iterator[Array]:
69
+ """Raise exceptions on known-bad subclasses. Discard 0-dimensional ArrayLikes
70
+ and convert 1+-dimensional ArrayLikes to numpy.
59
71
 
60
72
  The following subclasses are not supported and raise and error:
61
73
  - `numpy.ma.MaskedArray`
@@ -64,10 +76,10 @@ def _compliance_scipy(arrays):
64
76
  - Any array-like which is neither array API compatible nor coercible by NumPy
65
77
  - Any array-like which is coerced by NumPy to an unsupported dtype
66
78
  """
67
- for i in range(len(arrays)):
68
- array = arrays[i]
79
+ for array in arrays:
80
+ if array is None:
81
+ continue
69
82
 
70
- from scipy.sparse import issparse
71
83
  # this comes from `_util._asarray_validated`
72
84
  if issparse(array):
73
85
  msg = ('Sparse arrays/matrices are not supported by this function. '
@@ -77,14 +89,19 @@ def _compliance_scipy(arrays):
77
89
 
78
90
  if isinstance(array, np.ma.MaskedArray):
79
91
  raise TypeError("Inputs of type `numpy.ma.MaskedArray` are not supported.")
80
- elif isinstance(array, np.matrix):
92
+
93
+ if isinstance(array, np.matrix):
81
94
  raise TypeError("Inputs of type `numpy.matrix` are not supported.")
95
+
82
96
  if isinstance(array, np.ndarray | np.generic):
83
97
  dtype = array.dtype
84
98
  if not (np.issubdtype(dtype, np.number) or np.issubdtype(dtype, np.bool_)):
85
99
  raise TypeError(f"An argument has dtype `{dtype!r}`; "
86
100
  f"only boolean and numerical dtypes are supported.")
87
- elif not is_array_api_obj(array):
101
+
102
+ if is_array_api_obj(array):
103
+ yield array
104
+ else:
88
105
  try:
89
106
  array = np.asanyarray(array)
90
107
  except TypeError:
@@ -97,17 +114,17 @@ def _compliance_scipy(arrays):
97
114
  f"only boolean and numerical dtypes are supported."
98
115
  )
99
116
  raise TypeError(message)
100
- arrays[i] = array
101
- return arrays
117
+ # Ignore 0-dimensional arrays, coherently with array-api-compat.
118
+ # Raise if there are 1+-dimensional array-likes mixed with non-numpy
119
+ # Array API objects.
120
+ if array.ndim:
121
+ yield array
102
122
 
103
123
 
104
124
  def _check_finite(array: Array, xp: ModuleType) -> None:
105
125
  """Check for NaNs or Infs."""
106
- msg = "array must not contain infs or NaNs"
107
- try:
108
- if not xp.all(xp.isfinite(array)):
109
- raise ValueError(msg)
110
- except TypeError:
126
+ if not xp.all(xp.isfinite(array)):
127
+ msg = "array must not contain infs or NaNs"
111
128
  raise ValueError(msg)
112
129
 
113
130
 
@@ -141,11 +158,13 @@ def array_namespace(*arrays: Array) -> ModuleType:
141
158
  # here we could wrap the namespace if needed
142
159
  return np_compat
143
160
 
144
- _arrays = [array for array in arrays if array is not None]
145
-
146
- _arrays = _compliance_scipy(_arrays)
147
-
148
- return array_api_compat.array_namespace(*_arrays)
161
+ api_arrays = list(_compliance_scipy(arrays))
162
+ # In case of a mix of array API compliant arrays and scalars, return
163
+ # the array API namespace. If there are only ArrayLikes (e.g. lists),
164
+ # return NumPy (wrapped by array-api-compat).
165
+ if api_arrays:
166
+ return array_api_compat.array_namespace(*api_arrays)
167
+ return np_compat
149
168
 
150
169
 
151
170
  def _asarray(
@@ -223,12 +242,49 @@ def xp_copy(x: Array, *, xp: ModuleType | None = None) -> Array:
223
242
  return _asarray(x, copy=True, xp=xp)
224
243
 
225
244
 
245
+ _default_xp_ctxvar: ContextVar[ModuleType] = ContextVar("_default_xp")
246
+
247
+ @contextmanager
248
+ def default_xp(xp: ModuleType) -> Generator[None, None, None]:
249
+ """In all ``xp_assert_*`` and ``assert_*`` function calls executed within this
250
+ context manager, test by default that the array namespace is
251
+ the provided across all arrays, unless one explicitly passes the ``xp=``
252
+ parameter or ``check_namespace=False``.
253
+
254
+ Without this context manager, the default value for `xp` is the namespace
255
+ for the desired array (the second parameter of the tests).
256
+ """
257
+ token = _default_xp_ctxvar.set(xp)
258
+ try:
259
+ yield
260
+ finally:
261
+ _default_xp_ctxvar.reset(token)
262
+
263
+
264
+ def eager_warns(x, warning_type, match=None):
265
+ """pytest.warns context manager, but only if x is not a lazy array."""
266
+ import pytest
267
+ # This attribute is interpreted by pytest-run-parallel, ensuring that tests that use
268
+ # `eager_warns` aren't run in parallel (since pytest.warns isn't thread-safe).
269
+ __thread_safe__ = False # noqa: F841
270
+ if is_lazy_array(x):
271
+ return contextlib.nullcontext()
272
+ return pytest.warns(warning_type, match=match)
273
+
274
+
226
275
  def _strict_check(actual, desired, xp, *,
227
276
  check_namespace=True, check_dtype=True, check_shape=True,
228
277
  check_0d=True):
229
278
  __tracebackhide__ = True # Hide traceback for py.test
279
+
280
+ if xp is None:
281
+ try:
282
+ xp = _default_xp_ctxvar.get()
283
+ except LookupError:
284
+ xp = array_namespace(desired)
285
+
230
286
  if check_namespace:
231
- _assert_matching_namespace(actual, desired)
287
+ _assert_matching_namespace(actual, desired, xp)
232
288
 
233
289
  # only NumPy distinguishes between scalars and arrays; we do if check_0d=True.
234
290
  # do this first so we can then cast to array (and thus use the array API) below.
@@ -246,32 +302,39 @@ def _strict_check(actual, desired, xp, *,
246
302
  assert actual.dtype == desired.dtype, _msg
247
303
 
248
304
  if check_shape:
305
+ if is_dask(xp):
306
+ actual.compute_chunk_sizes()
307
+ desired.compute_chunk_sizes()
249
308
  _msg = f"Shapes do not match.\nActual: {actual.shape}\nDesired: {desired.shape}"
250
309
  assert actual.shape == desired.shape, _msg
251
310
 
252
311
  desired = xp.broadcast_to(desired, actual.shape)
253
- return actual, desired
312
+ return actual, desired, xp
254
313
 
255
314
 
256
- def _assert_matching_namespace(actual, desired):
315
+ def _assert_matching_namespace(actual, desired, xp):
257
316
  __tracebackhide__ = True # Hide traceback for py.test
258
- actual = actual if isinstance(actual, tuple) else (actual,)
259
- desired_space = array_namespace(desired)
260
- for arr in actual:
261
- arr_space = array_namespace(arr)
262
- _msg = (f"Namespaces do not match.\n"
263
- f"Actual: {arr_space.__name__}\n"
264
- f"Desired: {desired_space.__name__}")
265
- assert arr_space == desired_space, _msg
317
+
318
+ desired_arr_space = array_namespace(desired)
319
+ _msg = ("Namespace of desired array does not match expectations "
320
+ "set by the `default_xp` context manager or by the `xp`"
321
+ "pytest fixture.\n"
322
+ f"Desired array's space: {desired_arr_space.__name__}\n"
323
+ f"Expected namespace: {xp.__name__}")
324
+ assert desired_arr_space == xp, _msg
325
+
326
+ actual_arr_space = array_namespace(actual)
327
+ _msg = ("Namespace of actual and desired arrays do not match.\n"
328
+ f"Actual: {actual_arr_space.__name__}\n"
329
+ f"Desired: {xp.__name__}")
330
+ assert actual_arr_space == xp, _msg
266
331
 
267
332
 
268
333
  def xp_assert_equal(actual, desired, *, check_namespace=True, check_dtype=True,
269
334
  check_shape=True, check_0d=True, err_msg='', xp=None):
270
335
  __tracebackhide__ = True # Hide traceback for py.test
271
- if xp is None:
272
- xp = array_namespace(actual)
273
336
 
274
- actual, desired = _strict_check(
337
+ actual, desired, xp = _strict_check(
275
338
  actual, desired, xp, check_namespace=check_namespace,
276
339
  check_dtype=check_dtype, check_shape=check_shape,
277
340
  check_0d=check_0d
@@ -293,10 +356,8 @@ def xp_assert_close(actual, desired, *, rtol=None, atol=0, check_namespace=True,
293
356
  check_dtype=True, check_shape=True, check_0d=True,
294
357
  err_msg='', xp=None):
295
358
  __tracebackhide__ = True # Hide traceback for py.test
296
- if xp is None:
297
- xp = array_namespace(actual)
298
359
 
299
- actual, desired = _strict_check(
360
+ actual, desired, xp = _strict_check(
300
361
  actual, desired, xp,
301
362
  check_namespace=check_namespace, check_dtype=check_dtype,
302
363
  check_shape=check_shape, check_0d=check_0d
@@ -326,10 +387,8 @@ def xp_assert_close(actual, desired, *, rtol=None, atol=0, check_namespace=True,
326
387
  def xp_assert_less(actual, desired, *, check_namespace=True, check_dtype=True,
327
388
  check_shape=True, check_0d=True, err_msg='', verbose=True, xp=None):
328
389
  __tracebackhide__ = True # Hide traceback for py.test
329
- if xp is None:
330
- xp = array_namespace(actual)
331
390
 
332
- actual, desired = _strict_check(
391
+ actual, desired, xp = _strict_check(
333
392
  actual, desired, xp, check_namespace=check_namespace,
334
393
  check_dtype=check_dtype, check_shape=check_shape,
335
394
  check_0d=check_0d
@@ -374,42 +433,6 @@ def is_complex(x: Array, xp: ModuleType) -> bool:
374
433
  return xp.isdtype(x.dtype, 'complex floating')
375
434
 
376
435
 
377
- def get_xp_devices(xp: ModuleType) -> list[str] | list[None]:
378
- """Returns a list of available devices for the given namespace."""
379
- devices: list[str] = []
380
- if is_torch(xp):
381
- devices += ['cpu']
382
- import torch # type: ignore[import]
383
- num_cuda = torch.cuda.device_count()
384
- for i in range(0, num_cuda):
385
- devices += [f'cuda:{i}']
386
- if torch.backends.mps.is_available():
387
- devices += ['mps']
388
- return devices
389
- elif is_cupy(xp):
390
- import cupy # type: ignore[import]
391
- num_cuda = cupy.cuda.runtime.getDeviceCount()
392
- for i in range(0, num_cuda):
393
- devices += [f'cuda:{i}']
394
- return devices
395
- elif is_jax(xp):
396
- import jax # type: ignore[import]
397
- num_cpu = jax.device_count(backend='cpu')
398
- for i in range(0, num_cpu):
399
- devices += [f'cpu:{i}']
400
- num_gpu = jax.device_count(backend='gpu')
401
- for i in range(0, num_gpu):
402
- devices += [f'gpu:{i}']
403
- num_tpu = jax.device_count(backend='tpu')
404
- for i in range(0, num_tpu):
405
- devices += [f'tpu:{i}']
406
- return devices
407
-
408
- # given namespace is not known to have a list of available devices;
409
- # return `[None]` so that one can use this in tests for `device=None`.
410
- return [None]
411
-
412
-
413
436
  def scipy_namespace_for(xp: ModuleType) -> ModuleType | None:
414
437
  """Return the `scipy`-like namespace of a non-NumPy backend
415
438
 
@@ -432,42 +455,6 @@ def scipy_namespace_for(xp: ModuleType) -> ModuleType | None:
432
455
  return None
433
456
 
434
457
 
435
- # temporary substitute for xp.moveaxis, which is not yet in all backends
436
- # or covered by array_api_compat.
437
- def xp_moveaxis_to_end(
438
- x: Array,
439
- source: int,
440
- /, *,
441
- xp: ModuleType | None = None) -> Array:
442
- xp = array_namespace(xp) if xp is None else xp
443
- axes = list(range(x.ndim))
444
- temp = axes.pop(source)
445
- axes = axes + [temp]
446
- return xp.permute_dims(x, axes)
447
-
448
-
449
- # temporary substitute for xp.copysign, which is not yet in all backends
450
- # or covered by array_api_compat.
451
- def xp_copysign(x1: Array, x2: Array, /, *, xp: ModuleType | None = None) -> Array:
452
- # no attempt to account for special cases
453
- xp = array_namespace(x1, x2) if xp is None else xp
454
- abs_x1 = xp.abs(x1)
455
- return xp.where(x2 >= 0, abs_x1, -abs_x1)
456
-
457
-
458
- # partial substitute for xp.sign, which does not cover the NaN special case
459
- # that I need. (https://github.com/data-apis/array-api-compat/issues/136)
460
- def xp_sign(x: Array, /, *, xp: ModuleType | None = None) -> Array:
461
- xp = array_namespace(x) if xp is None else xp
462
- if is_numpy(xp): # only NumPy implements the special cases correctly
463
- return xp.sign(x)
464
- sign = xp.zeros_like(x)
465
- one = xp.asarray(1, dtype=x.dtype)
466
- sign = xp.where(x > 0, one, sign)
467
- sign = xp.where(x < 0, -one, sign)
468
- sign = xp.where(xp.isnan(x), xp.nan*one, sign)
469
- return sign
470
-
471
458
  # maybe use `scipy.linalg` if/when array API support is added
472
459
  def xp_vector_norm(x: Array, /, *,
473
460
  axis: int | tuple[int] | None = None,
@@ -503,53 +490,96 @@ def xp_ravel(x: Array, /, *, xp: ModuleType | None = None) -> Array:
503
490
  return xp.reshape(x, (-1,))
504
491
 
505
492
 
506
- def xp_real(x: Array, /, *, xp: ModuleType | None = None) -> Array:
507
- # Convenience wrapper of xp.real that allows non-complex input;
508
- # see data-apis/array-api#824
509
- xp = array_namespace(x) if xp is None else xp
510
- return xp.real(x) if xp.isdtype(x.dtype, 'complex floating') else x
493
+ def xp_swapaxes(a, axis1, axis2, xp=None):
494
+ # Equivalent of np.swapaxes written in terms of array API
495
+ xp = array_namespace(a) if xp is None else xp
496
+ axes = list(range(a.ndim))
497
+ axes[axis1], axes[axis2] = axes[axis2], axes[axis1]
498
+ a = xp.permute_dims(a, axes)
499
+ return a
511
500
 
512
501
 
513
- def xp_take_along_axis(arr: Array,
514
- indices: Array, /, *,
515
- axis: int = -1,
516
- xp: ModuleType | None = None) -> Array:
517
- # Dispatcher for np.take_along_axis for backends that support it;
518
- # see data-apis/array-api/pull#816
519
- xp = array_namespace(arr) if xp is None else xp
520
- if is_torch(xp):
521
- return xp.take_along_dim(arr, indices, dim=axis)
522
- elif is_array_api_strict(xp):
523
- raise NotImplementedError("Array API standard does not define take_along_axis")
524
- else:
525
- return xp.take_along_axis(arr, indices, axis)
502
+ # utility to find common dtype with option to force floating
503
+ def xp_result_type(*args, force_floating=False, xp):
504
+ """
505
+ Returns the dtype that results from applying type promotion rules
506
+ (see Array API Standard Type Promotion Rules) to the arguments. Augments
507
+ standard `result_type` in a few ways:
508
+
509
+ - There is a `force_floating` argument that ensures that the result type
510
+ is floating point, even when all args are integer.
511
+ - When a TypeError is raised (e.g. due to an unsupported promotion)
512
+ and `force_floating=True`, we define a custom rule: use the result type
513
+ of the default float and any other floats passed. See
514
+ https://github.com/scipy/scipy/pull/22695/files#r1997905891
515
+ for rationale.
516
+ - This function accepts array-like iterables, which are immediately converted
517
+ to the namespace's arrays before result type calculation. Consequently, the
518
+ result dtype may be different when an argument is `1.` vs `[1.]`.
519
+
520
+ Typically, this function will be called shortly after `array_namespace`
521
+ on a subset of the arguments passed to `array_namespace`.
522
+ """
523
+ args = [(_asarray(arg, subok=True, xp=xp) if np.iterable(arg) else arg)
524
+ for arg in args]
525
+ args_not_none = [arg for arg in args if arg is not None]
526
+ if force_floating:
527
+ args_not_none.append(1.0)
526
528
 
529
+ if is_numpy(xp) and xp.__version__ < '2.0':
530
+ # Follow NEP 50 promotion rules anyway
531
+ args_not_none = [arg.dtype if getattr(arg, 'size', 0) == 1 else arg
532
+ for arg in args_not_none]
533
+ return xp.result_type(*args_not_none)
527
534
 
528
- # utility to broadcast arrays and promote to common dtype
529
- def xp_broadcast_promote(*args, ensure_writeable=False, force_floating=False, xp=None):
530
- xp = array_namespace(*args) if xp is None else xp
535
+ try: # follow library's preferred promotion rules
536
+ return xp.result_type(*args_not_none)
537
+ except TypeError: # mixed type promotion isn't defined
538
+ if not force_floating:
539
+ raise
540
+ # use `result_type` of default floating point type and any floats present
541
+ # This can be revisited, but right now, the only backends that get here
542
+ # are array-api-strict (which is not for production use) and PyTorch
543
+ # (due to data-apis/array-api-compat#279).
544
+ float_args = []
545
+ for arg in args_not_none:
546
+ arg_array = xp.asarray(arg) if np.isscalar(arg) else arg
547
+ dtype = getattr(arg_array, 'dtype', arg)
548
+ if xp.isdtype(dtype, ('real floating', 'complex floating')):
549
+ float_args.append(arg)
550
+ return xp.result_type(*float_args, xp_default_dtype(xp))
551
+
552
+
553
+ def xp_promote(*args, broadcast=False, force_floating=False, xp):
554
+ """
555
+ Promotes elements of *args to result dtype, ignoring `None`s.
556
+ Includes options for forcing promotion to floating point and
557
+ broadcasting the arrays, again ignoring `None`s.
558
+ Type promotion rules follow `xp_result_type` instead of `xp.result_type`.
531
559
 
532
- args = [(_asarray(arg, subok=True) if arg is not None else arg) for arg in args]
533
- args_not_none = [arg for arg in args if arg is not None]
560
+ Typically, this function will be called shortly after `array_namespace`
561
+ on a subset of the arguments passed to `array_namespace`.
534
562
 
535
- # determine minimum dtype
536
- default_float = xp.asarray(1.).dtype
537
- dtypes = [arg.dtype for arg in args_not_none]
538
- try: # follow library's prefered mixed promotion rules
539
- dtype = xp.result_type(*dtypes)
540
- if force_floating and xp.isdtype(dtype, 'integral'):
541
- # If we were to add `default_float` before checking whether the result
542
- # type is otherwise integral, we risk promotion from lower float.
543
- dtype = xp.result_type(dtype, default_float)
544
- except TypeError: # mixed type promotion isn't defined
545
- float_dtypes = [dtype for dtype in dtypes
546
- if not xp.isdtype(dtype, 'integral')]
547
- if float_dtypes:
548
- dtype = xp.result_type(*float_dtypes, default_float)
549
- elif force_floating:
550
- dtype = default_float
551
- else:
552
- dtype = xp.result_type(*dtypes)
563
+ This function accepts array-like iterables, which are immediately converted
564
+ to the namespace's arrays before result type calculation. Consequently, the
565
+ result dtype may be different when an argument is `1.` vs `[1.]`.
566
+
567
+ See Also
568
+ --------
569
+ xp_result_type
570
+ """
571
+ args = [(_asarray(arg, subok=True, xp=xp) if np.iterable(arg) else arg)
572
+ for arg in args] # solely to prevent double conversion of iterable to array
573
+
574
+ dtype = xp_result_type(*args, force_floating=force_floating, xp=xp)
575
+
576
+ args = [(_asarray(arg, dtype=dtype, subok=True, xp=xp) if arg is not None else arg)
577
+ for arg in args]
578
+
579
+ if not broadcast:
580
+ return args[0] if len(args)==1 else tuple(args)
581
+
582
+ args_not_none = [arg for arg in args if arg is not None]
553
583
 
554
584
  # determine result shape
555
585
  shapes = {arg.shape for arg in args_not_none}
@@ -573,12 +603,13 @@ def xp_broadcast_promote(*args, ensure_writeable=False, force_floating=False, xp
573
603
  kwargs = {'subok': True} if is_numpy(xp) else {}
574
604
  arg = xp.broadcast_to(arg, shape, **kwargs)
575
605
 
576
- # convert dtype/copy only if needed
577
- if (arg.dtype != dtype) or ensure_writeable:
578
- arg = xp.astype(arg, dtype, copy=True)
606
+ # This is much faster than xp.astype(arg, dtype, copy=False)
607
+ if arg.dtype != dtype:
608
+ arg = xp.astype(arg, dtype)
609
+
579
610
  out.append(arg)
580
611
 
581
- return out
612
+ return out[0] if len(out)==1 else tuple(out)
582
613
 
583
614
 
584
615
  def xp_float_to_complex(arr: Array, xp: ModuleType | None = None) -> Array:
@@ -604,3 +635,297 @@ def xp_default_dtype(xp):
604
635
  else:
605
636
  # we default to float64
606
637
  return xp.float64
638
+
639
+
640
+ def xp_result_device(*args):
641
+ """Return the device of an array in `args`, for the purpose of
642
+ input-output device propagation.
643
+ If there are multiple devices, return an arbitrary one.
644
+ If there are no arrays, return None (this typically happens only on NumPy).
645
+ """
646
+ for arg in args:
647
+ # Do not do a duck-type test for the .device attribute, as many backends today
648
+ # don't have it yet. See workarouunds in array_api_compat.device().
649
+ if is_array_api_obj(arg):
650
+ return xp_device(arg)
651
+ return None
652
+
653
+
654
+ def is_marray(xp):
655
+ """Returns True if `xp` is an MArray namespace; False otherwise."""
656
+ return "marray" in xp.__name__
657
+
658
+
659
+ @dataclasses.dataclass(repr=False)
660
+ class _XPSphinxCapability:
661
+ cpu: bool | None # None if not applicable
662
+ gpu: bool | None
663
+ warnings: list[str] = dataclasses.field(default_factory=list)
664
+
665
+ def _render(self, value):
666
+ if value is None:
667
+ return "n/a"
668
+ if not value:
669
+ return "⛔"
670
+ if self.warnings:
671
+ res = "⚠️ " + '; '.join(self.warnings)
672
+ assert len(res) <= 20, "Warnings too long"
673
+ return res
674
+ return "✅"
675
+
676
+ def __str__(self):
677
+ cpu = self._render(self.cpu)
678
+ gpu = self._render(self.gpu)
679
+ return f"{cpu:20} {gpu:20}"
680
+
681
+
682
+ def _make_sphinx_capabilities(
683
+ # lists of tuples [(module name, reason), ...]
684
+ skip_backends=(), xfail_backends=(),
685
+ # @pytest.mark.skip/xfail_xp_backends kwargs
686
+ cpu_only=False, np_only=False, exceptions=(),
687
+ # xpx.lazy_xp_backends kwargs
688
+ allow_dask_compute=False, jax_jit=True,
689
+ # list of tuples [(module name, reason), ...]
690
+ warnings = (),
691
+ # unused in documentation
692
+ reason=None,
693
+ ):
694
+ exceptions = set(exceptions)
695
+
696
+ # Default capabilities
697
+ capabilities = {
698
+ "numpy": _XPSphinxCapability(cpu=True, gpu=None),
699
+ "array_api_strict": _XPSphinxCapability(cpu=True, gpu=None),
700
+ "cupy": _XPSphinxCapability(cpu=None, gpu=True),
701
+ "torch": _XPSphinxCapability(cpu=True, gpu=True),
702
+ "jax.numpy": _XPSphinxCapability(cpu=True, gpu=True,
703
+ warnings=[] if jax_jit else ["no JIT"]),
704
+ # Note: Dask+CuPy is currently untested and unsupported
705
+ "dask.array": _XPSphinxCapability(cpu=True, gpu=None,
706
+ warnings=["computes graph"] if allow_dask_compute else []),
707
+ }
708
+
709
+ # documentation doesn't display the reason
710
+ for module, _ in list(skip_backends) + list(xfail_backends):
711
+ backend = capabilities[module]
712
+ if backend.cpu is not None:
713
+ backend.cpu = False
714
+ if backend.gpu is not None:
715
+ backend.gpu = False
716
+
717
+ for module, backend in capabilities.items():
718
+ if np_only and module not in exceptions | {"numpy"}:
719
+ if backend.cpu is not None:
720
+ backend.cpu = False
721
+ if backend.gpu is not None:
722
+ backend.gpu = False
723
+ elif cpu_only and module not in exceptions and backend.gpu is not None:
724
+ backend.gpu = False
725
+
726
+ for module, warning in warnings:
727
+ backend = capabilities[module]
728
+ backend.warnings.append(warning)
729
+
730
+ return capabilities
731
+
732
+
733
+ def _make_capabilities_note(fun_name, capabilities):
734
+ # Note: deliberately not documenting array-api-strict
735
+ note = f"""
736
+ `{fun_name}` has experimental support for Python Array API Standard compatible
737
+ backends in addition to NumPy. Please consider testing these features
738
+ by setting an environment variable ``SCIPY_ARRAY_API=1`` and providing
739
+ CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following
740
+ combinations of backend and device (or other capability) are supported.
741
+
742
+ ==================== ==================== ====================
743
+ Library CPU GPU
744
+ ==================== ==================== ====================
745
+ NumPy {capabilities['numpy'] }
746
+ CuPy {capabilities['cupy'] }
747
+ PyTorch {capabilities['torch'] }
748
+ JAX {capabilities['jax.numpy'] }
749
+ Dask {capabilities['dask.array'] }
750
+ ==================== ==================== ====================
751
+
752
+ See :ref:`dev-arrayapi` for more information.
753
+ """
754
+ return textwrap.dedent(note)
755
+
756
+
757
+ def xp_capabilities(
758
+ *,
759
+ # Alternative capabilities table.
760
+ # Used only for testing this decorator.
761
+ capabilities_table=None,
762
+ # Generate pytest.mark.skip/xfail_xp_backends.
763
+ # See documentation in conftest.py.
764
+ # lists of tuples [(module name, reason), ...]
765
+ skip_backends=(), xfail_backends=(),
766
+ cpu_only=False, np_only=False, reason=None, exceptions=(),
767
+ # lists of tuples [(module name, reason), ...]
768
+ warnings=(),
769
+ # xpx.testing.lazy_xp_function kwargs.
770
+ # Refer to array-api-extra documentation.
771
+ allow_dask_compute=False, jax_jit=True,
772
+ ):
773
+ """Decorator for a function that states its support among various
774
+ Array API compatible backends.
775
+
776
+ This decorator has two effects:
777
+ 1. It allows tagging tests with ``@make_xp_test_case`` or
778
+ ``make_xp_pytest_param`` (see below) to automatically generate
779
+ SKIP/XFAIL markers and perform additional backend-specific
780
+ testing, such as extra validation for Dask and JAX;
781
+ 2. It automatically adds a note to the function's docstring, containing
782
+ a table matching what has been tested.
783
+
784
+ See Also
785
+ --------
786
+ make_xp_test_case
787
+ make_xp_pytest_param
788
+ array_api_extra.testing.lazy_xp_function
789
+ """
790
+ capabilities_table = (xp_capabilities_table if capabilities_table is None
791
+ else capabilities_table)
792
+
793
+ capabilities = dict(
794
+ skip_backends=skip_backends,
795
+ xfail_backends=xfail_backends,
796
+ cpu_only=cpu_only,
797
+ np_only=np_only,
798
+ reason=reason,
799
+ exceptions=exceptions,
800
+ allow_dask_compute=allow_dask_compute,
801
+ jax_jit=jax_jit,
802
+ warnings=warnings,
803
+ )
804
+ sphinx_capabilities = _make_sphinx_capabilities(**capabilities)
805
+
806
+ def decorator(f):
807
+ # Don't use a wrapper, as in some cases @xp_capabilities is
808
+ # applied to a ufunc
809
+ capabilities_table[f] = capabilities
810
+ note = _make_capabilities_note(f.__name__, sphinx_capabilities)
811
+ doc = FunctionDoc(f)
812
+ doc['Notes'].append(note)
813
+ doc = str(doc).split("\n", 1)[1] # remove signature
814
+ try:
815
+ f.__doc__ = doc
816
+ except AttributeError:
817
+ # Can't update __doc__ on ufuncs if SciPy
818
+ # was compiled against NumPy < 2.2.
819
+ pass
820
+
821
+ return f
822
+ return decorator
823
+
824
+
825
+ def _make_xp_pytest_marks(*funcs, capabilities_table=None):
826
+ capabilities_table = (xp_capabilities_table if capabilities_table is None
827
+ else capabilities_table)
828
+ import pytest
829
+ from scipy._lib.array_api_extra.testing import lazy_xp_function
830
+
831
+ marks = []
832
+ for func in funcs:
833
+ capabilities = capabilities_table[func]
834
+ exceptions = capabilities['exceptions']
835
+ reason = capabilities['reason']
836
+
837
+ if capabilities['cpu_only']:
838
+ marks.append(pytest.mark.skip_xp_backends(
839
+ cpu_only=True, exceptions=exceptions, reason=reason))
840
+ if capabilities['np_only']:
841
+ marks.append(pytest.mark.skip_xp_backends(
842
+ np_only=True, exceptions=exceptions, reason=reason))
843
+
844
+ for mod_name, reason in capabilities['skip_backends']:
845
+ marks.append(pytest.mark.skip_xp_backends(mod_name, reason=reason))
846
+ for mod_name, reason in capabilities['xfail_backends']:
847
+ marks.append(pytest.mark.xfail_xp_backends(mod_name, reason=reason))
848
+
849
+ lazy_kwargs = {k: capabilities[k]
850
+ for k in ('allow_dask_compute', 'jax_jit')}
851
+ lazy_xp_function(func, **lazy_kwargs)
852
+
853
+ return marks
854
+
855
+
856
+ def make_xp_test_case(*funcs, capabilities_table=None):
857
+ capabilities_table = (xp_capabilities_table if capabilities_table is None
858
+ else capabilities_table)
859
+ """Generate pytest decorator for a test function that tests functionality
860
+ of one or more Array API compatible functions.
861
+
862
+ Read the parameters of the ``@xp_capabilities`` decorator applied to the
863
+ listed functions and:
864
+
865
+ - Generate the ``@pytest.mark.skip_xp_backends`` and
866
+ ``@pytest.mark.xfail_xp_backends`` decorators
867
+ for the decorated test function
868
+ - Tag the function with `xpx.testing.lazy_xp_function`
869
+
870
+ See Also
871
+ --------
872
+ xp_capabilities
873
+ make_xp_pytest_param
874
+ array_api_extra.testing.lazy_xp_function
875
+ """
876
+ marks = _make_xp_pytest_marks(*funcs, capabilities_table=capabilities_table)
877
+ return lambda func: functools.reduce(lambda f, g: g(f), marks, func)
878
+
879
+
880
+ def make_xp_pytest_param(func, *args, capabilities_table=None):
881
+ """Variant of ``make_xp_test_case`` that returns a pytest.param for a function,
882
+ with all necessary skip_xp_backends and xfail_xp_backends marks applied::
883
+
884
+ @pytest.mark.parametrize(
885
+ "func", [make_xp_pytest_param(f1), make_xp_pytest_param(f2)]
886
+ )
887
+ def test(func, xp):
888
+ ...
889
+
890
+ The above is equivalent to::
891
+
892
+ @pytest.mark.parametrize(
893
+ "func", [
894
+ pytest.param(f1, marks=[
895
+ pytest.mark.skip_xp_backends(...),
896
+ pytest.mark.xfail_xp_backends(...), ...]),
897
+ pytest.param(f2, marks=[
898
+ pytest.mark.skip_xp_backends(...),
899
+ pytest.mark.xfail_xp_backends(...), ...]),
900
+ )
901
+ def test(func, xp):
902
+ ...
903
+
904
+ Parameters
905
+ ----------
906
+ func : Callable
907
+ Function to be tested. It must be decorated with ``@xp_capabilities``.
908
+ *args : Any, optional
909
+ Extra pytest parameters for the use case, e.g.::
910
+
911
+ @pytest.mark.parametrize("func,verb", [
912
+ make_xp_pytest_param(f1, "hello"),
913
+ make_xp_pytest_param(f2, "world")])
914
+ def test(func, verb, xp):
915
+ # iterates on (func=f1, verb="hello")
916
+ # and (func=f2, verb="world")
917
+
918
+ See Also
919
+ --------
920
+ xp_capabilities
921
+ make_xp_test_case
922
+ array_api_extra.testing.lazy_xp_function
923
+ """
924
+ import pytest
925
+
926
+ marks = _make_xp_pytest_marks(func, capabilities_table=capabilities_table)
927
+ return pytest.param(func, *args, marks=marks, id=func.__name__)
928
+
929
+
930
+ # Is it OK to have a dictionary that is mutated (once upon import) in many places?
931
+ xp_capabilities_table = {} # type: ignore[var-annotated]