scipy 1.15.3__cp312-cp312-macosx_14_0_arm64.whl → 1.16.0rc2__cp312-cp312-macosx_14_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 (628) hide show
  1. scipy/__config__.py +4 -4
  2. scipy/__init__.py +3 -6
  3. scipy/_cyutility.cpython-312-darwin.so +0 -0
  4. scipy/_lib/_array_api.py +486 -161
  5. scipy/_lib/_array_api_compat_vendor.py +9 -0
  6. scipy/_lib/_bunch.py +4 -0
  7. scipy/_lib/_ccallback_c.cpython-312-darwin.so +0 -0
  8. scipy/_lib/_docscrape.py +1 -1
  9. scipy/_lib/_elementwise_iterative_method.py +15 -26
  10. scipy/_lib/_sparse.py +41 -0
  11. scipy/_lib/_test_deprecation_call.cpython-312-darwin.so +0 -0
  12. scipy/_lib/_test_deprecation_def.cpython-312-darwin.so +0 -0
  13. scipy/_lib/_testutils.py +6 -2
  14. scipy/_lib/_util.py +222 -125
  15. scipy/_lib/array_api_compat/__init__.py +4 -4
  16. scipy/_lib/array_api_compat/_internal.py +19 -6
  17. scipy/_lib/array_api_compat/common/__init__.py +1 -1
  18. scipy/_lib/array_api_compat/common/_aliases.py +365 -193
  19. scipy/_lib/array_api_compat/common/_fft.py +94 -64
  20. scipy/_lib/array_api_compat/common/_helpers.py +413 -180
  21. scipy/_lib/array_api_compat/common/_linalg.py +116 -40
  22. scipy/_lib/array_api_compat/common/_typing.py +179 -10
  23. scipy/_lib/array_api_compat/cupy/__init__.py +1 -4
  24. scipy/_lib/array_api_compat/cupy/_aliases.py +61 -41
  25. scipy/_lib/array_api_compat/cupy/_info.py +16 -6
  26. scipy/_lib/array_api_compat/cupy/_typing.py +24 -39
  27. scipy/_lib/array_api_compat/dask/array/__init__.py +6 -3
  28. scipy/_lib/array_api_compat/dask/array/_aliases.py +267 -108
  29. scipy/_lib/array_api_compat/dask/array/_info.py +105 -34
  30. scipy/_lib/array_api_compat/dask/array/fft.py +5 -8
  31. scipy/_lib/array_api_compat/dask/array/linalg.py +21 -22
  32. scipy/_lib/array_api_compat/numpy/__init__.py +13 -15
  33. scipy/_lib/array_api_compat/numpy/_aliases.py +98 -49
  34. scipy/_lib/array_api_compat/numpy/_info.py +36 -16
  35. scipy/_lib/array_api_compat/numpy/_typing.py +27 -43
  36. scipy/_lib/array_api_compat/numpy/fft.py +11 -5
  37. scipy/_lib/array_api_compat/numpy/linalg.py +75 -22
  38. scipy/_lib/array_api_compat/torch/__init__.py +3 -5
  39. scipy/_lib/array_api_compat/torch/_aliases.py +262 -159
  40. scipy/_lib/array_api_compat/torch/_info.py +27 -16
  41. scipy/_lib/array_api_compat/torch/_typing.py +3 -0
  42. scipy/_lib/array_api_compat/torch/fft.py +17 -18
  43. scipy/_lib/array_api_compat/torch/linalg.py +16 -16
  44. scipy/_lib/array_api_extra/__init__.py +26 -3
  45. scipy/_lib/array_api_extra/_delegation.py +171 -0
  46. scipy/_lib/array_api_extra/_lib/__init__.py +1 -0
  47. scipy/_lib/array_api_extra/_lib/_at.py +463 -0
  48. scipy/_lib/array_api_extra/_lib/_backends.py +46 -0
  49. scipy/_lib/array_api_extra/_lib/_funcs.py +937 -0
  50. scipy/_lib/array_api_extra/_lib/_lazy.py +357 -0
  51. scipy/_lib/array_api_extra/_lib/_testing.py +278 -0
  52. scipy/_lib/array_api_extra/_lib/_utils/__init__.py +1 -0
  53. scipy/_lib/array_api_extra/_lib/_utils/_compat.py +74 -0
  54. scipy/_lib/array_api_extra/_lib/_utils/_compat.pyi +45 -0
  55. scipy/_lib/array_api_extra/_lib/_utils/_helpers.py +559 -0
  56. scipy/_lib/array_api_extra/_lib/_utils/_typing.py +10 -0
  57. scipy/_lib/array_api_extra/_lib/_utils/_typing.pyi +105 -0
  58. scipy/_lib/array_api_extra/testing.py +359 -0
  59. scipy/_lib/decorator.py +2 -2
  60. scipy/_lib/doccer.py +1 -7
  61. scipy/_lib/messagestream.cpython-312-darwin.so +0 -0
  62. scipy/_lib/pyprima/__init__.py +212 -0
  63. scipy/_lib/pyprima/cobyla/__init__.py +0 -0
  64. scipy/_lib/pyprima/cobyla/cobyla.py +559 -0
  65. scipy/_lib/pyprima/cobyla/cobylb.py +714 -0
  66. scipy/_lib/pyprima/cobyla/geometry.py +226 -0
  67. scipy/_lib/pyprima/cobyla/initialize.py +215 -0
  68. scipy/_lib/pyprima/cobyla/trustregion.py +492 -0
  69. scipy/_lib/pyprima/cobyla/update.py +289 -0
  70. scipy/_lib/pyprima/common/__init__.py +0 -0
  71. scipy/_lib/pyprima/common/_bounds.py +34 -0
  72. scipy/_lib/pyprima/common/_linear_constraints.py +46 -0
  73. scipy/_lib/pyprima/common/_nonlinear_constraints.py +54 -0
  74. scipy/_lib/pyprima/common/_project.py +173 -0
  75. scipy/_lib/pyprima/common/checkbreak.py +93 -0
  76. scipy/_lib/pyprima/common/consts.py +47 -0
  77. scipy/_lib/pyprima/common/evaluate.py +99 -0
  78. scipy/_lib/pyprima/common/history.py +38 -0
  79. scipy/_lib/pyprima/common/infos.py +30 -0
  80. scipy/_lib/pyprima/common/linalg.py +435 -0
  81. scipy/_lib/pyprima/common/message.py +290 -0
  82. scipy/_lib/pyprima/common/powalg.py +131 -0
  83. scipy/_lib/pyprima/common/preproc.py +277 -0
  84. scipy/_lib/pyprima/common/present.py +5 -0
  85. scipy/_lib/pyprima/common/ratio.py +54 -0
  86. scipy/_lib/pyprima/common/redrho.py +47 -0
  87. scipy/_lib/pyprima/common/selectx.py +296 -0
  88. scipy/_lib/tests/test__util.py +105 -121
  89. scipy/_lib/tests/test_array_api.py +166 -35
  90. scipy/_lib/tests/test_bunch.py +7 -0
  91. scipy/_lib/tests/test_ccallback.py +2 -10
  92. scipy/_lib/tests/test_public_api.py +13 -0
  93. scipy/cluster/_hierarchy.cpython-312-darwin.so +0 -0
  94. scipy/cluster/_optimal_leaf_ordering.cpython-312-darwin.so +0 -0
  95. scipy/cluster/_vq.cpython-312-darwin.so +0 -0
  96. scipy/cluster/hierarchy.py +393 -223
  97. scipy/cluster/tests/test_hierarchy.py +273 -335
  98. scipy/cluster/tests/test_vq.py +45 -61
  99. scipy/cluster/vq.py +39 -35
  100. scipy/conftest.py +263 -157
  101. scipy/constants/_constants.py +4 -1
  102. scipy/constants/tests/test_codata.py +2 -2
  103. scipy/constants/tests/test_constants.py +11 -18
  104. scipy/datasets/_download_all.py +15 -1
  105. scipy/datasets/_fetchers.py +7 -1
  106. scipy/datasets/_utils.py +1 -1
  107. scipy/differentiate/_differentiate.py +25 -25
  108. scipy/differentiate/tests/test_differentiate.py +24 -25
  109. scipy/fft/_basic.py +20 -0
  110. scipy/fft/_helper.py +3 -34
  111. scipy/fft/_pocketfft/helper.py +29 -1
  112. scipy/fft/_pocketfft/tests/test_basic.py +2 -4
  113. scipy/fft/_pocketfft/tests/test_real_transforms.py +4 -4
  114. scipy/fft/_realtransforms.py +13 -0
  115. scipy/fft/tests/test_basic.py +27 -25
  116. scipy/fft/tests/test_fftlog.py +16 -7
  117. scipy/fft/tests/test_helper.py +18 -34
  118. scipy/fft/tests/test_real_transforms.py +8 -10
  119. scipy/fftpack/convolve.cpython-312-darwin.so +0 -0
  120. scipy/fftpack/tests/test_basic.py +2 -4
  121. scipy/fftpack/tests/test_real_transforms.py +8 -9
  122. scipy/integrate/_bvp.py +9 -3
  123. scipy/integrate/_cubature.py +3 -2
  124. scipy/integrate/_dop.cpython-312-darwin.so +0 -0
  125. scipy/integrate/_lsoda.cpython-312-darwin.so +0 -0
  126. scipy/integrate/_ode.py +9 -2
  127. scipy/integrate/_odepack.cpython-312-darwin.so +0 -0
  128. scipy/integrate/_quad_vec.py +21 -29
  129. scipy/integrate/_quadpack.cpython-312-darwin.so +0 -0
  130. scipy/integrate/_quadpack_py.py +11 -7
  131. scipy/integrate/_quadrature.py +3 -3
  132. scipy/integrate/_rules/_base.py +2 -2
  133. scipy/integrate/_tanhsinh.py +48 -47
  134. scipy/integrate/_test_odeint_banded.cpython-312-darwin.so +0 -0
  135. scipy/integrate/_vode.cpython-312-darwin.so +0 -0
  136. scipy/integrate/tests/test__quad_vec.py +0 -6
  137. scipy/integrate/tests/test_banded_ode_solvers.py +85 -0
  138. scipy/integrate/tests/test_cubature.py +21 -35
  139. scipy/integrate/tests/test_quadrature.py +6 -8
  140. scipy/integrate/tests/test_tanhsinh.py +56 -48
  141. scipy/interpolate/__init__.py +70 -58
  142. scipy/interpolate/_bary_rational.py +22 -22
  143. scipy/interpolate/_bsplines.py +119 -66
  144. scipy/interpolate/_cubic.py +65 -50
  145. scipy/interpolate/_dfitpack.cpython-312-darwin.so +0 -0
  146. scipy/interpolate/_dierckx.cpython-312-darwin.so +0 -0
  147. scipy/interpolate/_fitpack.cpython-312-darwin.so +0 -0
  148. scipy/interpolate/_fitpack2.py +9 -6
  149. scipy/interpolate/_fitpack_impl.py +32 -26
  150. scipy/interpolate/_fitpack_repro.py +23 -19
  151. scipy/interpolate/_interpnd.cpython-312-darwin.so +0 -0
  152. scipy/interpolate/_interpolate.py +30 -12
  153. scipy/interpolate/_ndbspline.py +13 -18
  154. scipy/interpolate/_ndgriddata.py +5 -8
  155. scipy/interpolate/_polyint.py +95 -31
  156. scipy/interpolate/_ppoly.cpython-312-darwin.so +0 -0
  157. scipy/interpolate/_rbf.py +2 -2
  158. scipy/interpolate/_rbfinterp.py +1 -1
  159. scipy/interpolate/_rbfinterp_pythran.cpython-312-darwin.so +0 -0
  160. scipy/interpolate/_rgi.py +31 -26
  161. scipy/interpolate/_rgi_cython.cpython-312-darwin.so +0 -0
  162. scipy/interpolate/dfitpack.py +0 -20
  163. scipy/interpolate/interpnd.py +1 -2
  164. scipy/interpolate/tests/test_bary_rational.py +2 -2
  165. scipy/interpolate/tests/test_bsplines.py +97 -1
  166. scipy/interpolate/tests/test_fitpack2.py +39 -1
  167. scipy/interpolate/tests/test_interpnd.py +32 -20
  168. scipy/interpolate/tests/test_interpolate.py +48 -4
  169. scipy/interpolate/tests/test_rgi.py +2 -1
  170. scipy/io/_fast_matrix_market/__init__.py +2 -0
  171. scipy/io/_harwell_boeing/_fortran_format_parser.py +19 -16
  172. scipy/io/_harwell_boeing/hb.py +7 -11
  173. scipy/io/_idl.py +5 -7
  174. scipy/io/_netcdf.py +15 -5
  175. scipy/io/_test_fortran.cpython-312-darwin.so +0 -0
  176. scipy/io/arff/tests/test_arffread.py +3 -3
  177. scipy/io/matlab/__init__.py +5 -3
  178. scipy/io/matlab/_mio.py +4 -1
  179. scipy/io/matlab/_mio5.py +19 -13
  180. scipy/io/matlab/_mio5_utils.cpython-312-darwin.so +0 -0
  181. scipy/io/matlab/_mio_utils.cpython-312-darwin.so +0 -0
  182. scipy/io/matlab/_miobase.py +4 -1
  183. scipy/io/matlab/_streams.cpython-312-darwin.so +0 -0
  184. scipy/io/matlab/tests/test_mio.py +46 -18
  185. scipy/io/matlab/tests/test_mio_funcs.py +1 -1
  186. scipy/io/tests/test_mmio.py +7 -1
  187. scipy/io/tests/test_wavfile.py +41 -0
  188. scipy/io/wavfile.py +57 -10
  189. scipy/linalg/_basic.py +113 -86
  190. scipy/linalg/_cythonized_array_utils.cpython-312-darwin.so +0 -0
  191. scipy/linalg/_decomp.py +22 -9
  192. scipy/linalg/_decomp_cholesky.py +28 -13
  193. scipy/linalg/_decomp_cossin.py +45 -30
  194. scipy/linalg/_decomp_interpolative.cpython-312-darwin.so +0 -0
  195. scipy/linalg/_decomp_ldl.py +4 -1
  196. scipy/linalg/_decomp_lu.py +18 -6
  197. scipy/linalg/_decomp_lu_cython.cpython-312-darwin.so +0 -0
  198. scipy/linalg/_decomp_polar.py +2 -0
  199. scipy/linalg/_decomp_qr.py +6 -2
  200. scipy/linalg/_decomp_qz.py +3 -0
  201. scipy/linalg/_decomp_schur.py +3 -1
  202. scipy/linalg/_decomp_svd.py +13 -2
  203. scipy/linalg/_decomp_update.cpython-312-darwin.so +0 -0
  204. scipy/linalg/_expm_frechet.py +4 -0
  205. scipy/linalg/_fblas.cpython-312-darwin.so +0 -0
  206. scipy/linalg/_flapack.cpython-312-darwin.so +0 -0
  207. scipy/linalg/_linalg_pythran.cpython-312-darwin.so +0 -0
  208. scipy/linalg/_matfuncs.py +187 -4
  209. scipy/linalg/_matfuncs_expm.cpython-312-darwin.so +0 -0
  210. scipy/linalg/_matfuncs_schur_sqrtm.cpython-312-darwin.so +0 -0
  211. scipy/linalg/_matfuncs_sqrtm.py +1 -99
  212. scipy/linalg/_matfuncs_sqrtm_triu.cpython-312-darwin.so +0 -0
  213. scipy/linalg/_procrustes.py +2 -0
  214. scipy/linalg/_sketches.py +17 -6
  215. scipy/linalg/_solve_toeplitz.cpython-312-darwin.so +0 -0
  216. scipy/linalg/_solvers.py +7 -2
  217. scipy/linalg/_special_matrices.py +26 -36
  218. scipy/linalg/cython_blas.cpython-312-darwin.so +0 -0
  219. scipy/linalg/cython_lapack.cpython-312-darwin.so +0 -0
  220. scipy/linalg/lapack.py +22 -2
  221. scipy/linalg/tests/_cython_examples/meson.build +7 -0
  222. scipy/linalg/tests/test_basic.py +31 -16
  223. scipy/linalg/tests/test_batch.py +588 -0
  224. scipy/linalg/tests/test_cythonized_array_utils.py +0 -2
  225. scipy/linalg/tests/test_decomp.py +40 -3
  226. scipy/linalg/tests/test_decomp_cossin.py +14 -0
  227. scipy/linalg/tests/test_decomp_ldl.py +1 -1
  228. scipy/linalg/tests/test_lapack.py +115 -7
  229. scipy/linalg/tests/test_matfuncs.py +157 -102
  230. scipy/linalg/tests/test_procrustes.py +0 -7
  231. scipy/linalg/tests/test_solve_toeplitz.py +1 -1
  232. scipy/linalg/tests/test_special_matrices.py +1 -5
  233. scipy/ndimage/__init__.py +1 -0
  234. scipy/ndimage/_cytest.cpython-312-darwin.so +0 -0
  235. scipy/ndimage/_delegators.py +8 -2
  236. scipy/ndimage/_filters.py +453 -5
  237. scipy/ndimage/_interpolation.py +36 -6
  238. scipy/ndimage/_measurements.py +4 -2
  239. scipy/ndimage/_morphology.py +5 -0
  240. scipy/ndimage/_nd_image.cpython-312-darwin.so +0 -0
  241. scipy/ndimage/_ni_docstrings.py +5 -1
  242. scipy/ndimage/_ni_label.cpython-312-darwin.so +0 -0
  243. scipy/ndimage/_ni_support.py +1 -5
  244. scipy/ndimage/_rank_filter_1d.cpython-312-darwin.so +0 -0
  245. scipy/ndimage/_support_alternative_backends.py +18 -6
  246. scipy/ndimage/tests/test_filters.py +370 -259
  247. scipy/ndimage/tests/test_fourier.py +7 -9
  248. scipy/ndimage/tests/test_interpolation.py +68 -61
  249. scipy/ndimage/tests/test_measurements.py +18 -35
  250. scipy/ndimage/tests/test_morphology.py +143 -131
  251. scipy/ndimage/tests/test_splines.py +1 -3
  252. scipy/odr/__odrpack.cpython-312-darwin.so +0 -0
  253. scipy/optimize/_basinhopping.py +13 -7
  254. scipy/optimize/_bglu_dense.cpython-312-darwin.so +0 -0
  255. scipy/optimize/_bracket.py +17 -24
  256. scipy/optimize/_chandrupatla.py +9 -10
  257. scipy/optimize/_cobyla_py.py +104 -123
  258. scipy/optimize/_constraints.py +14 -10
  259. scipy/optimize/_differentiable_functions.py +371 -230
  260. scipy/optimize/_differentialevolution.py +4 -3
  261. scipy/optimize/_direct.cpython-312-darwin.so +0 -0
  262. scipy/optimize/_dual_annealing.py +1 -1
  263. scipy/optimize/_elementwise.py +1 -4
  264. scipy/optimize/_group_columns.cpython-312-darwin.so +0 -0
  265. scipy/optimize/_lbfgsb.cpython-312-darwin.so +0 -0
  266. scipy/optimize/_lbfgsb_py.py +57 -16
  267. scipy/optimize/_linprog_doc.py +2 -2
  268. scipy/optimize/_linprog_highs.py +2 -2
  269. scipy/optimize/_linprog_ip.py +25 -10
  270. scipy/optimize/_linprog_util.py +14 -16
  271. scipy/optimize/_lsap.cpython-312-darwin.so +0 -0
  272. scipy/optimize/_lsq/common.py +3 -3
  273. scipy/optimize/_lsq/dogbox.py +16 -2
  274. scipy/optimize/_lsq/givens_elimination.cpython-312-darwin.so +0 -0
  275. scipy/optimize/_lsq/least_squares.py +198 -126
  276. scipy/optimize/_lsq/lsq_linear.py +6 -6
  277. scipy/optimize/_lsq/trf.py +35 -8
  278. scipy/optimize/_milp.py +3 -1
  279. scipy/optimize/_minimize.py +105 -36
  280. scipy/optimize/_minpack.cpython-312-darwin.so +0 -0
  281. scipy/optimize/_minpack_py.py +21 -14
  282. scipy/optimize/_moduleTNC.cpython-312-darwin.so +0 -0
  283. scipy/optimize/_nnls.py +20 -21
  284. scipy/optimize/_nonlin.py +34 -3
  285. scipy/optimize/_numdiff.py +288 -110
  286. scipy/optimize/_optimize.py +86 -48
  287. scipy/optimize/_pava_pybind.cpython-312-darwin.so +0 -0
  288. scipy/optimize/_remove_redundancy.py +5 -5
  289. scipy/optimize/_root_scalar.py +1 -1
  290. scipy/optimize/_shgo.py +6 -0
  291. scipy/optimize/_shgo_lib/_complex.py +1 -1
  292. scipy/optimize/_slsqp_py.py +216 -124
  293. scipy/optimize/_slsqplib.cpython-312-darwin.so +0 -0
  294. scipy/optimize/_spectral.py +1 -1
  295. scipy/optimize/_tnc.py +8 -1
  296. scipy/optimize/_trlib/_trlib.cpython-312-darwin.so +0 -0
  297. scipy/optimize/_trustregion.py +20 -6
  298. scipy/optimize/_trustregion_constr/canonical_constraint.py +7 -7
  299. scipy/optimize/_trustregion_constr/equality_constrained_sqp.py +1 -1
  300. scipy/optimize/_trustregion_constr/minimize_trustregion_constr.py +11 -3
  301. scipy/optimize/_trustregion_constr/projections.py +12 -8
  302. scipy/optimize/_trustregion_constr/qp_subproblem.py +9 -9
  303. scipy/optimize/_trustregion_constr/tests/test_projections.py +7 -7
  304. scipy/optimize/_trustregion_constr/tests/test_qp_subproblem.py +77 -77
  305. scipy/optimize/_trustregion_constr/tr_interior_point.py +5 -5
  306. scipy/optimize/_trustregion_exact.py +0 -1
  307. scipy/optimize/_zeros.cpython-312-darwin.so +0 -0
  308. scipy/optimize/_zeros_py.py +97 -17
  309. scipy/optimize/cython_optimize/_zeros.cpython-312-darwin.so +0 -0
  310. scipy/optimize/slsqp.py +0 -1
  311. scipy/optimize/tests/test__basinhopping.py +1 -1
  312. scipy/optimize/tests/test__differential_evolution.py +4 -4
  313. scipy/optimize/tests/test__linprog_clean_inputs.py +5 -3
  314. scipy/optimize/tests/test__numdiff.py +66 -22
  315. scipy/optimize/tests/test__remove_redundancy.py +2 -2
  316. scipy/optimize/tests/test__shgo.py +9 -1
  317. scipy/optimize/tests/test_bracket.py +36 -46
  318. scipy/optimize/tests/test_chandrupatla.py +133 -135
  319. scipy/optimize/tests/test_cobyla.py +74 -45
  320. scipy/optimize/tests/test_constraints.py +1 -1
  321. scipy/optimize/tests/test_differentiable_functions.py +226 -6
  322. scipy/optimize/tests/test_lbfgsb_hessinv.py +22 -0
  323. scipy/optimize/tests/test_least_squares.py +125 -13
  324. scipy/optimize/tests/test_linear_assignment.py +3 -3
  325. scipy/optimize/tests/test_linprog.py +3 -3
  326. scipy/optimize/tests/test_lsq_linear.py +6 -6
  327. scipy/optimize/tests/test_minimize_constrained.py +2 -2
  328. scipy/optimize/tests/test_minpack.py +4 -4
  329. scipy/optimize/tests/test_nnls.py +43 -3
  330. scipy/optimize/tests/test_nonlin.py +36 -0
  331. scipy/optimize/tests/test_optimize.py +95 -17
  332. scipy/optimize/tests/test_slsqp.py +36 -4
  333. scipy/optimize/tests/test_zeros.py +34 -1
  334. scipy/signal/__init__.py +12 -23
  335. scipy/signal/_delegators.py +568 -0
  336. scipy/signal/_filter_design.py +459 -241
  337. scipy/signal/_fir_filter_design.py +262 -90
  338. scipy/signal/_lti_conversion.py +3 -2
  339. scipy/signal/_ltisys.py +118 -91
  340. scipy/signal/_max_len_seq_inner.cpython-312-darwin.so +0 -0
  341. scipy/signal/_peak_finding_utils.cpython-312-darwin.so +0 -0
  342. scipy/signal/_polyutils.py +172 -0
  343. scipy/signal/_short_time_fft.py +519 -70
  344. scipy/signal/_signal_api.py +30 -0
  345. scipy/signal/_signaltools.py +719 -399
  346. scipy/signal/_sigtools.cpython-312-darwin.so +0 -0
  347. scipy/signal/_sosfilt.cpython-312-darwin.so +0 -0
  348. scipy/signal/_spectral_py.py +230 -50
  349. scipy/signal/_spline.cpython-312-darwin.so +0 -0
  350. scipy/signal/_spline_filters.py +108 -68
  351. scipy/signal/_support_alternative_backends.py +73 -0
  352. scipy/signal/_upfirdn.py +4 -1
  353. scipy/signal/_upfirdn_apply.cpython-312-darwin.so +0 -0
  354. scipy/signal/_waveforms.py +2 -11
  355. scipy/signal/_wavelets.py +1 -1
  356. scipy/signal/fir_filter_design.py +1 -0
  357. scipy/signal/spline.py +4 -11
  358. scipy/signal/tests/_scipy_spectral_test_shim.py +2 -171
  359. scipy/signal/tests/test_bsplines.py +114 -79
  360. scipy/signal/tests/test_cont2discrete.py +9 -2
  361. scipy/signal/tests/test_filter_design.py +721 -481
  362. scipy/signal/tests/test_fir_filter_design.py +332 -140
  363. scipy/signal/tests/test_savitzky_golay.py +4 -3
  364. scipy/signal/tests/test_short_time_fft.py +221 -3
  365. scipy/signal/tests/test_signaltools.py +2144 -1348
  366. scipy/signal/tests/test_spectral.py +50 -6
  367. scipy/signal/tests/test_splines.py +161 -96
  368. scipy/signal/tests/test_upfirdn.py +84 -50
  369. scipy/signal/tests/test_waveforms.py +20 -0
  370. scipy/signal/tests/test_windows.py +607 -466
  371. scipy/signal/windows/_windows.py +287 -148
  372. scipy/sparse/__init__.py +23 -4
  373. scipy/sparse/_base.py +270 -108
  374. scipy/sparse/_bsr.py +7 -4
  375. scipy/sparse/_compressed.py +59 -231
  376. scipy/sparse/_construct.py +90 -38
  377. scipy/sparse/_coo.py +115 -181
  378. scipy/sparse/_csc.py +4 -4
  379. scipy/sparse/_csparsetools.cpython-312-darwin.so +0 -0
  380. scipy/sparse/_csr.py +2 -2
  381. scipy/sparse/_data.py +48 -48
  382. scipy/sparse/_dia.py +105 -18
  383. scipy/sparse/_dok.py +0 -23
  384. scipy/sparse/_index.py +4 -4
  385. scipy/sparse/_matrix.py +23 -0
  386. scipy/sparse/_sparsetools.cpython-312-darwin.so +0 -0
  387. scipy/sparse/_sputils.py +37 -22
  388. scipy/sparse/base.py +0 -9
  389. scipy/sparse/bsr.py +0 -14
  390. scipy/sparse/compressed.py +0 -23
  391. scipy/sparse/construct.py +0 -6
  392. scipy/sparse/coo.py +0 -14
  393. scipy/sparse/csc.py +0 -3
  394. scipy/sparse/csgraph/_flow.cpython-312-darwin.so +0 -0
  395. scipy/sparse/csgraph/_matching.cpython-312-darwin.so +0 -0
  396. scipy/sparse/csgraph/_min_spanning_tree.cpython-312-darwin.so +0 -0
  397. scipy/sparse/csgraph/_reordering.cpython-312-darwin.so +0 -0
  398. scipy/sparse/csgraph/_shortest_path.cpython-312-darwin.so +0 -0
  399. scipy/sparse/csgraph/_tools.cpython-312-darwin.so +0 -0
  400. scipy/sparse/csgraph/_traversal.cpython-312-darwin.so +0 -0
  401. scipy/sparse/csgraph/tests/test_matching.py +14 -2
  402. scipy/sparse/csgraph/tests/test_pydata_sparse.py +4 -1
  403. scipy/sparse/csgraph/tests/test_shortest_path.py +83 -27
  404. scipy/sparse/csr.py +0 -5
  405. scipy/sparse/data.py +1 -6
  406. scipy/sparse/dia.py +0 -7
  407. scipy/sparse/dok.py +0 -10
  408. scipy/sparse/linalg/_dsolve/_superlu.cpython-312-darwin.so +0 -0
  409. scipy/sparse/linalg/_dsolve/linsolve.py +9 -0
  410. scipy/sparse/linalg/_dsolve/tests/test_linsolve.py +35 -28
  411. scipy/sparse/linalg/_eigen/arpack/_arpack.cpython-312-darwin.so +0 -0
  412. scipy/sparse/linalg/_eigen/arpack/arpack.py +23 -17
  413. scipy/sparse/linalg/_eigen/lobpcg/lobpcg.py +6 -6
  414. scipy/sparse/linalg/_interface.py +17 -18
  415. scipy/sparse/linalg/_isolve/_gcrotmk.py +4 -4
  416. scipy/sparse/linalg/_isolve/iterative.py +51 -45
  417. scipy/sparse/linalg/_isolve/lgmres.py +6 -6
  418. scipy/sparse/linalg/_isolve/minres.py +5 -5
  419. scipy/sparse/linalg/_isolve/tfqmr.py +7 -7
  420. scipy/sparse/linalg/_isolve/utils.py +2 -8
  421. scipy/sparse/linalg/_matfuncs.py +1 -1
  422. scipy/sparse/linalg/_norm.py +1 -1
  423. scipy/sparse/linalg/_propack/_cpropack.cpython-312-darwin.so +0 -0
  424. scipy/sparse/linalg/_propack/_dpropack.cpython-312-darwin.so +0 -0
  425. scipy/sparse/linalg/_propack/_spropack.cpython-312-darwin.so +0 -0
  426. scipy/sparse/linalg/_propack/_zpropack.cpython-312-darwin.so +0 -0
  427. scipy/sparse/linalg/_special_sparse_arrays.py +39 -38
  428. scipy/sparse/linalg/tests/test_pydata_sparse.py +14 -0
  429. scipy/sparse/tests/test_arithmetic1d.py +5 -2
  430. scipy/sparse/tests/test_base.py +214 -42
  431. scipy/sparse/tests/test_common1d.py +7 -7
  432. scipy/sparse/tests/test_construct.py +1 -1
  433. scipy/sparse/tests/test_coo.py +272 -4
  434. scipy/sparse/tests/test_sparsetools.py +5 -0
  435. scipy/sparse/tests/test_sputils.py +36 -7
  436. scipy/spatial/_ckdtree.cpython-312-darwin.so +0 -0
  437. scipy/spatial/_distance_pybind.cpython-312-darwin.so +0 -0
  438. scipy/spatial/_distance_wrap.cpython-312-darwin.so +0 -0
  439. scipy/spatial/_hausdorff.cpython-312-darwin.so +0 -0
  440. scipy/spatial/_qhull.cpython-312-darwin.so +0 -0
  441. scipy/spatial/_voronoi.cpython-312-darwin.so +0 -0
  442. scipy/spatial/distance.py +49 -42
  443. scipy/spatial/tests/test_distance.py +15 -1
  444. scipy/spatial/tests/test_kdtree.py +1 -0
  445. scipy/spatial/tests/test_qhull.py +7 -2
  446. scipy/spatial/transform/__init__.py +5 -3
  447. scipy/spatial/transform/_rigid_transform.cpython-312-darwin.so +0 -0
  448. scipy/spatial/transform/_rotation.cpython-312-darwin.so +0 -0
  449. scipy/spatial/transform/tests/test_rigid_transform.py +1221 -0
  450. scipy/spatial/transform/tests/test_rotation.py +1213 -832
  451. scipy/spatial/transform/tests/test_rotation_groups.py +3 -3
  452. scipy/spatial/transform/tests/test_rotation_spline.py +29 -8
  453. scipy/special/__init__.py +1 -47
  454. scipy/special/_add_newdocs.py +34 -772
  455. scipy/special/_basic.py +22 -25
  456. scipy/special/_comb.cpython-312-darwin.so +0 -0
  457. scipy/special/_ellip_harm_2.cpython-312-darwin.so +0 -0
  458. scipy/special/_gufuncs.cpython-312-darwin.so +0 -0
  459. scipy/special/_logsumexp.py +67 -58
  460. scipy/special/_orthogonal.pyi +1 -1
  461. scipy/special/_specfun.cpython-312-darwin.so +0 -0
  462. scipy/special/_special_ufuncs.cpython-312-darwin.so +0 -0
  463. scipy/special/_spherical_bessel.py +4 -4
  464. scipy/special/_support_alternative_backends.py +212 -119
  465. scipy/special/_test_internal.cpython-312-darwin.so +0 -0
  466. scipy/special/_testutils.py +4 -4
  467. scipy/special/_ufuncs.cpython-312-darwin.so +0 -0
  468. scipy/special/_ufuncs.pyi +1 -0
  469. scipy/special/_ufuncs.pyx +215 -1400
  470. scipy/special/_ufuncs_cxx.cpython-312-darwin.so +0 -0
  471. scipy/special/_ufuncs_cxx.pxd +2 -15
  472. scipy/special/_ufuncs_cxx.pyx +5 -44
  473. scipy/special/_ufuncs_cxx_defs.h +2 -16
  474. scipy/special/_ufuncs_defs.h +0 -8
  475. scipy/special/cython_special.cpython-312-darwin.so +0 -0
  476. scipy/special/cython_special.pxd +1 -1
  477. scipy/special/tests/_cython_examples/meson.build +10 -1
  478. scipy/special/tests/test_basic.py +153 -20
  479. scipy/special/tests/test_boost_ufuncs.py +3 -0
  480. scipy/special/tests/test_cdflib.py +35 -11
  481. scipy/special/tests/test_gammainc.py +16 -0
  482. scipy/special/tests/test_hyp2f1.py +2 -2
  483. scipy/special/tests/test_log1mexp.py +85 -0
  484. scipy/special/tests/test_logsumexp.py +206 -64
  485. scipy/special/tests/test_mpmath.py +1 -0
  486. scipy/special/tests/test_nan_inputs.py +1 -1
  487. scipy/special/tests/test_orthogonal.py +17 -18
  488. scipy/special/tests/test_sf_error.py +3 -2
  489. scipy/special/tests/test_sph_harm.py +6 -7
  490. scipy/special/tests/test_support_alternative_backends.py +211 -76
  491. scipy/stats/__init__.py +4 -1
  492. scipy/stats/_ansari_swilk_statistics.cpython-312-darwin.so +0 -0
  493. scipy/stats/_axis_nan_policy.py +5 -12
  494. scipy/stats/_biasedurn.cpython-312-darwin.so +0 -0
  495. scipy/stats/_continued_fraction.py +387 -0
  496. scipy/stats/_continuous_distns.py +277 -310
  497. scipy/stats/_correlation.py +1 -1
  498. scipy/stats/_covariance.py +6 -3
  499. scipy/stats/_discrete_distns.py +39 -32
  500. scipy/stats/_distn_infrastructure.py +39 -12
  501. scipy/stats/_distribution_infrastructure.py +900 -238
  502. scipy/stats/_entropy.py +9 -10
  503. scipy/{_lib → stats}/_finite_differences.py +1 -1
  504. scipy/stats/_hypotests.py +83 -50
  505. scipy/stats/_kde.py +53 -49
  506. scipy/stats/_ksstats.py +1 -1
  507. scipy/stats/_levy_stable/__init__.py +7 -15
  508. scipy/stats/_levy_stable/levyst.cpython-312-darwin.so +0 -0
  509. scipy/stats/_morestats.py +118 -73
  510. scipy/stats/_mstats_basic.py +13 -17
  511. scipy/stats/_mstats_extras.py +8 -8
  512. scipy/stats/_multivariate.py +89 -113
  513. scipy/stats/_new_distributions.py +97 -20
  514. scipy/stats/_page_trend_test.py +12 -5
  515. scipy/stats/_probability_distribution.py +265 -43
  516. scipy/stats/_qmc.py +14 -9
  517. scipy/stats/_qmc_cy.cpython-312-darwin.so +0 -0
  518. scipy/stats/_qmvnt.py +16 -95
  519. scipy/stats/_qmvnt_cy.cpython-312-darwin.so +0 -0
  520. scipy/stats/_quantile.py +335 -0
  521. scipy/stats/_rcont/rcont.cpython-312-darwin.so +0 -0
  522. scipy/stats/_resampling.py +4 -29
  523. scipy/stats/_sampling.py +1 -1
  524. scipy/stats/_sobol.cpython-312-darwin.so +0 -0
  525. scipy/stats/_stats.cpython-312-darwin.so +0 -0
  526. scipy/stats/_stats_mstats_common.py +21 -2
  527. scipy/stats/_stats_py.py +550 -476
  528. scipy/stats/_stats_pythran.cpython-312-darwin.so +0 -0
  529. scipy/stats/_unuran/unuran_wrapper.cpython-312-darwin.so +0 -0
  530. scipy/stats/_unuran/unuran_wrapper.pyi +2 -1
  531. scipy/stats/_variation.py +6 -8
  532. scipy/stats/_wilcoxon.py +13 -7
  533. scipy/stats/tests/common_tests.py +6 -4
  534. scipy/stats/tests/test_axis_nan_policy.py +62 -24
  535. scipy/stats/tests/test_continued_fraction.py +173 -0
  536. scipy/stats/tests/test_continuous.py +379 -60
  537. scipy/stats/tests/test_continuous_basic.py +18 -12
  538. scipy/stats/tests/test_discrete_basic.py +14 -8
  539. scipy/stats/tests/test_discrete_distns.py +16 -16
  540. scipy/stats/tests/test_distributions.py +95 -75
  541. scipy/stats/tests/test_entropy.py +40 -48
  542. scipy/stats/tests/test_fit.py +4 -3
  543. scipy/stats/tests/test_hypotests.py +153 -24
  544. scipy/stats/tests/test_kdeoth.py +109 -41
  545. scipy/stats/tests/test_marray.py +289 -0
  546. scipy/stats/tests/test_morestats.py +79 -47
  547. scipy/stats/tests/test_mstats_basic.py +3 -3
  548. scipy/stats/tests/test_multivariate.py +434 -83
  549. scipy/stats/tests/test_qmc.py +13 -10
  550. scipy/stats/tests/test_quantile.py +199 -0
  551. scipy/stats/tests/test_rank.py +119 -112
  552. scipy/stats/tests/test_resampling.py +47 -56
  553. scipy/stats/tests/test_sampling.py +9 -4
  554. scipy/stats/tests/test_stats.py +799 -939
  555. scipy/stats/tests/test_variation.py +8 -6
  556. scipy/version.py +2 -2
  557. {scipy-1.15.3.dist-info → scipy-1.16.0rc2.dist-info}/LICENSE.txt +4 -4
  558. {scipy-1.15.3.dist-info → scipy-1.16.0rc2.dist-info}/METADATA +11 -11
  559. {scipy-1.15.3.dist-info → scipy-1.16.0rc2.dist-info}/RECORD +560 -567
  560. scipy-1.16.0rc2.dist-info/WHEEL +6 -0
  561. scipy/_lib/array_api_extra/_funcs.py +0 -484
  562. scipy/_lib/array_api_extra/_typing.py +0 -8
  563. scipy/interpolate/_bspl.cpython-312-darwin.so +0 -0
  564. scipy/optimize/_cobyla.cpython-312-darwin.so +0 -0
  565. scipy/optimize/_cython_nnls.cpython-312-darwin.so +0 -0
  566. scipy/optimize/_slsqp.cpython-312-darwin.so +0 -0
  567. scipy/spatial/qhull_src/COPYING.txt +0 -38
  568. scipy/special/libsf_error_state.dylib +0 -0
  569. scipy/special/tests/test_log_softmax.py +0 -109
  570. scipy/special/tests/test_xsf_cuda.py +0 -114
  571. scipy/special/xsf/binom.h +0 -89
  572. scipy/special/xsf/cdflib.h +0 -100
  573. scipy/special/xsf/cephes/airy.h +0 -307
  574. scipy/special/xsf/cephes/besselpoly.h +0 -51
  575. scipy/special/xsf/cephes/beta.h +0 -257
  576. scipy/special/xsf/cephes/cbrt.h +0 -131
  577. scipy/special/xsf/cephes/chbevl.h +0 -85
  578. scipy/special/xsf/cephes/chdtr.h +0 -193
  579. scipy/special/xsf/cephes/const.h +0 -87
  580. scipy/special/xsf/cephes/ellie.h +0 -293
  581. scipy/special/xsf/cephes/ellik.h +0 -251
  582. scipy/special/xsf/cephes/ellpe.h +0 -107
  583. scipy/special/xsf/cephes/ellpk.h +0 -117
  584. scipy/special/xsf/cephes/expn.h +0 -260
  585. scipy/special/xsf/cephes/gamma.h +0 -398
  586. scipy/special/xsf/cephes/hyp2f1.h +0 -596
  587. scipy/special/xsf/cephes/hyperg.h +0 -361
  588. scipy/special/xsf/cephes/i0.h +0 -149
  589. scipy/special/xsf/cephes/i1.h +0 -158
  590. scipy/special/xsf/cephes/igam.h +0 -421
  591. scipy/special/xsf/cephes/igam_asymp_coeff.h +0 -195
  592. scipy/special/xsf/cephes/igami.h +0 -313
  593. scipy/special/xsf/cephes/j0.h +0 -225
  594. scipy/special/xsf/cephes/j1.h +0 -198
  595. scipy/special/xsf/cephes/jv.h +0 -715
  596. scipy/special/xsf/cephes/k0.h +0 -164
  597. scipy/special/xsf/cephes/k1.h +0 -163
  598. scipy/special/xsf/cephes/kn.h +0 -243
  599. scipy/special/xsf/cephes/lanczos.h +0 -112
  600. scipy/special/xsf/cephes/ndtr.h +0 -275
  601. scipy/special/xsf/cephes/poch.h +0 -85
  602. scipy/special/xsf/cephes/polevl.h +0 -167
  603. scipy/special/xsf/cephes/psi.h +0 -194
  604. scipy/special/xsf/cephes/rgamma.h +0 -111
  605. scipy/special/xsf/cephes/scipy_iv.h +0 -811
  606. scipy/special/xsf/cephes/shichi.h +0 -248
  607. scipy/special/xsf/cephes/sici.h +0 -224
  608. scipy/special/xsf/cephes/sindg.h +0 -221
  609. scipy/special/xsf/cephes/tandg.h +0 -139
  610. scipy/special/xsf/cephes/trig.h +0 -58
  611. scipy/special/xsf/cephes/unity.h +0 -186
  612. scipy/special/xsf/cephes/zeta.h +0 -172
  613. scipy/special/xsf/config.h +0 -304
  614. scipy/special/xsf/digamma.h +0 -205
  615. scipy/special/xsf/error.h +0 -57
  616. scipy/special/xsf/evalpoly.h +0 -47
  617. scipy/special/xsf/expint.h +0 -266
  618. scipy/special/xsf/hyp2f1.h +0 -694
  619. scipy/special/xsf/iv_ratio.h +0 -173
  620. scipy/special/xsf/lambertw.h +0 -150
  621. scipy/special/xsf/loggamma.h +0 -163
  622. scipy/special/xsf/sici.h +0 -200
  623. scipy/special/xsf/tools.h +0 -427
  624. scipy/special/xsf/trig.h +0 -164
  625. scipy/special/xsf/wright_bessel.h +0 -843
  626. scipy/special/xsf/zlog1.h +0 -35
  627. scipy/stats/_mvn.cpython-312-darwin.so +0 -0
  628. scipy-1.15.3.dist-info/WHEEL +0 -4
