scipy 1.16.0rc2__cp312-cp312-macosx_14_0_arm64.whl → 1.16.2__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 (131) hide show
  1. scipy/.dylibs/libgcc_s.1.1.dylib +0 -0
  2. scipy/.dylibs/libgfortran.5.dylib +0 -0
  3. scipy/.dylibs/libquadmath.0.dylib +0 -0
  4. scipy/__config__.py +5 -5
  5. scipy/_cyutility.cpython-312-darwin.so +0 -0
  6. scipy/_lib/_ccallback_c.cpython-312-darwin.so +0 -0
  7. scipy/_lib/_test_deprecation_call.cpython-312-darwin.so +0 -0
  8. scipy/_lib/_util.py +7 -0
  9. scipy/_lib/messagestream.cpython-312-darwin.so +0 -0
  10. scipy/cluster/_hierarchy.cpython-312-darwin.so +0 -0
  11. scipy/cluster/_optimal_leaf_ordering.cpython-312-darwin.so +0 -0
  12. scipy/cluster/_vq.cpython-312-darwin.so +0 -0
  13. scipy/conftest.py +25 -0
  14. scipy/fft/_pocketfft/pypocketfft.cpython-312-darwin.so +0 -0
  15. scipy/fftpack/convolve.cpython-312-darwin.so +0 -0
  16. scipy/integrate/_dop.cpython-312-darwin.so +0 -0
  17. scipy/integrate/_lsoda.cpython-312-darwin.so +0 -0
  18. scipy/integrate/_odepack.cpython-312-darwin.so +0 -0
  19. scipy/integrate/_test_odeint_banded.cpython-312-darwin.so +0 -0
  20. scipy/integrate/_vode.cpython-312-darwin.so +0 -0
  21. scipy/interpolate/_dfitpack.cpython-312-darwin.so +0 -0
  22. scipy/interpolate/_fitpack.cpython-312-darwin.so +0 -0
  23. scipy/interpolate/_interpnd.cpython-312-darwin.so +0 -0
  24. scipy/interpolate/_ppoly.cpython-312-darwin.so +0 -0
  25. scipy/interpolate/_rgi_cython.cpython-312-darwin.so +0 -0
  26. scipy/io/_fast_matrix_market/_fmm_core.cpython-312-darwin.so +0 -0
  27. scipy/io/_test_fortran.cpython-312-darwin.so +0 -0
  28. scipy/io/matlab/_mio5_utils.cpython-312-darwin.so +0 -0
  29. scipy/io/matlab/_mio_utils.cpython-312-darwin.so +0 -0
  30. scipy/io/matlab/_streams.cpython-312-darwin.so +0 -0
  31. scipy/io/matlab/tests/test_streams.py +9 -0
  32. scipy/linalg/_cythonized_array_utils.cpython-312-darwin.so +0 -0
  33. scipy/linalg/_decomp_interpolative.cpython-312-darwin.so +0 -0
  34. scipy/linalg/_decomp_lu_cython.cpython-312-darwin.so +0 -0
  35. scipy/linalg/_decomp_update.cpython-312-darwin.so +0 -0
  36. scipy/linalg/_fblas.cpython-312-darwin.so +0 -0
  37. scipy/linalg/_flapack.cpython-312-darwin.so +0 -0
  38. scipy/linalg/_matfuncs_schur_sqrtm.cpython-312-darwin.so +0 -0
  39. scipy/linalg/_matfuncs_sqrtm_triu.cpython-312-darwin.so +0 -0
  40. scipy/linalg/_solve_toeplitz.cpython-312-darwin.so +0 -0
  41. scipy/linalg/blas.py +35 -24
  42. scipy/linalg/cython_blas.cpython-312-darwin.so +0 -0
  43. scipy/linalg/cython_lapack.cpython-312-darwin.so +0 -0
  44. scipy/linalg/tests/test_lapack.py +5 -1
  45. scipy/linalg/tests/test_matfuncs.py +7 -0
  46. scipy/ndimage/_cytest.cpython-312-darwin.so +0 -0
  47. scipy/ndimage/_filters.py +11 -2
  48. scipy/ndimage/_ni_label.cpython-312-darwin.so +0 -0
  49. scipy/ndimage/_rank_filter_1d.cpython-312-darwin.so +0 -0
  50. scipy/ndimage/tests/test_filters.py +52 -0
  51. scipy/odr/__odrpack.cpython-312-darwin.so +0 -0
  52. scipy/optimize/_bglu_dense.cpython-312-darwin.so +0 -0
  53. scipy/optimize/_highspy/_core.cpython-312-darwin.so +0 -0
  54. scipy/optimize/_highspy/_highs_options.cpython-312-darwin.so +0 -0
  55. scipy/optimize/_lbfgsb_py.py +25 -10
  56. scipy/optimize/_lsq/givens_elimination.cpython-312-darwin.so +0 -0
  57. scipy/optimize/_lsq/least_squares.py +2 -2
  58. scipy/optimize/_minimize.py +0 -1
  59. scipy/optimize/_moduleTNC.cpython-312-darwin.so +0 -0
  60. scipy/optimize/_pava_pybind.cpython-312-darwin.so +0 -0
  61. scipy/optimize/_shgo_lib/_complex.py +2 -2
  62. scipy/optimize/_slsqp_py.py +5 -5
  63. scipy/optimize/_slsqplib.cpython-312-darwin.so +0 -0
  64. scipy/optimize/_trlib/_trlib.cpython-312-darwin.so +0 -0
  65. scipy/optimize/cython_optimize/_zeros.cpython-312-darwin.so +0 -0
  66. scipy/optimize/tests/test_optimize.py +12 -3
  67. scipy/signal/_filter_design.py +13 -1
  68. scipy/signal/_fir_filter_design.py +1 -1
  69. scipy/signal/_peak_finding_utils.cpython-312-darwin.so +0 -0
  70. scipy/signal/_polyutils.py +1 -1
  71. scipy/signal/_short_time_fft.py +74 -33
  72. scipy/signal/_sosfilt.cpython-312-darwin.so +0 -0
  73. scipy/signal/_spectral_py.py +2 -2
  74. scipy/signal/_upfirdn_apply.cpython-312-darwin.so +0 -0
  75. scipy/signal/tests/test_filter_design.py +19 -0
  76. scipy/signal/tests/test_fir_filter_design.py +5 -0
  77. scipy/signal/tests/test_short_time_fft.py +9 -0
  78. scipy/signal/tests/test_signaltools.py +9 -3
  79. scipy/signal/tests/test_spectral.py +39 -1
  80. scipy/sparse/_base.py +4 -1
  81. scipy/sparse/_csparsetools.cpython-312-darwin.so +0 -0
  82. scipy/sparse/csgraph/_flow.cpython-312-darwin.so +0 -0
  83. scipy/sparse/csgraph/_matching.cpython-312-darwin.so +0 -0
  84. scipy/sparse/csgraph/_min_spanning_tree.cpython-312-darwin.so +0 -0
  85. scipy/sparse/csgraph/_reordering.cpython-312-darwin.so +0 -0
  86. scipy/sparse/csgraph/_shortest_path.cpython-312-darwin.so +0 -0
  87. scipy/sparse/csgraph/_tools.cpython-312-darwin.so +0 -0
  88. scipy/sparse/csgraph/_traversal.cpython-312-darwin.so +0 -0
  89. scipy/sparse/linalg/_eigen/arpack/_arpack.cpython-312-darwin.so +0 -0
  90. scipy/sparse/linalg/_propack/_cpropack.cpython-312-darwin.so +0 -0
  91. scipy/sparse/linalg/_propack/_dpropack.cpython-312-darwin.so +0 -0
  92. scipy/sparse/linalg/_propack/_spropack.cpython-312-darwin.so +0 -0
  93. scipy/sparse/linalg/_propack/_zpropack.cpython-312-darwin.so +0 -0
  94. scipy/sparse/tests/test_base.py +3 -0
  95. scipy/spatial/_ckdtree.cpython-312-darwin.so +0 -0
  96. scipy/spatial/_distance_pybind.cpython-312-darwin.so +0 -0
  97. scipy/spatial/_hausdorff.cpython-312-darwin.so +0 -0
  98. scipy/spatial/_qhull.cpython-312-darwin.so +0 -0
  99. scipy/spatial/_voronoi.cpython-312-darwin.so +0 -0
  100. scipy/spatial/qhull_src/COPYING_QHULL.txt +39 -0
  101. scipy/spatial/tests/test_distance.py +5 -4
  102. scipy/spatial/transform/_rigid_transform.cpython-312-darwin.so +0 -0
  103. scipy/spatial/transform/_rotation.cpython-312-darwin.so +0 -0
  104. scipy/special/_comb.cpython-312-darwin.so +0 -0
  105. scipy/special/_ellip_harm_2.cpython-312-darwin.so +0 -0
  106. scipy/special/_specfun.cpython-312-darwin.so +0 -0
  107. scipy/special/_test_internal.cpython-312-darwin.so +0 -0
  108. scipy/special/_ufuncs.cpython-312-darwin.so +0 -0
  109. scipy/special/_ufuncs_cxx.cpython-312-darwin.so +0 -0
  110. scipy/special/cython_special.cpython-312-darwin.so +0 -0
  111. scipy/stats/_ansari_swilk_statistics.cpython-312-darwin.so +0 -0
  112. scipy/stats/_biasedurn.cpython-312-darwin.so +0 -0
  113. scipy/stats/_continuous_distns.py +19 -16
  114. scipy/stats/_distribution_infrastructure.py +20 -0
  115. scipy/stats/_levy_stable/levyst.cpython-312-darwin.so +0 -0
  116. scipy/stats/_qmc_cy.cpython-312-darwin.so +0 -0
  117. scipy/stats/_qmvnt_cy.cpython-312-darwin.so +0 -0
  118. scipy/stats/_rcont/rcont.cpython-312-darwin.so +0 -0
  119. scipy/stats/_resampling.py +1 -1
  120. scipy/stats/_sobol.cpython-312-darwin.so +0 -0
  121. scipy/stats/_stats.cpython-312-darwin.so +0 -0
  122. scipy/stats/_stats_py.py +1 -1
  123. scipy/stats/_unuran/unuran_wrapper.cpython-312-darwin.so +0 -0
  124. scipy/stats/tests/test_distributions.py +13 -0
  125. scipy/stats/tests/test_fast_gen_inversion.py +2 -0
  126. scipy/stats/tests/test_morestats.py +4 -4
  127. scipy/version.py +2 -2
  128. {scipy-1.16.0rc2.dist-info → scipy-1.16.2.dist-info}/METADATA +3 -2
  129. {scipy-1.16.0rc2.dist-info → scipy-1.16.2.dist-info}/RECORD +131 -130
  130. {scipy-1.16.0rc2.dist-info → scipy-1.16.2.dist-info}/LICENSE.txt +0 -0
  131. {scipy-1.16.0rc2.dist-info → scipy-1.16.2.dist-info}/WHEEL +0 -0
