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
@@ -4,18 +4,23 @@ import operator
4
4
  import warnings
5
5
 
6
6
  import numpy as np
7
- from numpy import (atleast_1d, poly, polyval, roots, real, asarray,
8
- resize, pi, absolute, sqrt, tan, log10,
7
+ from numpy import (atleast_1d, asarray,
8
+ pi, absolute, sqrt, tan, log10,
9
9
  arcsinh, sin, exp, cosh, arccosh, ceil, conjugate,
10
- zeros, sinh, append, concatenate, prod, ones, full, array,
11
- mintypecode)
10
+ sinh, concatenate, prod, array)
12
11
  from numpy.polynomial.polynomial import polyval as npp_polyval
13
- from numpy.polynomial.polynomial import polyvalfromroots
14
12
 
15
13
  from scipy import special, optimize, fft as sp_fft
16
14
  from scipy.special import comb
17
15
  from scipy._lib._util import float_factorial
18
16
  from scipy.signal._arraytools import _validate_fs
17
+ from scipy.signal import _polyutils as _pu
18
+
19
+ import scipy._lib.array_api_extra as xpx
20
+ from scipy._lib._array_api import (
21
+ array_namespace, xp_promote, xp_size, xp_default_dtype, is_jax, xp_float_to_complex,
22
+ )
23
+ from scipy._lib.array_api_compat import numpy as np_compat
19
24
 
20
25
 