@@ -1,21 +1,23 @@
1
1
  """Functions for FIR filter design."""
2
2
 
3
- from math import ceil, log
4
- import operator
3
+ from math import ceil, log, log2
5
4
  import warnings
6
5
  from typing import Literal
7
6
 
8
7
  import numpy as np
9
- from numpy.fft import irfft, fft, ifft
10
- from scipy.special import sinc
8
+ from scipy.fft import irfft, fft, ifft
11
9
  from scipy.linalg import (toeplitz, hankel, solve, LinAlgError, LinAlgWarning,
12
10
  lstsq)
13
11
  from scipy.signal._arraytools import _validate_fs
14
12
 
15
13
  from . import _sigtools
16
14
 
15
+ from scipy._lib._array_api import array_namespace, xp_size, xp_default_dtype
16
+ import scipy._lib.array_api_extra as xpx
17
+
18
+
17
19
  __all__ = ['kaiser_beta', 'kaiser_atten', 'kaiserord',
18
- 'firwin', 'firwin2', 'remez', 'firls', 'minimum_phase']
20
+ 'firwin', 'firwin2', 'firwin_2d', 'remez', 'firls', 'minimum_phase']
19
21
 
20
22
 
21
23
  # Some notes on function parameters:
@@ -323,6 +325,7 @@ def firwin(numtaps, cutoff, *, width=None, window='hamming', pass_zero=True,
323
325
  See Also
324
326
  --------
325
327
  firwin2
328
+ firwin_2d
326
329
  firls
327
330
  minimum_phase
328
331
  remez
@@ -370,6 +373,9 @@ def firwin(numtaps, cutoff, *, width=None, window='hamming', pass_zero=True,
370
373
  array([ 0.04890915, 0.91284326, 0.04890915])
371
374
 
372
375
  """
376
+ # NB: scipy's version of array_namespace returns `np_compat` for int or floats
377
+ xp = array_namespace(cutoff)
378
+
373
379
  # The major enhancements to this function added in November 2010 were
374
380
  # developed by Tom Krauss (see ticket #902).
375
381
  fs = _validate_fs(fs, allow_none=True)
@@ -377,18 +383,19 @@ def firwin(numtaps, cutoff, *, width=None, window='hamming', pass_zero=True,
377
383
 
378
384
  nyq = 0.5 * fs
379
385
 
380
- cutoff = np.atleast_1d(cutoff) / float(nyq)
386
+ cutoff = xp.asarray(cutoff, dtype=xp_default_dtype(xp))
387
+ cutoff = xpx.atleast_nd(cutoff, ndim=1, xp=xp) / float(nyq)
381
388
 
382
389
  # Check for invalid input.
383
390
  if cutoff.ndim > 1:
384
391
  raise ValueError("The cutoff argument must be at most "
385
392
  "one-dimensional.")
386
- if cutoff.size == 0:
393
+ if xp_size(cutoff) == 0:
387
394
  raise ValueError("At least one cutoff frequency must be given.")
388
- if cutoff.min() <= 0 or cutoff.max() >= 1:
395
+ if xp.min(cutoff) <= 0 or xp.max(cutoff) >= 1:
389
396
  raise ValueError("Invalid cutoff frequency: frequencies must be "
390
397
  "greater than 0 and less than fs/2.")
391
- if np.any(np.diff(cutoff) <= 0):
398
+ if xp.any(cutoff[1:] - cutoff[:-1] <= 0):
392
399
  raise ValueError("Invalid cutoff frequencies: the frequencies "
393
400
  "must be strictly increasing.")
394
401
 
@@ -399,69 +406,68 @@ def firwin(numtaps, cutoff, *, width=None, window='hamming', pass_zero=True,
399
406
  beta = kaiser_beta(atten)
400
407
  window = ('kaiser', beta)
401
408
 
402
- if isinstance(pass_zero, str):
403
- if pass_zero in ('bandstop', 'lowpass'):
404
- if pass_zero == 'lowpass':
405
- if cutoff.size != 1:
406
- raise ValueError('cutoff must have one element if '
407
- f'pass_zero=="lowpass", got {cutoff.shape}')
408
- elif cutoff.size <= 1:
409
- raise ValueError('cutoff must have at least two elements if '
410
- f'pass_zero=="bandstop", got {cutoff.shape}')
411
- pass_zero = True
412
- elif pass_zero in ('bandpass', 'highpass'):
413
- if pass_zero == 'highpass':
414
- if cutoff.size != 1:
415
- raise ValueError('cutoff must have one element if '
416
- f'pass_zero=="highpass", got {cutoff.shape}')
417
- elif cutoff.size <= 1:
418
- raise ValueError('cutoff must have at least two elements if '
419
- f'pass_zero=="bandpass", got {cutoff.shape}')
420
- pass_zero = False
421
- else:
422
- raise ValueError('pass_zero must be True, False, "bandpass", '
423
- '"lowpass", "highpass", or "bandstop", got '
424
- f'{pass_zero}')
425
- pass_zero = bool(operator.index(pass_zero)) # ensure bool-like
426
-
427
- pass_nyquist = bool(cutoff.size & 1) ^ pass_zero
409
+ if pass_zero in ('bandstop', 'lowpass'):
410
+ if pass_zero == 'lowpass':
411
+ if xp_size(cutoff) != 1:
412
+ raise ValueError('cutoff must have one element if '
413
+ f'pass_zero=="lowpass", got {cutoff.shape}')
414
+ elif xp_size(cutoff) <= 1:
415
+ raise ValueError('cutoff must have at least two elements if '
416
+ f'pass_zero=="bandstop", got {cutoff.shape}')
417
+ pass_zero = True
418
+ elif pass_zero in ('bandpass', 'highpass'):
419
+ if pass_zero == 'highpass':
420
+ if xp_size(cutoff) != 1:
421
+ raise ValueError('cutoff must have one element if '
422
+ f'pass_zero=="highpass", got {cutoff.shape}')
423
+ elif xp_size(cutoff) <= 1:
424
+ raise ValueError('cutoff must have at least two elements if '
425
+ f'pass_zero=="bandpass", got {cutoff.shape}')
426
+ pass_zero = False
427
+ elif not (pass_zero is True or pass_zero is False):
428
+ raise ValueError(f"Parameter {pass_zero=} not in (True, False, 'bandpass', " +
429
+ "'lowpass', 'highpass', 'bandstop')")
430
+
431
+ pass_nyquist = (xp_size(cutoff) % 2 == 0) == pass_zero
428
432
  if pass_nyquist and numtaps % 2 == 0:
429
433
  raise ValueError("A filter with an even number of coefficients must "
430
434
  "have zero response at the Nyquist frequency.")
431
435
 
432
436
  # Insert 0 and/or 1 at the ends of cutoff so that the length of cutoff
433
437
  # is even, and each pair in cutoff corresponds to passband.
434
- cutoff = np.hstack(([0.0] * pass_zero, cutoff, [1.0] * pass_nyquist))
438
+ cutoff = xp.concat((xp.zeros(int(pass_zero)), cutoff, xp.ones(int(pass_nyquist))))
439
+
435
440
 
436
441
  # `bands` is a 2-D array; each row gives the left and right edges of
437
442
  # a passband.
438
- bands = cutoff.reshape(-1, 2)
443
+ bands = xp.reshape(cutoff, (-1, 2))
439
444
 
440
445
  # Build up the coefficients.
441
446
  alpha = 0.5 * (numtaps - 1)
442
- m = np.arange(0, numtaps) - alpha
447
+ m = xp.arange(0, numtaps, dtype=cutoff.dtype) - alpha
443
448
  h = 0
444
- for left, right in bands:
445
- h += right * sinc(right * m)
446
- h -= left * sinc(left * m)
449
+ for j in range(bands.shape[0]):
450
+ left, right = bands[j, 0], bands[j, 1]
451
+ h += right * xpx.sinc(right * m, xp=xp)
452
+ h -= left * xpx.sinc(left * m, xp=xp)
447
453
 
448
454
  # Get and apply the window function.
449
455
  from .windows import get_window
450
- win = get_window(window, numtaps, fftbins=False)
456
+ win = get_window(window, numtaps, fftbins=False, xp=xp)
451
457
  h *= win
452
458
 
453
459
  # Now handle scaling if desired.
454
460
  if scale:
455
461
  # Get the first passband.
456
- left, right = bands[0]
462
+ left, right = bands[0, ...]
457
463
  if left == 0:
458
464
  scale_frequency = 0.0
459
465
  elif right == 1:
460
466
  scale_frequency = 1.0
461
467
  else:
462
468
  scale_frequency = 0.5 * (left + right)
463
- c = np.cos(np.pi * m * scale_frequency)
464
- s = np.sum(h * c)
469
+ c = xp.cos(xp.pi * m * scale_frequency)
470
+ s = xp.sum(h * c)
465
471
  h /= s
466
472
 
467
473
  return h
@@ -571,25 +577,29 @@ def firwin2(numtaps, freq, gain, *, nfreqs=None, window='hamming',
571
577
  [-0.02286961 -0.06362756 0.57310236 0.57310236 -0.06362756 -0.02286961]
572
578
 
573
579
  """
580
+ xp = array_namespace(freq, gain)
581
+ freq, gain = xp.asarray(freq), xp.asarray(gain)
582
+
574
583
  fs = _validate_fs(fs, allow_none=True)
575
584
  fs = 2 if fs is None else fs
576
585
  nyq = 0.5 * fs
577
586
 
578
- if len(freq) != len(gain):
587
+ if freq.shape[0] != gain.shape[0]:
579
588
  raise ValueError('freq and gain must be of same length.')
580
589
 
581
590
  if nfreqs is not None and numtaps >= nfreqs:
582
- raise ValueError(('ntaps must be less than nfreqs, but firwin2 was '
583
- 'called with ntaps=%d and nfreqs=%s') %
584
- (numtaps, nfreqs))
591
+ raise ValueError(
592
+ f'ntaps must be less than nfreqs, but firwin2 was called with '
593
+ f'ntaps={numtaps} and nfreqs={nfreqs}'
594
+ )
585
595
 
586
596
  if freq[0] != 0 or freq[-1] != nyq:
587
597
  raise ValueError('freq must start with 0 and end with fs/2.')
588
- d = np.diff(freq)
589
- if (d < 0).any():
598
+ d = freq[1:] - freq[:-1]
599
+ if xp.any(d < 0):
590
600
  raise ValueError('The values in freq must be nondecreasing.')
591
601
  d2 = d[:-1] + d[1:]
592
- if (d2 == 0).any():
602
+ if xp.any(d2 == 0):
593
603
  raise ValueError('A value in freq must not occur more than twice.')
594
604
  if freq[1] == 0:
595
605
  raise ValueError('Value 0 must not be repeated in freq')
@@ -620,28 +630,30 @@ def firwin2(numtaps, freq, gain, *, nfreqs=None, window='hamming',
620
630
  if nfreqs is None:
621
631
  nfreqs = 1 + 2 ** int(ceil(log(numtaps, 2)))
622
632
 
623
- if (d == 0).any():
633
+ if xp.any(d == 0):
624
634
  # Tweak any repeated values in freq so that interp works.
625
- freq = np.array(freq, copy=True)
626
- eps = np.finfo(float).eps * nyq
627
- for k in range(len(freq) - 1):
635
+ freq = xp.asarray(freq, copy=True)
636
+ eps = xp.finfo(xp_default_dtype(xp)).eps * nyq
637
+ for k in range(freq.shape[0] - 1):
628
638
  if freq[k] == freq[k + 1]:
629
639
  freq[k] = freq[k] - eps
630
640
  freq[k + 1] = freq[k + 1] + eps
631
641
  # Check if freq is strictly increasing after tweak
632
- d = np.diff(freq)
633
- if (d <= 0).any():
642
+ d = freq[1:] - freq[:-1]
643
+ if xp.any(d <= 0):
634
644
  raise ValueError("freq cannot contain numbers that are too close "
635
645
  "(within eps * (fs/2): "
636
646
  f"{eps}) to a repeated value")
637
647
 
638
648
  # Linearly interpolate the desired response on a uniform mesh `x`.
639
649
  x = np.linspace(0.0, nyq, nfreqs)
640
- fx = np.interp(x, freq, gain)
650
+ fx = np.interp(x, np.asarray(freq), np.asarray(gain)) # XXX array-api-extra#193
651
+ x = xp.asarray(x)
652
+ fx = xp.asarray(fx)
641
653
 
642
654
  # Adjust the phases of the coefficients so that the first `ntaps` of the
643
655
  # inverse FFT are the desired filter coefficients.
644
- shift = np.exp(-(numtaps - 1) / 2. * 1.j * np.pi * x / nyq)
656
+ shift = xp.exp(-(numtaps - 1) / 2. * 1j * xp.pi * x / nyq)
645
657
  if ftype > 2:
646
658
  shift *= 1j
647
659
 
@@ -653,7 +665,7 @@ def firwin2(numtaps, freq, gain, *, nfreqs=None, window='hamming',
653
665
  if window is not None:
654
666
  # Create the window to apply to the filter coefficients.
655
667
  from .windows import get_window
656
- wind = get_window(window, numtaps, fftbins=False)
668
+ wind = get_window(window, numtaps, fftbins=False, xp=xp)
657
669
  else:
658
670
  wind = 1
659
671
 
@@ -662,7 +674,7 @@ def firwin2(numtaps, freq, gain, *, nfreqs=None, window='hamming',
662
674
  out = out_full[:numtaps] * wind
663
675
 
664
676
  if ftype == 3:
665
- out[out.size // 2] = 0.0
677
+ out[xp_size(out) // 2] = 0.0
666
678
 
667
679
  return out
668
680
 
@@ -819,6 +831,12 @@ def remez(numtaps, bands, desired, *, weight=None, type='bandpass',
819
831
  >>> plt.show()
820
832
 
821
833
  """
834
+ xp = array_namespace(bands, desired, weight)
835
+ bands = np.asarray(bands)
836
+ desired = np.asarray(desired)
837
+ if weight:
838
+ weight = np.asarray(weight)
839
+
822
840
  fs = _validate_fs(fs, allow_none=True)
823
841
  fs = 1.0 if fs is None else fs
824
842
 
@@ -834,8 +852,9 @@ def remez(numtaps, bands, desired, *, weight=None, type='bandpass',
834
852
  weight = [1] * len(desired)
835
853
 
836
854
  bands = np.asarray(bands).copy()
837
- return _sigtools._remez(numtaps, bands, desired, weight, tnum, fs,
838
- maxiter, grid_density)
855
+ result = _sigtools._remez(numtaps, bands, desired, weight, tnum, fs,
856
+ maxiter, grid_density)
857
+ return xp.asarray(result)
839
858
 
840
859
 
841
860
  def firls(numtaps, bands, desired, *, weight=None, fs=None):
@@ -948,6 +967,10 @@ def firls(numtaps, bands, desired, *, weight=None, fs=None):
948
967
  >>> plt.show()
949
968
 
950
969
  """
970
+ xp = array_namespace(bands, desired)
971
+ bands = np.asarray(bands)
972
+ desired = np.asarray(desired)
973
+
951
974
  fs = _validate_fs(fs, allow_none=True)
952
975
  fs = 2 if fs is None else fs
953
976
  nyq = 0.5 * fs
@@ -1051,10 +1074,10 @@ def firls(numtaps, bands, desired, *, weight=None, fs=None):
1051
1074
 
1052
1075
  # make coefficients symmetric (linear phase)
1053
1076
  coeffs = np.hstack((a[:0:-1], 2 * a[0], a[1:]))
1054
- return coeffs
1077
+ return xp.asarray(coeffs)
1055
1078
 
1056
1079
 
1057
- def _dhtm(mag):
1080
+ def _dhtm(mag, xp):
1058
1081
  """Compute the modified 1-D discrete Hilbert transform
1059
1082
 
1060
1083
  Parameters
@@ -1065,20 +1088,20 @@ def _dhtm(mag):
1065
1088
  """
1066
1089
  # Adapted based on code by Niranjan Damera-Venkata,
1067
1090
  # Brian L. Evans and Shawn R. McCaslin (see refs for `minimum_phase`)
1068
- sig = np.zeros(len(mag))
1091
+ sig = xp.zeros(mag.shape[0])
1069
1092
  # Leave Nyquist and DC at 0, knowing np.abs(fftfreq(N)[midpt]) == 0.5
1070
- midpt = len(mag) // 2
1093
+ midpt = mag.shape[0] // 2
1071
1094
  sig[1:midpt] = 1
1072
1095
  sig[midpt+1:] = -1
1073
1096
  # eventually if we want to support complex filters, we will need a
1074
1097
  # np.abs() on the mag inside the log, and should remove the .real
1075
- recon = ifft(mag * np.exp(fft(sig * ifft(np.log(mag))))).real
1098
+ recon = xp.real(ifft(mag * xp.exp(fft(sig * ifft(xp.log(mag))))))
1076
1099
  return recon
1077
1100
 
1078
1101
 
1079
- def minimum_phase(h: np.ndarray,
1102
+ def minimum_phase(h,
1080
1103
  method: Literal['homomorphic', 'hilbert'] = 'homomorphic',
1081
- n_fft: int | None = None, *, half: bool = True) -> np.ndarray:
1104
+ n_fft: int | None = None, *, half: bool = True):
1082
1105
  """Convert a linear-phase FIR filter to minimum phase
1083
1106
 
1084
1107
  Parameters
@@ -1229,13 +1252,16 @@ def minimum_phase(h: np.ndarray,
1229
1252
  linear filter `h` whereas the other minimum phase filters have only half the order
1230
1253
  and the square root of the magnitude response.
1231
1254
  """
1232
- h = np.asarray(h)
1233
- if np.iscomplexobj(h):
1255
+ xp = array_namespace(h)
1256
+
1257
+ h = xp.asarray(h)
1258
+ if xp.isdtype(h.dtype, "complex floating"):
1234
1259
  raise ValueError('Complex filters not supported')
1235
- if h.ndim != 1 or h.size <= 2:
1260
+ if h.ndim != 1 or h.shape[0] <= 2:
1236
1261
  raise ValueError('h must be 1-D and at least 2 samples long')
1237
- n_half = len(h) // 2
1238
- if not np.allclose(h[-n_half:][::-1], h[:n_half]):
1262
+ n_half = h.shape[0] // 2
1263
+
1264
+ if not xp.any(xp.flip(h[-n_half:]) - h[:n_half] <= 1e-8 + 1e-6*abs(h[:n_half])):
1239
1265
  warnings.warn('h does not appear to by symmetric, conversion may fail',
1240
1266
  RuntimeWarning, stacklevel=2)
1241
1267
  if not isinstance(method, str) or method not in \
@@ -1244,43 +1270,189 @@ def minimum_phase(h: np.ndarray,
1244
1270
  if method == "hilbert" and not half:
1245
1271
  raise ValueError("`half=False` is only supported when `method='homomorphic'`")
1246
1272
  if n_fft is None:
1247
- n_fft = 2 ** int(np.ceil(np.log2(2 * (len(h) - 1) / 0.01)))
1273
+ n_fft = 2 ** int(ceil(log2(2 * (h.shape[0] - 1) / 0.01)))
1248
1274
  n_fft = int(n_fft)
1249
- if n_fft < len(h):
1275
+ if n_fft < h.shape[0]:
1250
1276
  raise ValueError(f'n_fft must be at least len(h)=={len(h)}')
1277
+
1251
1278
  if method == 'hilbert':
1252
- w = np.arange(n_fft) * (2 * np.pi / n_fft * n_half)
1253
- H = np.real(fft(h, n_fft) * np.exp(1j * w))
1279
+ w = xp.arange(n_fft, dtype=xp.float64) * (2 * xp.pi / n_fft * n_half)
1280
+ H = xp.real(fft(h, n_fft) * xp.exp(1j * w))
1254
1281
  dp = max(H) - 1
1255
1282
  ds = 0 - min(H)
1256
- S = 4. / (np.sqrt(1+dp+ds) + np.sqrt(1-dp+ds)) ** 2
1283
+ S = 4. / (xp.sqrt(1+dp+ds) + xp.sqrt(1-dp+ds)) ** 2
1257
1284
  H += ds
1258
1285
  H *= S
1259
- H = np.sqrt(H, out=H)
1286
+ H = xp.sqrt(H)
1260
1287
  H += 1e-10 # ensure that the log does not explode
1261
- h_minimum = _dhtm(H)
1288
+ h_minimum = _dhtm(H, xp)
1262
1289
  else: # method == 'homomorphic'
1263
1290
  # zero-pad; calculate the DFT
1264
- h_temp = np.abs(fft(h, n_fft))
1291
+ h_temp = xp.abs(fft(h, n_fft))
1265
1292
  # take 0.25*log(|H|**2) = 0.5*log(|H|)
1266
- h_temp += 1e-7 * h_temp[h_temp > 0].min() # don't let log blow up
1267
- np.log(h_temp, out=h_temp)
1293
+ h_temp += 1e-7 * xp.min(h_temp[h_temp > 0]) # don't let log blow up
1294
+ h_temp = xp.log(h_temp)
1268
1295
  if half: # halving of magnitude spectrum optional
1269
1296
  h_temp *= 0.5
1270
1297
  # IDFT
1271
- h_temp = ifft(h_temp).real
1298
+ h_temp = xp.real(ifft(h_temp))
1272
1299
  # multiply pointwise by the homomorphic filter
1273
1300
  # lmin[n] = 2u[n] - d[n]
1274
1301
  # i.e., double the positive frequencies and zero out the negative ones;
1275
1302
  # Oppenheim+Shafer 3rd ed p991 eq13.42b and p1004 fig13.7
1276
- win = np.zeros(n_fft)
1303
+ win = xp.zeros(n_fft)
1277
1304
  win[0] = 1
1278
1305
  stop = n_fft // 2
1279
1306
  win[1:stop] = 2
1280
1307
  if n_fft % 2:
1281
1308
  win[stop] = 1
1282
1309
  h_temp *= win
1283
- h_temp = ifft(np.exp(fft(h_temp)))
1310
+ h_temp = ifft(xp.exp(fft(h_temp)))
1284
1311
  h_minimum = h_temp.real
1285
- n_out = (n_half + len(h) % 2) if half else len(h)
1312
+ n_out = (n_half + h.shape[0] % 2) if half else h.shape[0]
1286
1313
  return h_minimum[:n_out]
1314
+
1315
+
1316
+ def firwin_2d(hsize, window, *, fc=None, fs=2, circular=False,
1317
+ pass_zero=True, scale=True):
1318
+ """
1319
+ 2D FIR filter design using the window method.
1320
+
1321
+ This function computes the coefficients of a 2D finite impulse response
1322
+ filter. The filter is separable with linear phase; it will be designed
1323
+ as a product of two 1D filters with dimensions defined by `hsize`.
1324
+ Additionally, it can create approximately circularly symmetric 2-D windows.
1325
+
1326
+ Parameters
1327
+ ----------
1328
+ hsize : tuple or list of length 2
1329
+ Lengths of the filter in each dimension. `hsize[0]` specifies the
1330
+ number of coefficients in the row direction and `hsize[1]` specifies
1331
+ the number of coefficients in the column direction.
1332
+ window : tuple or list of length 2 or string
1333
+ Desired window to use for each 1D filter or a single window type
1334
+ for creating circularly symmetric 2-D windows. Each element should be
1335
+ a string or tuple of string and parameter values. See
1336
+ `~scipy.signal.get_window` for a list of windows and required
1337
+ parameters.
1338
+ fc : float or 1-D array_like, optional
1339
+ Cutoff frequency of the filter in the same units as `fs`. This defines
1340
+ the frequency at which the filter's gain drops to approximately -6 dB
1341
+ (half power) in a low-pass or high-pass filter. For multi-band filters,
1342
+ `fc` can be an array of cutoff frequencies (i.e., band edges) in the
1343
+ range [0, fs/2], with each band specified in pairs. Required if
1344
+ `circular` is False.
1345
+ fs : float, optional
1346
+ The sampling frequency of the signal. Default is 2.
1347
+ circular : bool, optional
1348
+ Whether to create a circularly symmetric 2-D window. Default is ``False``.
1349
+ pass_zero : {True, False, 'bandpass', 'lowpass', 'highpass', 'bandstop'}, optional
1350
+ This parameter is directly passed to `firwin` for each scalar frequency axis.
1351
+ Hence, if ``True``, the DC gain, i.e., the gain at frequency (0, 0), is 1.
1352
+ If ``False``, the DC gain is 0 at frequency (0, 0) if `circular` is ``True``.
1353
+ If `circular` is ``False`` the frequencies (0, f1) and (f0, 0) will
1354
+ have gain 0.
1355
+ It can also be a string argument for the desired filter type
1356
+ (equivalent to ``btype`` in IIR design functions).
1357
+ scale : bool, optional
1358
+ This parameter is directly passed to `firwin` for each scalar frequency axis.
1359
+ Set to ``True`` to scale the coefficients so that the frequency
1360
+ response is exactly unity at a certain frequency on one frequency axis.
1361
+ That frequency is either:
1362
+
1363
+ - 0 (DC) if the first passband starts at 0 (i.e. pass_zero is ``True``)
1364
+ - `fs`/2 (the Nyquist frequency) if the first passband ends at `fs`/2
1365
+ (i.e., the filter is a single band highpass filter);
1366
+ center of first passband otherwise
1367
+
1368
+ Returns
1369
+ -------
1370
+ filter_2d : (hsize[0], hsize[1]) ndarray
1371
+ Coefficients of 2D FIR filter.
1372
+
1373
+ Raises
1374
+ ------
1375
+ ValueError
1376
+ - If `hsize` and `window` are not 2-element tuples or lists.
1377
+ - If `cutoff` is None when `circular` is True.
1378
+ - If `cutoff` is outside the range [0, `fs`/2] and `circular` is ``False``.
1379
+ - If any of the elements in `window` are not recognized.
1380
+ RuntimeError
1381
+ If `firwin` fails to converge when designing the filter.
1382
+
1383
+ See Also
1384
+ --------
1385
+ firwin: FIR filter design using the window method for 1d arrays.
1386
+ get_window: Return a window of a given length and type.
1387
+
1388
+ Examples
1389
+ --------
1390
+ Generate a 5x5 low-pass filter with cutoff frequency 0.1:
1391
+
1392
+ >>> import numpy as np
1393
+ >>> from scipy.signal import get_window
1394
+ >>> from scipy.signal import firwin_2d
1395
+ >>> hsize = (5, 5)
1396
+ >>> window = (("kaiser", 5.0), ("kaiser", 5.0))
1397
+ >>> fc = 0.1
1398
+ >>> filter_2d = firwin_2d(hsize, window, fc=fc)
1399
+ >>> filter_2d
1400
+ array([[0.00025366, 0.00401662, 0.00738617, 0.00401662, 0.00025366],
1401
+ [0.00401662, 0.06360159, 0.11695714, 0.06360159, 0.00401662],
1402
+ [0.00738617, 0.11695714, 0.21507283, 0.11695714, 0.00738617],
1403
+ [0.00401662, 0.06360159, 0.11695714, 0.06360159, 0.00401662],
1404
+ [0.00025366, 0.00401662, 0.00738617, 0.00401662, 0.00025366]])
1405
+
1406
+ Generate a circularly symmetric 5x5 low-pass filter with Hamming window:
1407
+
1408
+ >>> filter_2d = firwin_2d((5, 5), 'hamming', fc=fc, circular=True)
1409
+ >>> filter_2d
1410
+ array([[-0.00020354, -0.00020354, -0.00020354, -0.00020354, -0.00020354],
1411
+ [-0.00020354, 0.01506844, 0.09907658, 0.01506844, -0.00020354],
1412
+ [-0.00020354, 0.09907658, -0.00020354, 0.09907658, -0.00020354],
1413
+ [-0.00020354, 0.01506844, 0.09907658, 0.01506844, -0.00020354],
1414
+ [-0.00020354, -0.00020354, -0.00020354, -0.00020354, -0.00020354]])
1415
+
1416
+ Generate Plots comparing the product of two 1d filters with a circular
1417
+ symmetric filter:
1418
+
1419
+ >>> import matplotlib.pyplot as plt
1420
+ >>> hsize, fc = (50, 50), 0.05
1421
+ >>> window = (("kaiser", 5.0), ("kaiser", 5.0))
1422
+ >>> filter0_2d = firwin_2d(hsize, window, fc=fc)
1423
+ >>> filter1_2d = firwin_2d((50, 50), 'hamming', fc=fc, circular=True)
1424
+ ...
1425
+ >>> fg, (ax0, ax1) = plt.subplots(1, 2, tight_layout=True, figsize=(6.5, 3.5))
1426
+ >>> ax0.set_title("Product of 2 Windows")
1427
+ >>> im0 = ax0.imshow(filter0_2d, cmap='viridis', origin='lower', aspect='equal')
1428
+ >>> fg.colorbar(im0, ax=ax0, shrink=0.7)
1429
+ >>> ax1.set_title("Circular Window")
1430
+ >>> im1 = ax1.imshow(filter1_2d, cmap='plasma', origin='lower', aspect='equal')
1431
+ >>> fg.colorbar(im1, ax=ax1, shrink=0.7)
1432
+ >>> plt.show()
1433
+ """
1434
+ if len(hsize) != 2:
1435
+ raise ValueError("hsize must be a 2-element tuple or list")
1436
+
1437
+ if circular:
1438
+ if fc is None:
1439
+ raise ValueError("Cutoff frequency `fc` must be "
1440
+ "provided when `circular` is True")
1441
+
1442
+ n_r = max(hsize[0], hsize[1]) * 8 # oversample 1d window by factor 8
1443
+
1444
+ win_r = firwin(n_r, cutoff=fc, window=window, fs=fs)
1445
+
1446
+ f1, f2 = np.meshgrid(np.linspace(-1, 1, hsize[0]), np.linspace(-1, 1, hsize[1]))
1447
+ r = np.sqrt(f1**2 + f2**2)
1448
+
1449
+ win_2d = np.interp(r, np.linspace(0, 1, n_r), win_r)
1450
+ return win_2d
1451
+
1452
+ if len(window) != 2:
1453
+ raise ValueError("window must be a 2-element tuple or list")
1454
+
1455
+ row_filter = firwin(hsize[0], cutoff=fc, window=window[0], fs=fs)
1456
+ col_filter = firwin(hsize[1], cutoff=fc, window=window[1], fs=fs)
1457
+
1458
+ return np.outer(row_filter, col_filter)
@@ -431,8 +431,9 @@ def cont2discrete(system, dt, method="zoh", alpha=None):
431
431
  >>> plt.show()
432
432
 
433
433
  """
434
- if len(system) == 1:
435
- return system.to_discrete()
434
+ if hasattr(system, 'to_discrete') and callable(system.to_discrete):
435
+ return system.to_discrete(dt=dt, method=method, alpha=alpha)
436
+
436
437
  if len(system) == 2:
437
438
  sysd = cont2discrete(tf2ss(system[0], system[1]), dt, method=method,
438
439
  alpha=alpha)