@@ -234,8 +234,8 @@ def _minimize_slsqp(func, x0, args=(), jac=None, bounds=None,
234
234
  disp : bool
235
235
  Set to True to print convergence messages. If False,
236
236
  `verbosity` is ignored and set to 0.
237
- maxiter : int
238
- Maximum number of iterations.
237
+ maxiter : int, optional
238
+ Maximum number of iterations. Default value is 100.
239
239
  finite_diff_rel_step : None or array_like, optional
240
240
  If ``jac in ['2-point', '3-point', 'cs']`` the relative step size to
241
241
  use for numerical approximation of `jac`. The absolute step
@@ -268,8 +268,8 @@ def _minimize_slsqp(func, x0, args=(), jac=None, bounds=None,
268
268
  attribute as a NumPy array. Denoting the dimension of the equality constraints
269
269
  with ``meq``, and of inequality constraints with ``mineq``, then the returned
270
270
  array slice ``m[:meq]`` contains the multipliers for the equality constraints,
271
- and the remaining ``m[meq:meq + mineq]`` contains the multipliers for the
272
- inequality constraints. The multipliers corresponding to bound inequalities
271
+ and the remaining ``m[meq:meq + mineq]`` contains the multipliers for the
272
+ inequality constraints. The multipliers corresponding to bound inequalities
273
273
  are not returned. See [1]_ pp. 321 or [2]_ for an explanation of how to interpret
274
274
  these multipliers. The internal QP problem is solved using the methods given
275
275
  in [3]_ Chapter 25.
@@ -461,7 +461,7 @@ def _minimize_slsqp(func, x0, args=(), jac=None, bounds=None,
461
461
  "inconsistent": 0,
462
462
  "reset": 0,
463
463
  "iter": 0,
464
- "itermax": maxiter,
464
+ "itermax": int(maxiter),
465
465
  "line": 0,
466
466
  "m": m,
467
467
  "meq": meq,
@@ -1126,7 +1126,7 @@ class TestOptimizeSimple(CheckOptimize):
1126
1126
 
1127
1127
  def test_minimize_l_bfgs_b(self):
1128
1128
  # Minimize with L-BFGS-B method
1129
- opts = {'disp': False, 'maxiter': self.maxiter}
1129
+ opts = {'maxiter': self.maxiter}
1130
1130
  r = optimize.minimize(self.func, self.startparams,
1131
1131
  method='L-BFGS-B', jac=self.grad,
1132
1132
  options=opts)
@@ -1156,7 +1156,7 @@ class TestOptimizeSimple(CheckOptimize):
1156
1156
  # Check that the `ftol` parameter in l_bfgs_b works as expected
1157
1157
  v0 = None
1158
1158
  for tol in [1e-1, 1e-4, 1e-7, 1e-10]:
1159
- opts = {'disp': False, 'maxiter': self.maxiter, 'ftol': tol}
1159
+ opts = {'maxiter': self.maxiter, 'ftol': tol}
1160
1160
  sol = optimize.minimize(self.func, self.startparams,
1161
1161
  method='L-BFGS-B', jac=self.grad,
1162
1162
  options=opts)
@@ -1173,7 +1173,7 @@ class TestOptimizeSimple(CheckOptimize):
1173
1173
  # check that the maxls is passed down to the Fortran routine
1174
1174
  sol = optimize.minimize(optimize.rosen, np.array([-1.2, 1.0]),
1175
1175
  method='L-BFGS-B', jac=optimize.rosen_der,
1176
- options={'disp': False, 'maxls': 1})
1176
+ options={'maxls': 1})
1177
1177
  assert not sol.success
1178
1178
 
1179
1179
  def test_minimize_l_bfgs_b_maxfun_interruption(self):
@@ -3167,6 +3167,15 @@ def test_bounds_with_list():
3167
3167
  )
3168
3168
 
3169
3169
 
3170
+ @pytest.mark.parametrize('method', (
3171
+ 'slsqp', 'cg', 'cobyqa', 'powell','nelder-mead', 'bfgs', 'l-bfgs-b',
3172
+ 'trust-constr'))
3173
+ def test_minimize_maxiter_noninteger(method):
3174
+ # Regression test for gh-23430
3175
+ x0 = np.array([1.3, 0.7, 0.8, 1.9, 1.2])
3176
+ optimize.minimize(rosen, x0, method=method, options={'maxiter': 100.1})
3177
+
3178
+
3170
3179
  def test_x_overwritten_user_function():
3171
3180
  # if the user overwrites the x-array in the user function it's likely
3172
3181
  # that the minimizer stops working properly.
@@ -60,6 +60,17 @@ def _is_int_type(x):
60
60
  return True
61
61
 
62
62
 
63
+ def _real_dtype_for_complex(dtyp, *, xp):
64
+ if xp.isdtype(dtyp, 'real floating'):
65
+ return dtyp
66
+ if dtyp == xp.complex64:
67
+ return xp.float32
68
+ elif dtyp == xp.complex128:
69
+ return xp.float64
70
+ else:
71
+ raise ValueError(f"Unknown dtype {dtyp}.")
72
+
73
+
63
74
  # https://github.com/numpy/numpy/blob/v2.2.0/numpy/_core/function_base.py#L195-L302
64
75
  def _logspace(start, stop, num=50, endpoint=True, base=10.0, dtype=None, *, xp):
65
76
  if not isinstance(base, float | int) and xp.asarray(base).ndim > 0:
@@ -488,6 +499,7 @@ def freqz(b, a=1, worN=512, whole=False, plot=None, fs=2*pi,
488
499
  if xp.isdtype(a.dtype, 'integral'):
489
500
  a = xp.astype(a, xp_default_dtype(xp))
490
501
  res_dtype = xp.result_type(b, a)
502
+ real_dtype = _real_dtype_for_complex(res_dtype, xp=xp)
491
503
 
492
504
  b = xpx.atleast_nd(b, ndim=1, xp=xp)
493
505
  a = xpx.atleast_nd(a, ndim=1, xp=xp)
@@ -509,7 +521,7 @@ def freqz(b, a=1, worN=512, whole=False, plot=None, fs=2*pi,
509
521
  # if include_nyquist is true and whole is false, w should
510
522
  # include end point
511
523
  w = xp.linspace(0, lastpoint, N,
512
- endpoint=include_nyquist and not whole, dtype=res_dtype)
524
+ endpoint=include_nyquist and not whole, dtype=real_dtype)
513
525
  n_fft = N if whole else 2 * (N - 1) if include_nyquist else 2 * N
514
526
  if (xp_size(a) == 1 and (b.ndim == 1 or (b.shape[-1] == 1))
515
527
  and n_fft >= b.shape[0]
@@ -834,7 +834,7 @@ def remez(numtaps, bands, desired, *, weight=None, type='bandpass',
834
834
  xp = array_namespace(bands, desired, weight)
835
835
  bands = np.asarray(bands)
836
836
  desired = np.asarray(desired)
837
- if weight:
837
+ if weight is not None:
838
838
  weight = np.asarray(weight)
839
839
 
840
840
  fs = _validate_fs(fs, allow_none=True)
@@ -117,7 +117,7 @@ def poly(seq_of_zeros, *, xp):
117
117
  if xp.isdtype(a.dtype, 'complex floating'):
118
118
  # if complex roots are all complex conjugates, the roots are real.
119
119
  roots = xp.asarray(seq_of_zeros, dtype=xp.complex128)
120
- if xp.all(_sort_cmplx(roots, xp) == _sort_cmplx(xp.conj(roots), xp)):
120
+ if xp.all(xp.sort(xp.imag(roots)) == xp.sort(xp.imag(xp.conj(roots)))):
121
121
  a = xp.asarray(xp.real(a), copy=True)
122
122
 
123
123
  return a
@@ -1,11 +1,7 @@
1
1
  """Implementation of an FFT-based Short-time Fourier Transform. """
2
2
 
3
- # Implementation Notes for this file (as of 2023-07)
3
+ # Implementation Notes for this file (as of 2025-08)
4
4
  # --------------------------------------------------
5
- # * MyPy version 1.1.1 does not seem to support decorated property methods
6
- # properly. Hence, applying ``@property`` to methods decorated with `@cache``
7
- # (as tried with the ``lower_border_end`` method) causes a mypy error when
8
- # accessing it as an index (e.g., ``SFT.lower_border_end[0]``).
9
5
  # * Since the method `stft` and `istft` have identical names as the legacy
10
6
  # functions in the signal module, referencing them as HTML link in the
11
7
  # docstrings has to be done by an explicit `~ShortTimeFFT.stft` instead of an
@@ -17,10 +13,9 @@
17
13
  # (currently 0.9). Consult Issue 18512 and PR 16660 for further details.
18
14
 
19
15
 
20
- # Provides typing union operator ``|`` in Python 3.9:
21
16
  # Linter does not allow to import ``Generator`` from ``typing`` module:
22
17
  from collections.abc import Generator, Callable
23
- from functools import cache, lru_cache, partial
18
+ from functools import partial, cached_property
24
19
  from typing import get_args, Literal
25
20
 
26
21
  import numpy as np
@@ -419,6 +414,15 @@ class ShortTimeFFT:
419
414
  _fac_mag: float | None = None
420
415
  _fac_psd: float | None = None
421
416
  _lower_border_end: tuple[int, int] | None = None
417
+ # The following tuples store parameter(s) and return value(s) of methods for caching
418
+ # (initialized with invalid parameters; should only be accessed by atomic
419
+ # read/writes to alleviate potential multithreading issues):
420
+ _cache_post_padding: tuple[int, tuple[int, int]] = -1, (0, 0)
421
+ _cache_upper_border_begin: tuple[int, tuple[int, int]] = -1, (0, 0)
422
+ _cache_t: tuple[tuple[int, int | None, int | None, int, float], np.ndarray] = \
423
+ (-1, None, None, 0, 0.), np.ndarray([])
424
+ _cache_f: tuple[tuple[FFT_MODE_TYPE, int, float], np.ndarray] = \
425
+ ('onesided', -1, 1.), np.ndarray([])
422
426
 
423
427
  def __init__(self, win: np.ndarray, hop: int, fs: float, *,
424
428
  fft_mode: FFT_MODE_TYPE = 'onesided',
@@ -1607,7 +1611,7 @@ class ShortTimeFFT:
1607
1611
  """
1608
1612
  return self.m_num // 2
1609
1613
 
1610
- @cache
1614
+ @cached_property
1611
1615
  def _pre_padding(self) -> tuple[int, int]:
1612
1616
  """Smallest signal index and slice index due to padding.
1613
1617
 
@@ -1617,13 +1621,12 @@ class ShortTimeFFT:
1617
1621
  w2 = self.win.real**2 + self.win.imag**2
1618
1622
  # move window to the left until the overlap with t >= 0 vanishes:
1619
1623
  n0 = -self.m_num_mid
1620
- for q_, n_ in enumerate(range(n0, n0-self.m_num-1, -self.hop)):
1624
+ for p_, n_ in enumerate(range(n0, n0-self.m_num-1, -self.hop)):
1621
1625
  n_next = n_ - self.hop
1622
1626
  if n_next + self.m_num <= 0 or all(w2[n_next:] == 0):
1623
- return n_, -q_
1624
- raise RuntimeError("This is code line should not have been reached!")
1625
- # If this case is reached, it probably means the first slice should be
1626
- # returned, i.e.: return n0, 0
1627
+ return n_, -p_
1628
+ # Make the linter happy:
1629
+ raise RuntimeError("This code line should never run! Please file a bug.")
1627
1630
 
1628
1631
  @property
1629
1632
  def k_min(self) -> int:
@@ -1646,7 +1649,7 @@ class ShortTimeFFT:
1646
1649
  upper_border_begin: Where post-padding effects start.
1647
1650
  ShortTimeFFT: Class this property belongs to.
1648
1651
  """
1649
- return self._pre_padding()[0]
1652
+ return self._pre_padding[0]
1650
1653
 
1651
1654
  @property
1652
1655
  def p_min(self) -> int:
@@ -1671,9 +1674,8 @@ class ShortTimeFFT:
1671
1674
  p_range: Determine and validate slice index range.
1672
1675
  ShortTimeFFT: Class this property belongs to.
1673
1676
  """
1674
- return self._pre_padding()[1]
1677
+ return self._pre_padding[1]
1675
1678
 
1676
- @lru_cache(maxsize=256)
1677
1679
  def _post_padding(self, n: int) -> tuple[int, int]:
1678
1680
  """Largest signal index and slice index due to padding.
1679
1681
 
@@ -1681,9 +1683,17 @@ class ShortTimeFFT:
1681
1683
  ----------
1682
1684
  n : int
1683
1685
  Number of samples of input signal (must be ≥ half of the window length).
1686
+
1687
+ Notes
1688
+ -----
1689
+ Note that the return values are cached together with the parameter `n` to avoid
1690
+ unnecessary recalculations.
1684
1691
  """
1685
1692
  if not (n >= (m2p := self.m_num - self.m_num_mid)):
1686
1693
  raise ValueError(f"Parameter n must be >= ceil(m_num/2) = {m2p}!")
1694
+ last_arg, last_return_value = self._cache_post_padding
1695
+ if n == last_arg: # use cached value:
1696
+ return last_return_value
1687
1697
  w2 = self.win.real**2 + self.win.imag**2
1688
1698
  # move window to the right until the overlap for t < t[n] vanishes:
1689
1699
  q1 = n // self.hop # last slice index with t[p1] <= t[n]
@@ -1691,15 +1701,17 @@ class ShortTimeFFT:
1691
1701
  for q_, k_ in enumerate(range(k1, n+self.m_num, self.hop), start=q1):
1692
1702
  n_next = k_ + self.hop
1693
1703
  if n_next >= n or all(w2[:n-n_next] == 0):
1694
- return k_ + self.m_num, q_ + 1
1695
- raise RuntimeError("This is code line should not have been reached!")
1704
+ return_value = k_ + self.m_num, q_ + 1
1705
+ self._cache_post_padding = n, return_value
1706
+ return return_value
1707
+ raise RuntimeError("This code line should never run! Please file a bug.")
1696
1708
  # If this case is reached, it probably means the last slice should be
1697
1709
  # returned, i.e.: return k1 + self.m_num - self.m_num_mid, q1 + 1
1698
1710
 
1699
1711
  def k_max(self, n: int) -> int:
1700
1712
  """First sample index after signal end not touched by a time slice.
1701
1713
 
1702
- `k_max` - 1 is the largest sample index of the slice `p_max` for a
1714
+ `k_max` - 1 is the largest sample index of the slice `p_max` - 1 for a
1703
1715
  given input signal of `n` samples.
1704
1716
  A detailed example is provided in the :ref:`tutorial_stft_sliding_win`
1705
1717
  section of the :ref:`user_guide`.
@@ -1785,7 +1797,6 @@ class ShortTimeFFT:
1785
1797
  upper_border_begin: Where post-padding effects start.
1786
1798
  ShortTimeFFT: Class this property belongs to.
1787
1799
  """
1788
- # not using @cache decorator due to MyPy limitations
1789
1800
  if self._lower_border_end is not None:
1790
1801
  return self._lower_border_end
1791
1802
 
@@ -1801,7 +1812,6 @@ class ShortTimeFFT:
1801
1812
  self._lower_border_end = (0, max(self.p_min, 0)) # ends at first slice
1802
1813
  return self._lower_border_end
1803
1814
 
1804
- @lru_cache(maxsize=256)
1805
1815
  def upper_border_begin(self, n: int) -> tuple[int, int]:
1806
1816
  """First signal index and first slice index affected by post-padding.
1807
1817
 
@@ -1823,6 +1833,11 @@ class ShortTimeFFT:
1823
1833
  p_ub : int
1824
1834
  Lowest index of time slice of which the end sticks out past the signal end.
1825
1835
 
1836
+ Notes
1837
+ -----
1838
+ Note that the return values are cached together with the parameter `n` to avoid
1839
+ unnecessary recalculations.
1840
+
1826
1841
  See Also
1827
1842
  --------
1828
1843
  k_min: The smallest possible signal index.
@@ -1836,6 +1851,9 @@ class ShortTimeFFT:
1836
1851
  """
1837
1852
  if not (n >= (m2p := self.m_num - self.m_num_mid)):
1838
1853
  raise ValueError(f"Parameter n must be >= ceil(m_num/2) = {m2p}!")
1854
+ last_arg, last_return_value = self._cache_upper_border_begin
1855
+ if n == last_arg: # use cached value:
1856
+ return last_return_value
1839
1857
  w2 = self.win.real**2 + self.win.imag**2
1840
1858
  q2 = n // self.hop + 1 # first t[q] >= t[n]
1841
1859
  q1 = max((n-self.m_num) // self.hop - 1, -1)
@@ -1843,8 +1861,11 @@ class ShortTimeFFT:
1843
1861
  for q_ in range(q2, q1, -1):
1844
1862
  k_ = q_ * self.hop + (self.m_num - self.m_num_mid)
1845
1863
  if k_ <= n or all(w2[n-k_:] == 0):
1846
- return (q_ + 1) * self.hop - self.m_num_mid, q_ + 1
1847
- return 0, 0 # border starts at first slice
1864
+ return_value = (q_ + 1) * self.hop - self.m_num_mid, q_ + 1
1865
+ self. _cache_upper_border_begin = n, return_value
1866
+ return return_value
1867
+ # make linter happy:
1868
+ raise RuntimeError("This code line should never run! Please file a bug.")
1848
1869
 
1849
1870
  @property
1850
1871
  def delta_t(self) -> float:
@@ -1912,7 +1933,6 @@ class ShortTimeFFT:
1912
1933
  f"does not hold for signal length {n=}!")
1913
1934
  return p0_, p1_
1914
1935
 
1915
- @lru_cache(maxsize=1)
1916
1936
  def t(self, n: int, p0: int | None = None, p1: int | None = None,
1917
1937
  k_offset: int = 0) -> np.ndarray:
1918
1938
  """Times of STFT for an input signal with `n` samples.
@@ -1934,6 +1954,10 @@ class ShortTimeFFT:
1934
1954
  k_offset
1935
1955
  Index of first sample (t = 0) in `x`.
1936
1956
 
1957
+ Notes
1958
+ -----
1959
+ Note that the returned array is cached together with the method's call
1960
+ parameters to avoid unnecessary recalculations.
1937
1961
 
1938
1962
  See Also
1939
1963
  --------
@@ -1944,8 +1968,18 @@ class ShortTimeFFT:
1944
1968
  fs: Sampling frequency (being ``1/T``)
1945
1969
  ShortTimeFFT: Class this method belongs to.
1946
1970
  """
1971
+ if not (n > 0 and isinstance(n, int | np.integer)):
1972
+ raise ValueError(f"Parameter {n=} is not a positive integer!")
1973
+ args = n, p0, p1, k_offset, self.T # since `self.T` is mutable, it's needed too
1974
+ last_args, last_return_value = self._cache_t
1975
+ if args == last_args: # use cached value:
1976
+ return last_return_value
1977
+
1947
1978
  p0, p1 = self.p_range(n, p0, p1)
1948
- return np.arange(p0, p1) * self.delta_t + k_offset * self.T
1979
+ return_value = np.arange(p0, p1) * self.delta_t + k_offset * self.T
1980
+
1981
+ self._cache_t = args, return_value
1982
+ return return_value
1949
1983
 
1950
1984
  def nearest_k_p(self, k: int, left: bool = True) -> int:
1951
1985
  """Return nearest sample index k_p for which t[k_p] == t[p] holds.
@@ -2022,6 +2056,7 @@ class ShortTimeFFT:
2022
2056
  """Frequencies values of the STFT.
2023
2057
 
2024
2058
  A 1d array of length `f_pts` with `delta_f` spaced entries is returned.
2059
+ This array is calculated lazily.
2025
2060
 
2026
2061
  See Also
2027
2062
  --------
@@ -2030,15 +2065,22 @@ class ShortTimeFFT:
2030
2065
  mfft: Length of the input for FFT used.
2031
2066
  ShortTimeFFT: Class this property belongs to.
2032
2067
  """
2068
+ last_state, last_return_value = self._cache_f
2069
+ current_state = self.fft_mode, self.mfft, self.T
2070
+ if current_state == last_state: # use cached value:
2071
+ return last_return_value
2072
+
2033
2073
  if self.fft_mode in {'onesided', 'onesided2X'}:
2034
- return fft_lib.rfftfreq(self.mfft, self.T)
2074
+ return_value = fft_lib.rfftfreq(self.mfft, self.T)
2035
2075
  elif self.fft_mode == 'twosided':
2036
- return fft_lib.fftfreq(self.mfft, self.T)
2076
+ return_value = fft_lib.fftfreq(self.mfft, self.T)
2037
2077
  elif self.fft_mode == 'centered':
2038
- return fft_lib.fftshift(fft_lib.fftfreq(self.mfft, self.T))
2039
- # This should never happen but makes the Linters happy:
2040
- fft_modes = get_args(FFT_MODE_TYPE)
2041
- raise RuntimeError(f"{self.fft_mode=} not in {fft_modes}!")
2078
+ return_value = fft_lib.fftshift(fft_lib.fftfreq(self.mfft, self.T))
2079
+ else: # This should never happen but makes the Linters happy:
2080
+ fft_modes = get_args(FFT_MODE_TYPE)
2081
+ raise RuntimeError(f"{self.fft_mode=} not in {fft_modes}!")
2082
+ self._cache_f = current_state, return_value
2083
+ return return_value
2042
2084
 
2043
2085
  def _fft_func(self, x: np.ndarray) -> np.ndarray:
2044
2086
  """FFT based on the `fft_mode`, `mfft`, `scaling` and `phase_shift`
@@ -2095,8 +2137,7 @@ class ShortTimeFFT:
2095
2137
  Xc[..., 1:q1] /= fac
2096
2138
  x = fft_lib.irfft(Xc, n=self.mfft, axis=-1)
2097
2139
  else: # This should never happen but makes the Linter happy:
2098
- error_str = f"{self.fft_mode=} not in {get_args(FFT_MODE_TYPE)}!"
2099
- raise RuntimeError(error_str)
2140
+ raise RuntimeError(f"{self.fft_mode=} not in {get_args(FFT_MODE_TYPE)}!")
2100
2141
 
2101
2142
  if self.phase_shift is None:
2102
2143
  return x[..., :self.m_num]
Binary file
@@ -243,11 +243,11 @@ def lombscargle(
243
243
  )
244
244
 
245
245
  # weight vector must sum to 1
246
- weights *= 1.0 / weights.sum()
246
+ weights = weights * (1.0 / weights.sum())
247
247
 
248
248
  # if requested, perform precenter
249
249
  if precenter:
250
- y -= y.mean()
250
+ y = y - y.mean()
251
251
 
252
252
  # transform arrays
253
253
  # row vector
@@ -382,6 +382,19 @@ class TestTf2Sos:
382
382
  sos2 = tf2sos(b, a, analog=analog)
383
383
  assert_array_almost_equal(sos, sos2, decimal=4)
384
384
 
385
+ def test_gh_23221(self):
386
+ # Tests that this tf2sos call below does not produce ComplexWarnings
387
+ # This test is specific for scipy==1.16.0: later scipy versions do not produce
388
+ # the warning.
389
+ with suppress_warnings():
390
+ warnings.simplefilter("error")
391
+ tf2sos([0.21860986786301265, -0.4372197357260253, -0.2186098678630126,
392
+ 0.8744394714520509, -0.21860986786301248, -0.4372197357260253,
393
+ 0.21860986786301265],
394
+ [1., -4.18323041786553, 6.829924151626914, -5.407777865686045,
395
+ 2.0773105450802336, -0.33482732571537893, 0.0186009178695853 ]
396
+ )
397
+
385
398
 
386
399
  @skip_xp_backends(
387
400
  cpu_only=True, reason="XXX zpk2sos is numpy-only", exceptions=['cupy']
@@ -754,6 +767,12 @@ class TestFreqz:
754
767
  xp_assert_equal(w, xp.asarray([0. , 0.1]))
755
768
  xp_assert_equal(h, xp.asarray([1.+0.j, 1.+0.j]))
756
769
 
770
+ def test_gh_23277(self):
771
+ # backwards compatibility: `w` array must be real, not complex
772
+ filt = [0.5 + 0.0j, 0.5 + 0.0j]
773
+ w, _ = freqz(filt, worN=8)
774
+ assert w.dtype == np.float64
775
+
757
776
  def test_basic(self, xp):
758
777
  w, h = freqz(xp.asarray([1.0]), worN=8)
759
778
  assert_array_almost_equal(w, xp.pi * xp.arange(8, dtype=w.dtype) / 8.)
@@ -546,6 +546,11 @@ class TestRemez:
546
546
  with pytest.raises(ValueError, match="Sampling.*single scalar"):
547
547
  remez(11, .1, 1, fs=np.array([10, 20]))
548
548
 
549
+ def test_gh_23266(self, xp):
550
+ bands = xp.asarray([0.0, 0.2, 0.3, 0.5])
551
+ desired = xp.asarray([1.0, 0.0])
552
+ weight = xp.asarray([1.0, 2.0])
553
+ remez(21, bands, desired, weight=weight)
549
554
 
550
555
 
551
556
  @skip_xp_backends(cpu_only=True, reason="lstsq")
@@ -531,11 +531,15 @@ def test_border_values():
531
531
  assert SFT.p_max(10) == 4
532
532
  assert SFT.k_max(10) == 16
533
533
  assert SFT.upper_border_begin(10) == (4, 2)
534
+ assert SFT.upper_border_begin(10) == (4, 2) # needed to test caching
534
535
  # Raise exceptions:
535
536
  with pytest.raises(ValueError, match="^Parameter n must be"):
536
537
  SFT.upper_border_begin(3)
537
538
  with pytest.raises(ValueError, match="^Parameter n must be"):
538
539
  SFT._post_padding(3)
540
+ with pytest.raises(RuntimeError):
541
+ SFT._hop = -1 # illegal hop interval
542
+ SFT.upper_border_begin(8)
539
543
 
540
544
  def test_border_values_exotic():
541
545
  """Ensure that the border calculations are correct for windows with
@@ -573,6 +577,11 @@ def test_t():
573
577
  SFT.fs = 1/8
574
578
  assert SFT.fs == 1/8
575
579
  assert SFT.T == 8
580
+ with pytest.raises(ValueError):
581
+ # noinspection PyTypeChecker
582
+ SFT.t(1.5) # only integers allowed
583
+ with pytest.raises(ValueError):
584
+ SFT.t(-1) # only positive `n` allowed
576
585
 
577
586
 
578
587
  @pytest.mark.parametrize('fft_mode, f',
@@ -1295,7 +1295,7 @@ class TestMedFilt:
1295
1295
  # us into wrong memory if used (but it does not need to be used)
1296
1296
  dummy = xp.arange(10, dtype=xp.float64)
1297
1297
  a = dummy[5:6]
1298
- a.strides = 16
1298
+ a = np.lib.stride_tricks.as_strided(a, strides=(16,))
1299
1299
  xp_assert_close(signal.medfilt(a, 1), xp.asarray([5.]))
1300
1300
 
1301
1301
  @skip_xp_backends(
@@ -2084,7 +2084,9 @@ class _TestLinearFilter:
2084
2084
  a = self.convert_dtype(a, xp)
2085
2085
  x = self.convert_dtype(x, xp)
2086
2086
  zi = self.convert_dtype(zi, xp)
2087
- assert_raises(ValueError, lfilter, b, a, x, axis, zi)
2087
+ # NOTE: MemoryError is currently allowed below because of:
2088
+ # https://github.com/numpy/numpy/issues/29721
2089
+ assert_raises((ValueError, MemoryError), lfilter, b, a, x, axis, zi)
2088
2090
 
2089
2091
  @skip_xp_backends('cupy', reason='cupy does not raise')
2090
2092
  def test_bad_size_zi(self, xp):
@@ -2134,7 +2136,11 @@ class _TestLinearFilter:
2134
2136
  self.base_bad_size_zi([1, 1, 1], [1], x2, 0, [[0, 1, 2, 3], [4, 5, 6, 7]], xp)
2135
2137
 
2136
2138
  self.base_bad_size_zi([1], [1, 1], x2, 0, [0, 1, 2], xp)
2137
- self.base_bad_size_zi([1], [1, 1], x2, 0, [[[0, 1, 2]]], xp)
2139
+ # this case is disabled on the release branch
2140
+ # because of:
2141
+ # https://github.com/scipy/scipy/pull/23543#issuecomment-3276286172
2142
+ # https://github.com/numpy/numpy/issues/29721
2143
+ #self.base_bad_size_zi([1], [1, 1], x2, 0, [[[0, 1, 2]]], xp)
2138
2144
  self.base_bad_size_zi([1], [1, 1], x2, 0, [[0], [1], [2]], xp)
2139
2145
  self.base_bad_size_zi([1], [1, 1], x2, 0, [[0, 1]], xp)
2140
2146
  self.base_bad_size_zi([1], [1, 1], x2, 0, [[0, 1, 2, 3]], xp)
@@ -1055,7 +1055,6 @@ class TestLombscargle:
1055
1055
  delta = f[1] - f[0]
1056
1056
  assert(w - f[np.argmax(P)] < (delta/2.))
1057
1057
 
1058
-
1059
1058
  def test_amplitude(self):
1060
1059
  # Test if height of peak in unnormalized Lomb-Scargle periodogram
1061
1060
  # corresponds to amplitude of the generated input signal.
@@ -1503,6 +1502,45 @@ class TestLombscargle:
1503
1502
 
1504
1503
  lombscargle(t, y, freqs)
1505
1504
 
1505
+ def test_input_mutation(self):
1506
+ # this tests for mutation of the input arrays
1507
+ # https://github.com/scipy/scipy/issues/23474
1508
+
1509
+ # Input parameters
1510
+ ampl = 2.
1511
+ w = 1.
1512
+ phi = 0.5 * np.pi
1513
+ nin = 100
1514
+ nout = 1000
1515
+ p = 0.7 # Fraction of points to select
1516
+
1517
+ # Randomly select a fraction of an array with timesteps
1518
+ rng = np.random.default_rng()
1519
+ r = rng.random(nin)
1520
+ t = np.linspace(0.01*np.pi, 10.*np.pi, nin)[r >= p]
1521
+
1522
+ # Plot a sine wave for the selected times
1523
+ y = ampl * np.sin(w*t + phi)
1524
+
1525
+ # Define the array of frequencies for which to compute the periodogram
1526
+ f = np.linspace(0.01, 10., nout)
1527
+
1528
+ weights = np.ones_like(y)
1529
+
1530
+ # create original copies before passing
1531
+ t_org = t.copy()
1532
+ y_org = y.copy()
1533
+ f_org = f.copy()
1534
+ weights_org = weights.copy()
1535
+
1536
+ lombscargle(t, y, f, precenter=True, weights=weights)
1537
+
1538
+ # check all 4 array inputs
1539
+ assert_array_equal(t, t_org)
1540
+ assert_array_equal(y, y_org)
1541
+ assert_array_equal(f, f_org)
1542
+ assert_array_equal(weights, weights_org)
1543
+
1506
1544
 
1507
1545
  class TestSTFT:
1508
1546
  @pytest.mark.thread_unsafe
scipy/sparse/_base.py CHANGED
@@ -494,7 +494,10 @@ class _spbase(SparseABC):
494
494
  return self._mul_scalar(other)
495
495
 
496
496
  if self.ndim < 3:
497
- return self.tocsr()._multiply_2d_with_broadcasting(other)
497
+ try:
498
+ return self._multiply_2d_with_broadcasting(other)
499
+ except AttributeError:
500
+ return self.tocsr()._multiply_2d_with_broadcasting(other)
498
501
 
499
502
  if not (issparse(other) or isdense(other)):
500
503
  # If it's a list or whatever, treat it like an array
@@ -1624,6 +1624,9 @@ class _TestCommon:
1624
1624
  B = array([[0,7,0],[0,-4,0]])
1625
1625
  Asp = self.spcreator(A)
1626
1626
  Bsp = self.spcreator(B)
1627
+ # check output format
1628
+ out_fmt = Asp.format if Asp.format in ('csc', 'dia', 'bsr') else 'csr'
1629
+ assert (Asp.multiply(Bsp)).format == out_fmt
1627
1630
  assert_almost_equal(Asp.multiply(Bsp).toarray(), A*B) # sparse/sparse
1628
1631
  assert_almost_equal(Asp.multiply(B).toarray(), A*B) # sparse/dense
1629
1632
 
Binary file