21
26
  __all__ = ['findfreqs', 'freqs', 'freqz', 'tf2zpk', 'zpk2tf', 'normalize',
@@ -55,6 +60,31 @@ def _is_int_type(x):
55
60
  return True
56
61
 
57
62
 
63
+ # https://github.com/numpy/numpy/blob/v2.2.0/numpy/_core/function_base.py#L195-L302
64
+ def _logspace(start, stop, num=50, endpoint=True, base=10.0, dtype=None, *, xp):
65
+ if not isinstance(base, float | int) and xp.asarray(base).ndim > 0:
66
+ # If base is non-scalar, broadcast it with the others, since it
67
+ # may influence how axis is interpreted.
68
+ start, stop, base = map(xp.asarray, (start, stop, base))
69
+ ndmax = xp.broadcast_arrays(start, stop, base).ndim
70
+ start, stop, base = (
71
+ xpx.atleast_nd(a, ndim=ndmax)
72
+ for a in (start, stop, base)
73
+ )
74
+ base = xp.expand_dims(base)
75
+ try:
76
+ result_dt = xp.result_type(start, stop, base)
77
+ except ValueError:
78
+ # all of start, stop and base are python scalars
79
+ result_dt = xp_default_dtype(xp)
80
+ y = xp.linspace(start, stop, num=num, endpoint=endpoint, dtype=result_dt)
81
+
82
+ yp = xp.pow(base, y)
83
+ if dtype is None:
84
+ return yp
85
+ return xp.astype(yp, dtype, copy=False)
86
+
87
+
58
88
  def findfreqs(num, den, N, kind='ba'):
59
89
  """
60
90
  Find array of frequencies for computing the response of an analog filter.
@@ -90,27 +120,42 @@ def findfreqs(num, den, N, kind='ba'):
90
120
  3.16227766e-01, 1.00000000e+00, 3.16227766e+00,
91
121
  1.00000000e+01, 3.16227766e+01, 1.00000000e+02])
92
122
  """
123
+ xp = array_namespace(num, den)
124
+ num, den = map(xp.asarray, (num, den))
125
+
93
126
  if kind == 'ba':
94
- ep = atleast_1d(roots(den)) + 0j
95
- tz = atleast_1d(roots(num)) + 0j
127
+ ep = xpx.atleast_nd(_pu.polyroots(den, xp=xp), ndim=1, xp=xp)
128
+ tz = xpx.atleast_nd(_pu.polyroots(num, xp=xp), ndim=1, xp=xp)
96
129
  elif kind == 'zp':
97
- ep = atleast_1d(den) + 0j
98
- tz = atleast_1d(num) + 0j
130
+ ep = xpx.atleast_nd(den, ndim=1, xp=xp)
131
+ tz = xpx.atleast_nd(num, ndim=1, xp=xp)
99
132
  else:
100
133
  raise ValueError("input must be one of {'ba', 'zp'}")
101
134
 
102
- if len(ep) == 0:
103
- ep = atleast_1d(-1000) + 0j
135
+ ep = xp_float_to_complex(ep, xp=xp)
136
+ tz = xp_float_to_complex(tz, xp=xp)
104
137
 
105
- ez = np.r_[ep[ep.imag >= 0], tz[(np.abs(tz) < 1e5) & (tz.imag >= 0)]]
138
+ if ep.shape[0] == 0:
139
+ ep = xp.asarray([-1000], dtype=ep.dtype)
106
140
 
107
- integ = np.abs(ez) < 1e-10
108
- hfreq = np.round(np.log10(np.max(3 * np.abs(ez.real + integ) +
109
- 1.5 * ez.imag)) + 0.5)
110
- lfreq = np.round(np.log10(0.1 * np.min(np.abs((ez + integ).real) +
111
- 2 * ez.imag)) - 0.5)
141
+ ez = xp.concat((
142
+ ep[xp.imag(ep) >= 0],
143
+ tz[(xp.abs(tz) < 1e5) & (xp.imag(tz) >= 0)]
144
+ ))
112
145
 
113
- w = np.logspace(lfreq, hfreq, N)
146
+ integ = xp.astype(xp.abs(ez) < 1e-10, ez.dtype) # XXX True->1, False->0
147
+ hfreq = xp.round(
148
+ xp.log10(xp.max(3*xp.abs(xp.real(ez) + integ) + 1.5*xp.imag(ez))) + 0.5
149
+ )
150
+
151
+ # the fudge factor is for backwards compatibility: round(-1.5) can be -1 or -2
152
+ # depending on the the floating-point jitter in -1.5
153
+ fudge = 1e-14 if is_jax(xp) else 0
154
+ lfreq = xp.round(
155
+ xp.log10(0.1*xp.min(xp.abs(xp.real(ez + integ)) + 2*xp.imag(ez))) - 0.5 - fudge
156
+ )
157
+
158
+ w = _logspace(lfreq, hfreq, N, xp=xp)
114
159
  return w
115
160
 
116
161
 
@@ -175,16 +220,18 @@ def freqs(b, a, worN=200, plot=None):
175
220
  >>> plt.show()
176
221
 
177
222
  """
223
+ xp = array_namespace(b, a, worN)
224
+
178
225
  if worN is None:
179
226
  # For backwards compatibility
180
227
  w = findfreqs(b, a, 200)
181
228
  elif _is_int_type(worN):
182
229
  w = findfreqs(b, a, worN)
183
230
  else:
184
- w = atleast_1d(worN)
231
+ w = xpx.atleast_nd(xp.asarray(worN), ndim=1, xp=xp)
185
232
 
186
233
  s = 1j * w
187
- h = polyval(b, s) / polyval(a, s)
234
+ h = _pu.polyval(b, s, xp=xp) / _pu.polyval(a, s, xp=xp)
188
235
  if plot is not None:
189
236
  plot(w, h)
190
237
 
@@ -251,8 +298,13 @@ def freqs_zpk(z, p, k, worN=200):
251
298
  >>> plt.show()
252
299
 
253
300
  """
254
- k = np.asarray(k)
255
- if k.size > 1:
301
+ xp = array_namespace(z, p)
302
+ z, p = map(xp.asarray, (z, p))
303
+
304
+ # NB: k is documented to be a scalar; for backwards compat we keep allowing it
305
+ # to be a size-1 array, but it does not influence the namespace calculation.
306
+ k = xp.asarray(k, dtype=xp_default_dtype(xp))
307
+ if xp_size(k) > 1:
256
308
  raise ValueError('k must be a single scalar gain')
257
309
 
258
310
  if worN is None:
@@ -263,10 +315,10 @@ def freqs_zpk(z, p, k, worN=200):
263
315
  else:
264
316
  w = worN
265
317
 
266
- w = atleast_1d(w)
318
+ w = xpx.atleast_nd(xp.asarray(w), ndim=1, xp=xp)
267
319
  s = 1j * w
268
- num = polyvalfromroots(s, z)
269
- den = polyvalfromroots(s, p)
320
+ num = _pu.npp_polyvalfromroots(s, z, xp=xp)
321
+ den = _pu.npp_polyvalfromroots(s, p, xp=xp)
270
322
  h = k * num/den
271
323
  return w, h
272
324
 
@@ -430,8 +482,15 @@ def freqz(b, a=1, worN=512, whole=False, plot=None, fs=2*pi,
430
482
  (2, 1024)
431
483
 
432
484
  """
433
- b = atleast_1d(b)
434
- a = atleast_1d(a)
485
+ xp = array_namespace(b, a)
486
+
487
+ b, a = map(xp.asarray, (b, a))
488
+ if xp.isdtype(a.dtype, 'integral'):
489
+ a = xp.astype(a, xp_default_dtype(xp))
490
+ res_dtype = xp.result_type(b, a)
491
+
492
+ b = xpx.atleast_nd(b, ndim=1, xp=xp)
493
+ a = xpx.atleast_nd(a, ndim=1, xp=xp)
435
494
 
436
495
  fs = _validate_fs(fs, allow_none=False)
437
496
 
@@ -449,40 +508,51 @@ def freqz(b, a=1, worN=512, whole=False, plot=None, fs=2*pi,
449
508
  lastpoint = 2 * pi if whole else pi
450
509
  # if include_nyquist is true and whole is false, w should
451
510
  # include end point
452
- w = np.linspace(0, lastpoint, N,
453
- endpoint=include_nyquist and not whole)
511
+ w = xp.linspace(0, lastpoint, N,
512
+ endpoint=include_nyquist and not whole, dtype=res_dtype)
454
513
  n_fft = N if whole else 2 * (N - 1) if include_nyquist else 2 * N
455
- if (a.size == 1 and (b.ndim == 1 or (b.shape[-1] == 1))
514
+ if (xp_size(a) == 1 and (b.ndim == 1 or (b.shape[-1] == 1))
456
515
  and n_fft >= b.shape[0]
457
516
  and n_fft > 0): # TODO: review threshold acc. to benchmark?
458
- if np.isrealobj(b) and np.isrealobj(a):
517
+
518
+ if (xp.isdtype(b.dtype, "real floating") and
519
+ xp.isdtype(a.dtype, "real floating")
520
+ ):
459
521
  fft_func = sp_fft.rfft
460
522
  else:
461
523
  fft_func = sp_fft.fft
462
- h = fft_func(b, n=n_fft, axis=0)[:N]
524
+
525
+ h = fft_func(b, n=n_fft, axis=0)
526
+ h = h[:min(N, h.shape[0]), ...]
463
527
  h /= a
528
+
464
529
  if fft_func is sp_fft.rfft and whole:
465
530
  # exclude DC and maybe Nyquist (no need to use axis_reverse
466
531
  # here because we can build reversal with the truncation)
467
- stop = -1 if n_fft % 2 == 1 else -2
468
- h_flip = slice(stop, 0, -1)
469
- h = np.concatenate((h, h[h_flip].conj()))
532
+ stop = None if n_fft % 2 == 1 else -1
533
+ h_flipped = xp.flip(h[1:stop, ...], axis=0)
534
+ h = xp.concat((h, xp.conj(h_flipped)))
470
535
  if b.ndim > 1:
471
536
  # Last axis of h has length 1, so drop it.
472
537
  h = h[..., 0]
473
538
  # Move the first axis of h to the end.
474
- h = np.moveaxis(h, 0, -1)
539
+ h = xp.moveaxis(h, 0, -1)
475
540
  else:
476
- w = atleast_1d(worN)
541
+ if isinstance(worN, complex):
542
+ # backwards compat
543
+ worN = worN.real
544
+ w = xpx.atleast_nd(xp.asarray(worN, dtype=res_dtype), ndim=1, xp=xp)
545
+ if xp.isdtype(w.dtype, 'integral'):
546
+ w = xp.astype(w, xp_default_dtype(xp))
477
547
  del worN
478
- w = 2*pi*w/fs
548
+ w = 2 * pi * w / fs
479
549
 
480
550
  if h is None: # still need to compute using freqs w
481
- zm1 = exp(-1j * w)
482
- h = (npp_polyval(zm1, b, tensor=False) /
483
- npp_polyval(zm1, a, tensor=False))
551
+ zm1 = xp.exp(-1j * w)
552
+ h = (_pu.npp_polyval(zm1, b, tensor=False, xp=xp) /
553
+ _pu.npp_polyval(zm1, a, tensor=False, xp=xp))
484
554
 
485
- w = w*(fs/(2*pi))
555
+ w = w * (fs / (2 * pi))
486
556
 
487
557
  if plot is not None:
488
558
  plot(w, h)
@@ -573,7 +643,13 @@ def freqz_zpk(z, p, k, worN=512, whole=False, fs=2*pi):
573
643
  >>> plt.show()
574
644
 
575
645
  """
576
- z, p = map(atleast_1d, (z, p))
646
+ xp = array_namespace(z, p)
647
+ z, p = map(xp.asarray, (z, p))
648
+
649
+ z = xpx.atleast_nd(z, ndim=1, xp=xp)
650
+ p = xpx.atleast_nd(p, ndim=1, xp=xp)
651
+ res_dtype = xp.result_type(z, p)
652
+ res_dtype = xp.float64 if res_dtype in (xp.float64, xp.complex128) else xp.float32
577
653
 
578
654
  fs = _validate_fs(fs, allow_none=False)
579
655
 
@@ -584,15 +660,19 @@ def freqz_zpk(z, p, k, worN=512, whole=False, fs=2*pi):
584
660
 
585
661
  if worN is None:
586
662
  # For backwards compatibility
587
- w = np.linspace(0, lastpoint, 512, endpoint=False)
663
+ w = xp.linspace(0, lastpoint, 512, endpoint=False, dtype=res_dtype)
588
664
  elif _is_int_type(worN):
589
- w = np.linspace(0, lastpoint, worN, endpoint=False)
665
+ w = xp.linspace(0, lastpoint, worN, endpoint=False, dtype=res_dtype)
590
666
  else:
591
- w = atleast_1d(worN)
592
- w = 2*pi*w/fs
667
+ w = xp.asarray(worN)
668
+ if xp.isdtype(w.dtype, 'integral'):
669
+ w = xp.astype(w, xp_default_dtype(xp))
670
+ w = xpx.atleast_nd(w, ndim=1, xp=xp)
671
+ w = 2 * pi * w / fs
593
672
 
594
- zm1 = exp(1j * w)
595
- h = k * polyvalfromroots(zm1, z) / polyvalfromroots(zm1, p)
673
+ zm1 = xp.exp(1j * w)
674
+ func = _pu.npp_polyvalfromroots
675
+ h = xp.asarray(k, dtype=res_dtype) * func(zm1, z, xp=xp) / func(zm1, p, xp=xp)
596
676
 
597
677
  w = w*(fs/(2*pi))
598
678
 
@@ -725,15 +805,19 @@ def group_delay(system, w=512, whole=False, fs=2*pi):
725
805
  return w, gd
726
806
 
727
807
 
728
- def _validate_sos(sos):
808
+ def _validate_sos(sos, xp=None):
729
809
  """Helper to validate a SOS input"""
730
- sos = np.atleast_2d(sos)
810
+ if xp is None:
811
+ xp = np # backcompat, cf sosfilt, sosfiltfilt
812
+
813
+ sos = xp.asarray(sos)
814
+ sos = xpx.atleast_nd(sos, ndim=2, xp=xp)
731
815
  if sos.ndim != 2:
732
816
  raise ValueError('sos array must be 2D')
733
817
  n_sections, m = sos.shape
734
818
  if m != 6:
735
819
  raise ValueError('sos array must be shape (n_sections, 6)')
736
- if not (sos[:, 3] == 1).all():
820
+ if not xp.all(sos[:, 3] == 1):
737
821
  raise ValueError('sos[:, 3] should be all ones')
738
822
  return sos, n_sections
739
823
 
@@ -787,12 +871,14 @@ def freqz_sos(sos, worN=512, whole=False, fs=2*pi):
787
871
 
788
872
  See Also
789
873
  --------
790
- freqz, sosfilt
874
+ freqz, sosfilt, sosfreqz
791
875
 
792
876
  Notes
793
877
  -----
794
- .. versionadded:: 0.19.0
795
-
878
+ This function used to be called ``sosfreqz`` in older versions (≥ 0.19.0)
879
+
880
+ .. versionadded:: 1.15.0
881
+
796
882
  Examples
797
883
  --------
798
884
  Design a 15th-order bandpass filter in SOS format.
@@ -850,13 +936,16 @@ def freqz_sos(sos, worN=512, whole=False, fs=2*pi):
850
936
  >>> plt.show()
851
937
 
852
938
  """
939
+ xp = array_namespace(sos)
940
+
853
941
  fs = _validate_fs(fs, allow_none=False)
854
942
 
855
- sos, n_sections = _validate_sos(sos)
943
+ sos, n_sections = _validate_sos(sos, xp)
856
944
  if n_sections == 0:
857
945
  raise ValueError('Cannot compute frequencies with no sections')
858
946
  h = 1.
859
- for row in sos:
947
+ for j in range(sos.shape[0]):
948
+ row = sos[j, :]
860
949
  w, rowh = freqz(row[:3], row[3:], worN=worN, whole=whole, fs=fs)
861
950
  h *= rowh
862
951
  return w, h
@@ -864,11 +953,14 @@ def freqz_sos(sos, worN=512, whole=False, fs=2*pi):
864
953
 
865
954
  def sosfreqz(*args, **kwargs):
866
955
  """
867
- Compute the frequency response of a digital filter in SOS format.
868
-
869
- .. warning:: This function is an alias, provided for backward
870
- compatibility. New code should use the function
871
- :func:`scipy.signal.freqz_sos`.
956
+ Compute the frequency response of a digital filter in SOS format (legacy).
957
+
958
+ .. legacy:: function
959
+
960
+ This function is an alias, provided for backward compatibility.
961
+ New code should use the function :func:`scipy.signal.freqz_sos`.
962
+ This function became obsolete from version 1.15.0.
963
+
872
964
  """
873
965
  return freqz_sos(*args, **kwargs)
874
966
 
@@ -1122,13 +1214,15 @@ def tf2zpk(b, a):
1122
1214
  array([ -2.5+2.59807621j , -2.5-2.59807621j]),
1123
1215
  3.0)
1124
1216
  """
1217
+ xp = array_namespace(b, a)
1125
1218
  b, a = normalize(b, a)
1126
- b = (b + 0.0) / a[0]
1127
- a = (a + 0.0) / a[0]
1219
+
1220
+ a, b = xp_promote(a, b, xp=xp, force_floating=True)
1221
+
1128
1222
  k = b[0]
1129
- b /= b[0]
1130
- z = roots(b)
1131
- p = roots(a)
1223
+ b = b / b[0]
1224
+ z = _pu.polyroots(b, xp=xp)
1225
+ p = _pu.polyroots(a, xp=xp)
1132
1226
  return z, p, k
1133
1227
 
1134
1228
 
@@ -1170,38 +1264,26 @@ def zpk2tf(z, p, k):
1170
1264
  >>> zpk2tf(z, p, k)
1171
1265
  ( array([ 5., -40., 60.]), array([ 1., -9., 8.]))
1172
1266
  """
1173
- z = atleast_1d(z)
1174
- k = atleast_1d(k)
1175
- if len(z.shape) > 1:
1176
- temp = poly(z[0])
1177
- b = np.empty((z.shape[0], z.shape[1] + 1), temp.dtype.char)
1178
- if len(k) == 1:
1267
+ xp = array_namespace(z, p)
1268
+ z, p, k = map(xp.asarray, (z, p, k))
1269
+
1270
+ z = xpx.atleast_nd(z, ndim=1, xp=xp)
1271
+ k = xpx.atleast_nd(k, ndim=1, xp=xp)
1272
+ if xp.isdtype(k.dtype, 'integral'):
1273
+ k = xp.astype(k, xp_default_dtype(xp))
1274
+
1275
+ if z.ndim > 1:
1276
+ temp = _pu.poly(z[0], xp=xp)
1277
+ b = xp.empty((z.shape[0], z.shape[1] + 1), dtype=temp.dtype)
1278
+ if k.shape[0] == 1:
1179
1279
  k = [k[0]] * z.shape[0]
1180
1280
  for i in range(z.shape[0]):
1181
- b[i] = k[i] * poly(z[i])
1281
+ b[i] = k[i] * _pu.poly(z[i], xp=xp)
1182
1282
  else:
1183
- b = k * poly(z)
1184
- a = atleast_1d(poly(p))
1185
-
1186
- # Use real output if possible. Copied from np.poly, since
1187
- # we can't depend on a specific version of numpy.
1188
- if issubclass(b.dtype.type, np.complexfloating):
1189
- # if complex roots are all complex conjugates, the roots are real.
1190
- roots = np.asarray(z, complex)
1191
- pos_roots = np.compress(roots.imag > 0, roots)
1192
- neg_roots = np.conjugate(np.compress(roots.imag < 0, roots))
1193
- if len(pos_roots) == len(neg_roots):
1194
- if np.all(np.sort_complex(neg_roots) == np.sort_complex(pos_roots)):
1195
- b = b.real.copy()
1196
-
1197
- if issubclass(a.dtype.type, np.complexfloating):
1198
- # if complex roots are all complex conjugates, the roots are real.
1199
- roots = np.asarray(p, complex)
1200
- pos_roots = np.compress(roots.imag > 0, roots)
1201
- neg_roots = np.conjugate(np.compress(roots.imag < 0, roots))
1202
- if len(pos_roots) == len(neg_roots):
1203
- if np.all(np.sort_complex(neg_roots) == np.sort_complex(pos_roots)):
1204
- a = a.real.copy()
1283
+ b = k * _pu.poly(z, xp=xp)
1284
+
1285
+ a = _pu.poly(p, xp=xp)
1286
+ a = xpx.atleast_nd(xp.asarray(a), ndim=1, xp=xp)
1205
1287
 
1206
1288
  return b, a
1207
1289
 
@@ -1297,17 +1379,20 @@ def sos2tf(sos):
1297
1379
  ( array([0.91256522, 0.91256522, 0. ]),
1298
1380
  array([1. , 0.82513043, 0. ]))
1299
1381
  """
1300
- sos = np.asarray(sos)
1382
+ xp = array_namespace(sos)
1383
+ sos = xp.asarray(sos)
1384
+
1301
1385
  result_type = sos.dtype
1302
- if result_type.kind in 'bui':
1303
- result_type = np.float64
1386
+ if xp.isdtype(result_type, 'integral'):
1387
+ result_type = xp_default_dtype(xp)
1388
+
1389
+ b = xp.asarray([1], dtype=result_type)
1390
+ a = xp.asarray([1], dtype=result_type)
1304
1391
 
1305
- b = np.array([1], dtype=result_type)
1306
- a = np.array([1], dtype=result_type)
1307
1392
  n_sections = sos.shape[0]
1308
1393
  for section in range(n_sections):
1309
- b = np.polymul(b, sos[section, :3])
1310
- a = np.polymul(a, sos[section, 3:])
1394
+ b = _pu.polymul(b, sos[section, :3], xp=xp)
1395
+ a = _pu.polymul(a, sos[section, 3:], xp=xp)
1311
1396
  return b, a
1312
1397
 
1313
1398
 
@@ -1338,15 +1423,17 @@ def sos2zpk(sos):
1338
1423
 
1339
1424
  .. versionadded:: 0.16.0
1340
1425
  """
1341
- sos = np.asarray(sos)
1426
+ xp = array_namespace(sos)
1427
+ sos = xp.asarray(sos)
1428
+
1342
1429
  n_sections = sos.shape[0]
1343
- z = np.zeros(n_sections*2, np.complex128)
1344
- p = np.zeros(n_sections*2, np.complex128)
1430
+ z = xp.zeros(n_sections*2, dtype=xp.complex128)
1431
+ p = xp.zeros(n_sections*2, dtype=xp.complex128)
1345
1432
  k = 1.
1346
1433
  for section in range(n_sections):
1347
1434
  zpk = tf2zpk(sos[section, :3], sos[section, 3:])
1348
- z[2*section:2*section+len(zpk[0])] = zpk[0]
1349
- p[2*section:2*section+len(zpk[1])] = zpk[1]
1435
+ z = xpx.at(z, slice(2*section, 2*section + zpk[0].shape[0])).set(zpk[0])
1436
+ p = xpx.at(p, slice(2*section, 2*section + zpk[1].shape[0])).set(zpk[1])
1350
1437
  k *= zpk[2]
1351
1438
  return z, p, k
1352
1439
 
@@ -1561,6 +1648,12 @@ def zpk2sos(z, p, k, pairing=None, *, analog=False):
1561
1648
  # 4. Further optimizations of the section ordering / pole-zero pairing.
1562
1649
  # See the wiki for other potential issues.
1563
1650
 
1651
+ xp = array_namespace(z, p)
1652
+
1653
+ # convert to numpy, convert back on exit XXX
1654
+ z, p = map(np.asarray, (z, p))
1655
+ k = np.asarray(k)
1656
+
1564
1657
  if pairing is None:
1565
1658
  pairing = 'minimal' if analog else 'nearest'
1566
1659
 
@@ -1574,9 +1667,9 @@ def zpk2sos(z, p, k, pairing=None, *, analog=False):
1574
1667
 
1575
1668
  if len(z) == len(p) == 0:
1576
1669
  if not analog:
1577
- return np.array([[k, 0., 0., 1., 0., 0.]])
1670
+ return xp.asarray(np.asarray([[k, 0., 0., 1., 0., 0.]]))
1578
1671
  else:
1579
- return np.array([[0., 0., k, 0., 0., 1.]])
1672
+ return xp.asarray(np.asarray([[0., 0., k, 0., 0., 1.]]))
1580
1673
 
1581
1674
  if pairing != 'minimal':
1582
1675
  # ensure we have the same number of poles and zeros, and make copies
@@ -1687,10 +1780,10 @@ def zpk2sos(z, p, k, pairing=None, *, analog=False):
1687
1780
 
1688
1781
  # put gain in first sos
1689
1782
  sos[0][:3] *= k
1690
- return sos
1783
+ return xp.asarray(sos)
1691
1784
 
1692
1785
 
1693
- def _align_nums(nums):
1786
+ def _align_nums(nums, xp):
1694
1787
  """Aligns the shapes of multiple numerators.
1695
1788
 
1696
1789
  Given an array of numerator coefficient arrays [[a_1, a_2,...,
@@ -1715,19 +1808,19 @@ def _align_nums(nums):
1715
1808
  # The statement can throw a ValueError if one
1716
1809
  # of the numerators is a single digit and another
1717
1810
  # is array-like e.g. if nums = [5, [1, 2, 3]]
1718
- nums = asarray(nums)
1811
+ nums = xp.asarray(nums)
1719
1812
 
1720
- if not np.issubdtype(nums.dtype, np.number):
1813
+ if not xp.isdtype(nums.dtype, "numeric"):
1721
1814
  raise ValueError("dtype of numerator is non-numeric")
1722
1815
 
1723
1816
  return nums
1724
1817
 
1725
1818
  except ValueError:
1726
- nums = [np.atleast_1d(num) for num in nums]
1727
- max_width = max(num.size for num in nums)
1819
+ nums = [xpx.atleast_nd(xp.asarray(num), ndim=1) for num in nums]
1820
+ max_width = max(xp_size(num) for num in nums)
1728
1821
 
1729
1822
  # pre-allocate
1730
- aligned_nums = np.zeros((len(nums), max_width))
1823
+ aligned_nums = xp.zeros((len(nums), max_width))
1731
1824
 
1732
1825
  # Create numerators with padded zeros
1733
1826
  for index, num in enumerate(nums):
@@ -1792,29 +1885,37 @@ def normalize(b, a):
1792
1885
  Badly conditioned filter coefficients (numerator): the results may be meaningless
1793
1886
 
1794
1887
  """
1795
- num, den = b, a
1888
+ try:
1889
+ xp = array_namespace(b, a)
1890
+ except TypeError:
1891
+ # object arrays, test_ltisys.py::TestSS2TF::test_simo_round_trip
1892
+ xp = np_compat
1893
+
1894
+ den = xp.asarray(a)
1895
+ den = xpx.atleast_nd(den, ndim=1, xp=xp)
1796
1896
 
1797
- den = np.atleast_1d(den)
1798
- num = np.atleast_2d(_align_nums(num))
1897
+ num = xp.asarray(b)
1898
+ num = xpx.atleast_nd(_align_nums(num, xp), ndim=2, xp=xp)
1799
1899
 
1800
1900
  if den.ndim != 1:
1801
1901
  raise ValueError("Denominator polynomial must be rank-1 array.")
1802
1902
  if num.ndim > 2:
1803
1903
  raise ValueError("Numerator polynomial must be rank-1 or"
1804
1904
  " rank-2 array.")
1805
- if np.all(den == 0):
1905
+ if xp.all(den == 0):
1806
1906
  raise ValueError("Denominator must have at least on nonzero element.")
1807
1907
 
1808
1908
  # Trim leading zeros in denominator, leave at least one.
1809
- den = np.trim_zeros(den, 'f')
1909
+ den = _pu._trim_zeros(den, 'f')
1810
1910
 
1811
1911
  # Normalize transfer function
1812
1912
  num, den = num / den[0], den / den[0]
1813
1913
 
1814
1914
  # Count numerator columns that are all zero
1815
1915
  leading_zeros = 0
1816
- for col in num.T:
1817
- if np.allclose(col, 0, atol=1e-14):
1916
+ for j in range(num.shape[-1]):
1917
+ col = num[:, j]
1918
+ if xp.all(xp.abs(col) <= 1e-14):
1818
1919
  leading_zeros += 1
1819
1920
  else:
1820
1921
  break
@@ -1892,15 +1993,20 @@ def lp2lp(b, a, wo=1.0):
1892
1993
  >>> plt.legend()
1893
1994
 
1894
1995
  """
1895
- a, b = map(atleast_1d, (a, b))
1996
+ xp = array_namespace(a, b)
1997
+ a, b = map(xp.asarray, (a, b))
1998
+ a, b = xp_promote(a, b, force_floating=True, xp=xp)
1999
+ a = xpx.atleast_nd(a, ndim=1, xp=xp)
2000
+ b = xpx.atleast_nd(b, ndim=1, xp=xp)
2001
+
1896
2002
  try:
1897
2003
  wo = float(wo)
1898
2004
  except TypeError:
1899
2005
  wo = float(wo[0])
1900
- d = len(a)
1901
- n = len(b)
2006
+ d = a.shape[0]
2007
+ n = b.shape[0]
1902
2008
  M = max((d, n))
1903
- pwo = pow(wo, np.arange(M - 1, -1, -1))
2009
+ pwo = wo ** xp.arange(M - 1, -1, -1, dtype=xp.float64)
1904
2010
  start1 = max((n - d, 0))
1905
2011
  start2 = max((d - n, 0))
1906
2012
  b = b * pwo[start1] / pwo[start2:]
@@ -1908,6 +2014,28 @@ def lp2lp(b, a, wo=1.0):
1908
2014
  return normalize(b, a)
1909
2015
 
1910
2016
 
2017
+ def _resize(a, new_shape, xp):
2018
+ # https://github.com/numpy/numpy/blob/v2.2.4/numpy/_core/fromnumeric.py#L1535
2019
+ a = xp.reshape(a, (-1,))
2020
+
2021
+ new_size = 1
2022
+ for dim_length in new_shape:
2023
+ new_size *= dim_length
2024
+ if dim_length < 0:
2025
+ raise ValueError(
2026
+ 'all elements of `new_shape` must be non-negative'
2027
+ )
2028
+
2029
+ if xp_size(a) == 0 or new_size == 0:
2030
+ # First case must zero fill. The second would have repeats == 0.
2031
+ return xp.zeros_like(a, shape=new_shape)
2032
+
2033
+ repeats = -(-new_size // xp_size(a)) # ceil division
2034
+ a = xp.concat((a,) * repeats)[:new_size]
2035
+
2036
+ return xp.reshape(a, new_shape)
2037
+
2038
+
1911
2039
  def lp2hp(b, a, wo=1.0):
1912
2040
  r"""
1913
2041
  Transform a lowpass filter prototype to a highpass filter.
@@ -1966,27 +2094,33 @@ def lp2hp(b, a, wo=1.0):
1966
2094
  >>> plt.legend()
1967
2095
 
1968
2096
  """
1969
- a, b = map(atleast_1d, (a, b))
2097
+ xp = array_namespace(a, b)
2098
+
2099
+ a, b = map(xp.asarray, (a, b))
2100
+ a, b = xp_promote(a, b, force_floating=True, xp=xp)
2101
+ a = xpx.atleast_nd(a, ndim=1, xp=xp)
2102
+ b = xpx.atleast_nd(b, ndim=1, xp=xp)
2103
+
1970
2104
  try:
1971
2105
  wo = float(wo)
1972
2106
  except TypeError:
1973
2107
  wo = float(wo[0])
1974
- d = len(a)
1975
- n = len(b)
2108
+ d = a.shape[0]
2109
+ n = b.shape[0]
1976
2110
  if wo != 1:
1977
- pwo = pow(wo, np.arange(max((d, n))))
2111
+ pwo = wo ** xp.arange(max((d, n)), dtype=b.dtype)
1978
2112
  else:
1979
- pwo = np.ones(max((d, n)), b.dtype.char)
2113
+ pwo = xp.ones(max((d, n)), dtype=b.dtype)
1980
2114
  if d >= n:
1981
- outa = a[::-1] * pwo
1982
- outb = resize(b, (d,))
2115
+ outa = xp.flip(a) * pwo
2116
+ outb = _resize(b, (d,), xp=xp)
1983
2117
  outb[n:] = 0.0
1984
- outb[:n] = b[::-1] * pwo[:n]
2118
+ outb[:n] = xp.flip(b) * pwo[:n]
1985
2119
  else:
1986
- outb = b[::-1] * pwo
1987
- outa = resize(a, (n,))
2120
+ outb = xp.flip(b) * pwo
2121
+ outa = _resize(a, (n,), xp=xp)
1988
2122
  outa[d:] = 0.0
1989
- outa[:d] = a[::-1] * pwo[:d]
2123
+ outa[:d] = xp.flip(a) * pwo[:d]
1990
2124
 
1991
2125
  return normalize(outb, outa)
1992
2126
 
@@ -2051,16 +2185,20 @@ def lp2bp(b, a, wo=1.0, bw=1.0):
2051
2185
  >>> plt.ylabel('Amplitude [dB]')
2052
2186
  >>> plt.legend()
2053
2187
  """
2188
+ xp = array_namespace(a, b)
2054
2189
 
2055
- a, b = map(atleast_1d, (a, b))
2056
- D = len(a) - 1
2057
- N = len(b) - 1
2058
- artype = mintypecode((a, b))
2190
+ a, b = map(xp.asarray, (a, b))
2191
+ a, b = xp_promote(a, b, force_floating=True, xp=xp)
2192
+ a = xpx.atleast_nd(a, ndim=1, xp=xp)
2193
+ b = xpx.atleast_nd(b, ndim=1, xp=xp)
2194
+
2195
+ D = a.shape[0] - 1
2196
+ N = b.shape[0] - 1
2059
2197
  ma = max([N, D])
2060
2198
  Np = N + ma
2061
2199
  Dp = D + ma
2062
- bprime = np.empty(Np + 1, artype)
2063
- aprime = np.empty(Dp + 1, artype)
2200
+ bprime = xp.empty(Np + 1, dtype=b.dtype)
2201
+ aprime = xp.empty(Dp + 1, dtype=a.dtype)
2064
2202
  wosq = wo * wo
2065
2203
  for j in range(Np + 1):
2066
2204
  val = 0.0
@@ -2139,15 +2277,20 @@ def lp2bs(b, a, wo=1.0, bw=1.0):
2139
2277
  >>> plt.ylabel('Amplitude [dB]')
2140
2278
  >>> plt.legend()
2141
2279
  """
2142
- a, b = map(atleast_1d, (a, b))
2143
- D = len(a) - 1
2144
- N = len(b) - 1
2145
- artype = mintypecode((a, b))
2280
+ xp = array_namespace(a, b)
2281
+
2282
+ a, b = map(xp.asarray, (a, b))
2283
+ a, b = xp_promote(a, b, force_floating=True, xp=xp)
2284
+ a = xpx.atleast_nd(a, ndim=1, xp=xp)
2285
+ b = xpx.atleast_nd(b, ndim=1, xp=xp)
2286
+
2287
+ D = a.shape[0] - 1
2288
+ N = b.shape[0] - 1
2146
2289
  M = max([N, D])
2147
2290
  Np = M + M
2148
2291
  Dp = M + M
2149
- bprime = np.empty(Np + 1, artype)
2150
- aprime = np.empty(Dp + 1, artype)
2292
+ bprime = xp.empty(Np + 1, dtype=b.dtype)
2293
+ aprime = xp.empty(Dp + 1, dtype=a.dtype)
2151
2294
  wosq = wo * wo
2152
2295
  for j in range(Np + 1):
2153
2296
  val = 0.0
@@ -2170,88 +2313,141 @@ def lp2bs(b, a, wo=1.0, bw=1.0):
2170
2313
 
2171
2314
 
2172
2315
  def bilinear(b, a, fs=1.0):
2173
- r"""
2174
- Return a digital IIR filter from an analog one using a bilinear transform.
2175
-
2176
- Transform a set of poles and zeros from the analog s-plane to the digital
2177
- z-plane using Tustin's method, which substitutes ``2*fs*(z-1) / (z+1)`` for
2178
- ``s``, maintaining the shape of the frequency response.
2316
+ r"""Calculate a digital IIR filter from an analog transfer function by utilizing
2317
+ the bilinear transform.
2179
2318
 
2180
2319
  Parameters
2181
2320
  ----------
2182
2321
  b : array_like
2183
- Numerator of the analog filter transfer function.
2322
+ Coefficients of the numerator polynomial of the analog transfer function in
2323
+ form of a complex- or real-valued 1d array.
2184
2324
  a : array_like
2185
- Denominator of the analog filter transfer function.
2325
+ Coefficients of the denominator polynomial of the analog transfer function in
2326
+ form of a complex- or real-valued 1d array.
2186
2327
  fs : float
2187
- Sample rate, as ordinary frequency (e.g., hertz). No prewarping is
2328
+ Sample rate, as ordinary frequency (e.g., hertz). No pre-warping is
2188
2329
  done in this function.
2189
2330
 
2190
2331
  Returns
2191
2332
  -------
2192
- b : ndarray
2193
- Numerator of the transformed digital filter transfer function.
2194
- a : ndarray
2195
- Denominator of the transformed digital filter transfer function.
2333
+ beta : ndarray
2334
+ Coefficients of the numerator polynomial of the digital transfer function in
2335
+ form of a complex- or real-valued 1d array.
2336
+ alpha : ndarray
2337
+ Coefficients of the denominator polynomial of the digital transfer function in
2338
+ form of a complex- or real-valued 1d array.
2339
+
2340
+ Notes
2341
+ -----
2342
+ The parameters :math:`b = [b_0, \ldots, b_Q]` and :math:`a = [a_0, \ldots, a_P]`
2343
+ are 1d arrays of length :math:`Q+1` and :math:`P+1`. They define the analog
2344
+ transfer function
2345
+
2346
+ .. math::
2347
+
2348
+ H_a(s) = \frac{b_0 s^Q + b_1 s^{Q-1} + \cdots + b_Q}{
2349
+ a_0 s^P + a_1 s^{P-1} + \cdots + a_P}\ .
2350
+
2351
+ The bilinear transform [1]_ is applied by substituting
2352
+
2353
+ .. math::
2354
+
2355
+ s = \kappa \frac{z-1}{z+1}\ , \qquad \kappa := 2 f_s\ ,
2356
+
2357
+ into :math:`H_a(s)`, with :math:`f_s` being the sampling rate.
2358
+ This results in the digital transfer function in the :math:`z`-domain
2359
+
2360
+ .. math::
2361
+
2362
+ H_d(z) = \frac{b_0 \left(\kappa \frac{z-1}{z+1}\right)^Q +
2363
+ b_1 \left(\kappa \frac{z-1}{z+1}\right)^{Q-1} +
2364
+ \cdots + b_Q}{
2365
+ a_0 \left(\kappa \frac{z-1}{z+1}\right)^P +
2366
+ a_1 \left(\kappa \frac{z-1}{z+1}\right)^{P-1} +
2367
+ \cdots + a_P}\ .
2368
+
2369
+ This expression can be simplified by multiplying numerator and denominator by
2370
+ :math:`(z+1)^N`, with :math:`N=\max(P, Q)`. This allows :math:`H_d(z)` to be
2371
+ reformulated as
2372
+
2373
+ .. math::
2374
+
2375
+ & & \frac{b_0 \big(\kappa (z-1)\big)^Q (z+1)^{N-Q} +
2376
+ b_1 \big(\kappa (z-1)\big)^{Q-1} (z+1)^{N-Q+1} +
2377
+ \cdots + b_Q(z+1)^N}{
2378
+ a_0 \big(\kappa (z-1)\big)^P (z+1)^{N-P} +
2379
+ a_1 \big(\kappa (z-1)\big)^{P-1} (z+1)^{N-P+1} +
2380
+ \cdots + a_P(z+1)^N}\\
2381
+ &=:& \frac{\beta_0 + \beta_1 z^{-1} + \cdots + \beta_N z^{-N}}{
2382
+ \alpha_0 + \alpha_1 z^{-1} + \cdots + \alpha_N z^{-N}}\ .
2383
+
2384
+
2385
+ This is the equation implemented to perform the bilinear transform. Note that for
2386
+ large :math:`f_s`, :math:`\kappa^Q` or :math:`\kappa^P` can cause a numeric
2387
+ overflow for sufficiently large :math:`P` or :math:`Q`.
2388
+
2389
+ References
2390
+ ----------
2391
+ .. [1] "Bilinear Transform", Wikipedia,
2392
+ https://en.wikipedia.org/wiki/Bilinear_transform
2196
2393
 
2197
2394
  See Also
2198
2395
  --------
2199
- lp2lp, lp2hp, lp2bp, lp2bs
2200
- bilinear_zpk
2396
+ lp2lp, lp2hp, lp2bp, lp2bs, bilinear_zpk
2201
2397
 
2202
2398
  Examples
2203
2399
  --------
2400
+ The following example shows the frequency response of an analog bandpass filter and
2401
+ the corresponding digital filter derived by utilitzing the bilinear transform:
2402
+
2204
2403
  >>> from scipy import signal
2205
2404
  >>> import matplotlib.pyplot as plt
2206
2405
  >>> import numpy as np
2406
+ ...
2407
+ >>> fs = 100 # sampling frequency
2408
+ >>> om_c = 2 * np.pi * np.array([7, 13]) # corner frequencies
2409
+ >>> bb_s, aa_s = signal.butter(4, om_c, btype='bandpass', analog=True, output='ba')
2410
+ >>> bb_z, aa_z = signal.bilinear(bb_s, aa_s, fs)
2411
+ ...
2412
+ >>> w_z, H_z = signal.freqz(bb_z, aa_z) # frequency response of digitial filter
2413
+ >>> w_s, H_s = signal.freqs(bb_s, aa_s, worN=w_z*fs) # analog filter response
2414
+ ...
2415
+ >>> f_z, f_s = w_z * fs / (2*np.pi), w_s / (2*np.pi)
2416
+ >>> Hz_dB, Hs_dB = (20*np.log10(np.abs(H_).clip(1e-10)) for H_ in (H_z, H_s))
2417
+ >>> fg0, ax0 = plt.subplots()
2418
+ >>> ax0.set_title("Frequency Response of 4-th order Bandpass Filter")
2419
+ >>> ax0.set(xlabel='Frequency $f$ in Hertz', ylabel='Magnitude in dB',
2420
+ ... xlim=[f_z[1], fs/2], ylim=[-200, 2])
2421
+ >>> ax0.semilogx(f_z, Hz_dB, alpha=.5, label=r'$|H_z(e^{j 2 \pi f})|$')
2422
+ >>> ax0.semilogx(f_s, Hs_dB, alpha=.5, label=r'$|H_s(j 2 \pi f)|$')
2423
+ >>> ax0.legend()
2424
+ >>> ax0.grid(which='both', axis='x')
2425
+ >>> ax0.grid(which='major', axis='y')
2426
+ >>> plt.show()
2207
2427
 
2208
- >>> fs = 100
2209
- >>> bf = 2 * np.pi * np.array([7, 13])
2210
- >>> filts = signal.lti(*signal.butter(4, bf, btype='bandpass',
2211
- ... analog=True))
2212
- >>> filtz = signal.lti(*signal.bilinear(filts.num, filts.den, fs))
2213
- >>> wz, hz = signal.freqz(filtz.num, filtz.den)
2214
- >>> ws, hs = signal.freqs(filts.num, filts.den, worN=fs*wz)
2215
-
2216
- >>> plt.semilogx(wz*fs/(2*np.pi), 20*np.log10(np.abs(hz).clip(1e-15)),
2217
- ... label=r'$|H_z(e^{j \omega})|$')
2218
- >>> plt.semilogx(wz*fs/(2*np.pi), 20*np.log10(np.abs(hs).clip(1e-15)),
2219
- ... label=r'$|H(j \omega)|$')
2220
- >>> plt.legend()
2221
- >>> plt.xlabel('Frequency [Hz]')
2222
- >>> plt.ylabel('Amplitude [dB]')
2223
- >>> plt.grid(True)
2428
+ The difference in the higher frequencies shown in the plot is caused by an effect
2429
+ called "frequency warping". [1]_ describes a method called "pre-warping" to
2430
+ reduce those deviations.
2224
2431
  """
2432
+ b, a = np.atleast_1d(b), np.atleast_1d(a) # convert scalars, if needed
2433
+ if not a.ndim == 1:
2434
+ raise ValueError(f"Parameter a is not a 1d array since {a.shape=}")
2435
+ if not b.ndim == 1:
2436
+ raise ValueError(f"Parameter b is not a 1d array since {b.shape=}")
2437
+ b, a = np.trim_zeros(b, 'f'), np.trim_zeros(a, 'f') # remove leading zeros
2225
2438
  fs = _validate_fs(fs, allow_none=False)
2226
- a, b = map(atleast_1d, (a, b))
2227
- D = len(a) - 1
2228
- N = len(b) - 1
2229
- artype = float
2230
- M = max([N, D])
2231
- Np = M
2232
- Dp = M
2233
- bprime = np.empty(Np + 1, artype)
2234
- aprime = np.empty(Dp + 1, artype)
2235
- for j in range(Np + 1):
2236
- val = 0.0
2237
- for i in range(N + 1):
2238
- for k in range(i + 1):
2239
- for l in range(M - i + 1):
2240
- if k + l == j:
2241
- val += (comb(i, k) * comb(M - i, l) * b[N - i] *
2242
- pow(2 * fs, i) * (-1) ** k)
2243
- bprime[j] = real(val)
2244
- for j in range(Dp + 1):
2245
- val = 0.0
2246
- for i in range(D + 1):
2247
- for k in range(i + 1):
2248
- for l in range(M - i + 1):
2249
- if k + l == j:
2250
- val += (comb(i, k) * comb(M - i, l) * a[D - i] *
2251
- pow(2 * fs, i) * (-1) ** k)
2252
- aprime[j] = real(val)
2253
2439
 
2254
- return normalize(bprime, aprime)
2440
+ # Splitting the factor fs*2 between numerator and denominator reduces the chance of
2441
+ # numeric overflow for large fs and large N:
2442
+ fac = np.sqrt(fs*2)
2443
+ zp1 = np.polynomial.Polynomial((+1, 1)) / fac # Polynomial (z + 1) / fac
2444
+ zm1 = np.polynomial.Polynomial((-1, 1)) * fac # Polynomial (z - 1) * fac
2445
+ # Note that NumPy's Polynomial coefficient order is backward compared to a and b.
2446
+
2447
+ N = max(len(a), len(b)) - 1
2448
+ numerator = sum(b_ * zp1**(N-q) * zm1**q for q, b_ in enumerate(b[::-1]))
2449
+ denominator = sum(a_ * zp1**(N-p) * zm1**p for p, a_ in enumerate(a[::-1]))
2450
+ return normalize(numerator.coef[::-1], denominator.coef[::-1])
2255
2451
 
2256
2452
 
2257
2453
  def _validate_gpass_gstop(gpass, gstop):
@@ -2685,7 +2881,7 @@ def _relative_degree(z, p):
2685
2881
  """
2686
2882
  Return relative degree of transfer function from zeros and poles
2687
2883
  """
2688
- degree = len(p) - len(z)
2884
+ degree = p.shape[0] - z.shape[0]
2689
2885
  if degree < 0:
2690
2886
  raise ValueError("Improper transfer function. "
2691
2887
  "Must have at least as many poles as zeros.")
@@ -2755,8 +2951,11 @@ def bilinear_zpk(z, p, k, fs):
2755
2951
  >>> plt.ylabel('Amplitude [dB]')
2756
2952
  >>> plt.grid(True)
2757
2953
  """
2758
- z = atleast_1d(z)
2759
- p = atleast_1d(p)
2954
+ xp = array_namespace(z, p)
2955
+
2956
+ z, p = map(xp.asarray, (z, p))
2957
+ z = xpx.atleast_nd(z, ndim=1, xp=xp)
2958
+ p = xpx.atleast_nd(p, ndim=1, xp=xp)
2760
2959
 
2761
2960
  fs = _validate_fs(fs, allow_none=False)
2762
2961
 
@@ -2769,10 +2968,10 @@ def bilinear_zpk(z, p, k, fs):
2769
2968
  p_z = (fs2 + p) / (fs2 - p)
2770
2969
 
2771
2970
  # Any zeros that were at infinity get moved to the Nyquist frequency
2772
- z_z = append(z_z, -ones(degree))
2971
+ z_z = xp.concat((z_z, -xp.ones(degree)))
2773
2972
 
2774
2973
  # Compensate for gain change
2775
- k_z = k * real(prod(fs2 - z) / prod(fs2 - p))
2974
+ k_z = k * xp.real(xp.prod(fs2 - z) / xp.prod(fs2 - p))
2776
2975
 
2777
2976
  return z_z, p_z, k_z
2778
2977
 
@@ -2832,8 +3031,12 @@ def lp2lp_zpk(z, p, k, wo=1.0):
2832
3031
  >>> lp2lp_zpk(z, p, k, wo)
2833
3032
  ( array([2.8, 0.8]), array([2. , 5.2]), 0.8)
2834
3033
  """
2835
- z = atleast_1d(z)
2836
- p = atleast_1d(p)
3034
+ xp = array_namespace(z, p)
3035
+
3036
+ z, p = map(xp.asarray, (z, p))
3037
+ z = xpx.atleast_nd(z, ndim=1, xp=xp)
3038
+ p = xpx.atleast_nd(p, ndim=1, xp=xp)
3039
+
2837
3040
  wo = float(wo) # Avoid int wraparound
2838
3041
 
2839
3042
  degree = _relative_degree(z, p)
@@ -2909,8 +3112,13 @@ def lp2hp_zpk(z, p, k, wo=1.0):
2909
3112
  array([-0.6 , -0.15]),
2910
3113
  8.5)
2911
3114
  """
2912
- z = atleast_1d(z)
2913
- p = atleast_1d(p)
3115
+ xp = array_namespace(z, p)
3116
+
3117
+ z, p = map(xp.asarray, (z, p))
3118
+ # XXX: no xp_promote here since that breaks TestButter
3119
+ z = xpx.atleast_nd(z, ndim=1, xp=xp)
3120
+ p = xpx.atleast_nd(p, ndim=1, xp=xp)
3121
+
2914
3122
  wo = float(wo)
2915
3123
 
2916
3124
  degree = _relative_degree(z, p)
@@ -2921,10 +3129,10 @@ def lp2hp_zpk(z, p, k, wo=1.0):
2921
3129
  p_hp = wo / p
2922
3130
 
2923
3131
  # If lowpass had zeros at infinity, inverting moves them to origin.
2924
- z_hp = append(z_hp, zeros(degree))
3132
+ z_hp = xp.concat((z_hp, xp.zeros(degree)))
2925
3133
 
2926
3134
  # Cancel out gain change caused by inversion
2927
- k_hp = k * real(prod(-z) / prod(-p))
3135
+ k_hp = k * xp.real(xp.prod(-z) / xp.prod(-p))
2928
3136
 
2929
3137
  return z_hp, p_hp, k_hp
2930
3138
 
@@ -2995,8 +3203,13 @@ def lp2bp_zpk(z, p, k, wo=1.0, bw=1.0):
2995
3203
  array([1.04996339e+02+0.j, -1.60167736e-03+0.j, 3.66108003e-03+0.j,
2996
3204
  -2.39998398e+02+0.j]), 0.8)
2997
3205
  """
2998
- z = atleast_1d(z)
2999
- p = atleast_1d(p)
3206
+ xp = array_namespace(z, p)
3207
+
3208
+ z, p = map(xp.asarray, (z, p))
3209
+ z, p = xp_promote(z, p, force_floating=True, xp=xp)
3210
+ z = xpx.atleast_nd(z, ndim=1, xp=xp)
3211
+ p = xpx.atleast_nd(p, ndim=1, xp=xp)
3212
+
3000
3213
  wo = float(wo)
3001
3214
  bw = float(bw)
3002
3215
 
@@ -3007,17 +3220,17 @@ def lp2bp_zpk(z, p, k, wo=1.0, bw=1.0):
3007
3220
  p_lp = p * bw/2
3008
3221
 
3009
3222
  # Square root needs to produce complex result, not NaN
3010
- z_lp = z_lp.astype(complex)
3011
- p_lp = p_lp.astype(complex)
3223
+ z_lp = xp.astype(z_lp, xp.complex128)
3224
+ p_lp = xp.astype(p_lp, xp.complex128)
3012
3225
 
3013
3226
  # Duplicate poles and zeros and shift from baseband to +wo and -wo
3014
- z_bp = concatenate((z_lp + sqrt(z_lp**2 - wo**2),
3015
- z_lp - sqrt(z_lp**2 - wo**2)))
3016
- p_bp = concatenate((p_lp + sqrt(p_lp**2 - wo**2),
3017
- p_lp - sqrt(p_lp**2 - wo**2)))
3227
+ z_bp = xp.concat((z_lp + xp.sqrt(z_lp**2 - wo**2),
3228
+ z_lp - xp.sqrt(z_lp**2 - wo**2)))
3229
+ p_bp = xp.concat((p_lp + xp.sqrt(p_lp**2 - wo**2),
3230
+ p_lp - xp.sqrt(p_lp**2 - wo**2)))
3018
3231
 
3019
3232
  # Move degree zeros to origin, leaving degree zeros at infinity for BPF
3020
- z_bp = append(z_bp, zeros(degree))
3233
+ z_bp = xp.concat((z_bp, xp.zeros(degree)))
3021
3234
 
3022
3235
  # Cancel out gain change from frequency scaling
3023
3236
  k_bp = k * bw**degree
@@ -3090,8 +3303,13 @@ def lp2bs_zpk(z, p, k, wo=1.0, bw=1.0):
3090
3303
  array([14.2681928 +0.j, -0.02506281+0.j, 0.01752149+0.j, -9.97493719+0.j]),
3091
3304
  -12.857142857142858)
3092
3305
  """
3093
- z = atleast_1d(z)
3094
- p = atleast_1d(p)
3306
+ xp = array_namespace(z, p)
3307
+
3308
+ z, p = map(xp.asarray, (z, p))
3309
+ z, p = xp_promote(z, p, force_floating=True, xp=xp)
3310
+ z = xpx.atleast_nd(z, ndim=1, xp=xp)
3311
+ p = xpx.atleast_nd(p, ndim=1, xp=xp)
3312
+
3095
3313
  wo = float(wo)
3096
3314
  bw = float(bw)
3097
3315
 
@@ -3102,21 +3320,21 @@ def lp2bs_zpk(z, p, k, wo=1.0, bw=1.0):
3102
3320
  p_hp = (bw/2) / p
3103
3321
 
3104
3322
  # Square root needs to produce complex result, not NaN
3105
- z_hp = z_hp.astype(complex)
3106
- p_hp = p_hp.astype(complex)
3323
+ z_hp = xp.astype(z_hp, xp.complex128)
3324
+ p_hp = xp.astype(p_hp, xp.complex128)
3107
3325
 
3108
3326
  # Duplicate poles and zeros and shift from baseband to +wo and -wo
3109
- z_bs = concatenate((z_hp + sqrt(z_hp**2 - wo**2),
3110
- z_hp - sqrt(z_hp**2 - wo**2)))
3111
- p_bs = concatenate((p_hp + sqrt(p_hp**2 - wo**2),
3112
- p_hp - sqrt(p_hp**2 - wo**2)))
3327
+ z_bs = xp.concat((z_hp + xp.sqrt(z_hp**2 - wo**2),
3328
+ z_hp - xp.sqrt(z_hp**2 - wo**2)))
3329
+ p_bs = xp.concat((p_hp + xp.sqrt(p_hp**2 - wo**2),
3330
+ p_hp - xp.sqrt(p_hp**2 - wo**2)))
3113
3331
 
3114
3332
  # Move any zeros that were at infinity to the center of the stopband
3115
- z_bs = append(z_bs, full(degree, +1j*wo))
3116
- z_bs = append(z_bs, full(degree, -1j*wo))
3333
+ z_bs = xp.concat((z_bs, xp.full(degree, +1j*wo)))
3334
+ z_bs = xp.concat((z_bs, xp.full(degree, -1j*wo)))
3117
3335
 
3118
3336
  # Cancel out gain change caused by inversion
3119
- k_bs = k * real(prod(-z) / prod(-p))
3337
+ k_bs = k * xp.real(xp.prod(-z) / xp.prod(-p))
3120
3338
 
3121
3339
  return z_bs, p_bs, k_bs
3122
3340