scipy 1.15.2__cp313-cp313-macosx_10_13_x86_64.whl → 1.15.3__cp313-cp313-macosx_10_13_x86_64.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 (125) hide show
  1. scipy/__config__.py +6 -6
  2. scipy/_lib/_array_api.py +11 -0
  3. scipy/_lib/_ccallback_c.cpython-313-darwin.so +0 -0
  4. scipy/_lib/_test_ccallback.cpython-313-darwin.so +0 -0
  5. scipy/_lib/tests/test_array_api.py +5 -1
  6. scipy/cluster/_hierarchy.cpython-313-darwin.so +0 -0
  7. scipy/cluster/_optimal_leaf_ordering.cpython-313-darwin.so +0 -0
  8. scipy/cluster/_vq.cpython-313-darwin.so +0 -0
  9. scipy/fftpack/convolve.cpython-313-darwin.so +0 -0
  10. scipy/integrate/_dop.cpython-313-darwin.so +0 -0
  11. scipy/integrate/_ivp/common.py +3 -3
  12. scipy/integrate/_ivp/ivp.py +9 -2
  13. scipy/integrate/_ivp/tests/test_ivp.py +19 -0
  14. scipy/integrate/_lsoda.cpython-313-darwin.so +0 -0
  15. scipy/integrate/_odepack.cpython-313-darwin.so +0 -0
  16. scipy/integrate/_quadpack.cpython-313-darwin.so +0 -0
  17. scipy/integrate/_tanhsinh.py +14 -12
  18. scipy/integrate/_test_odeint_banded.cpython-313-darwin.so +0 -0
  19. scipy/integrate/_vode.cpython-313-darwin.so +0 -0
  20. scipy/integrate/tests/test_tanhsinh.py +10 -0
  21. scipy/interpolate/_bspl.cpython-313-darwin.so +0 -0
  22. scipy/interpolate/_dfitpack.cpython-313-darwin.so +0 -0
  23. scipy/interpolate/_dierckx.cpython-313-darwin.so +0 -0
  24. scipy/interpolate/_rgi_cython.cpython-313-darwin.so +0 -0
  25. scipy/io/_test_fortran.cpython-313-darwin.so +0 -0
  26. scipy/io/matlab/_mio5_utils.cpython-313-darwin.so +0 -0
  27. scipy/io/matlab/_mio_utils.cpython-313-darwin.so +0 -0
  28. scipy/linalg/_cythonized_array_utils.cpython-313-darwin.so +0 -0
  29. scipy/linalg/_decomp_interpolative.cpython-313-darwin.so +0 -0
  30. scipy/linalg/_decomp_lu_cython.cpython-313-darwin.so +0 -0
  31. scipy/linalg/_decomp_update.cpython-313-darwin.so +0 -0
  32. scipy/linalg/_fblas.cpython-313-darwin.so +0 -0
  33. scipy/linalg/_flapack.cpython-313-darwin.so +0 -0
  34. scipy/linalg/_matfuncs_expm.cpython-313-darwin.so +0 -0
  35. scipy/linalg/_matfuncs_sqrtm_triu.cpython-313-darwin.so +0 -0
  36. scipy/linalg/_solve_toeplitz.cpython-313-darwin.so +0 -0
  37. scipy/linalg/cython_blas.cpython-313-darwin.so +0 -0
  38. scipy/linalg/cython_lapack.cpython-313-darwin.so +0 -0
  39. scipy/linalg/tests/test_interpolative.py +17 -0
  40. scipy/ndimage/_cytest.cpython-313-darwin.so +0 -0
  41. scipy/ndimage/_nd_image.cpython-313-darwin.so +0 -0
  42. scipy/ndimage/_ndimage_api.py +2 -1
  43. scipy/ndimage/_ni_label.cpython-313-darwin.so +0 -0
  44. scipy/ndimage/_rank_filter_1d.cpython-313-darwin.so +0 -0
  45. scipy/ndimage/tests/test_filters.py +14 -0
  46. scipy/odr/__odrpack.cpython-313-darwin.so +0 -0
  47. scipy/optimize/_bglu_dense.cpython-313-darwin.so +0 -0
  48. scipy/optimize/_bracket.py +35 -8
  49. scipy/optimize/_cobyla.cpython-313-darwin.so +0 -0
  50. scipy/optimize/_cython_nnls.cpython-313-darwin.so +0 -0
  51. scipy/optimize/_highspy/_highs_wrapper.py +6 -4
  52. scipy/optimize/_lbfgsb.cpython-313-darwin.so +0 -0
  53. scipy/optimize/_linprog_highs.py +9 -9
  54. scipy/optimize/_linprog_util.py +4 -3
  55. scipy/optimize/_minpack.cpython-313-darwin.so +0 -0
  56. scipy/optimize/_moduleTNC.cpython-313-darwin.so +0 -0
  57. scipy/optimize/_slsqp.cpython-313-darwin.so +0 -0
  58. scipy/optimize/_trlib/_trlib.cpython-313-darwin.so +0 -0
  59. scipy/optimize/tests/test_bracket.py +35 -0
  60. scipy/signal/_peak_finding_utils.cpython-313-darwin.so +0 -0
  61. scipy/signal/_short_time_fft.py +34 -6
  62. scipy/signal/_signaltools.py +4 -1
  63. scipy/signal/_sosfilt.cpython-313-darwin.so +0 -0
  64. scipy/signal/_upfirdn_apply.cpython-313-darwin.so +0 -0
  65. scipy/signal/tests/_scipy_spectral_test_shim.py +3 -11
  66. scipy/signal/tests/test_short_time_fft.py +10 -2
  67. scipy/signal/tests/test_signaltools.py +5 -0
  68. scipy/sparse/_base.py +2 -15
  69. scipy/sparse/_compressed.py +0 -3
  70. scipy/sparse/_csparsetools.cpython-313-darwin.so +0 -0
  71. scipy/sparse/_dia.py +0 -3
  72. scipy/sparse/csgraph/_flow.cpython-313-darwin.so +0 -0
  73. scipy/sparse/csgraph/_matching.cpython-313-darwin.so +0 -0
  74. scipy/sparse/csgraph/_min_spanning_tree.cpython-313-darwin.so +0 -0
  75. scipy/sparse/csgraph/_reordering.cpython-313-darwin.so +0 -0
  76. scipy/sparse/csgraph/_shortest_path.cpython-313-darwin.so +0 -0
  77. scipy/sparse/csgraph/_tools.cpython-313-darwin.so +0 -0
  78. scipy/sparse/csgraph/_traversal.cpython-313-darwin.so +0 -0
  79. scipy/sparse/linalg/_dsolve/_superlu.cpython-313-darwin.so +0 -0
  80. scipy/sparse/linalg/_eigen/arpack/_arpack.cpython-313-darwin.so +0 -0
  81. scipy/sparse/linalg/_eigen/arpack/arpack.py +5 -3
  82. scipy/sparse/linalg/_expm_multiply.py +8 -3
  83. scipy/sparse/linalg/_interface.py +12 -8
  84. scipy/sparse/linalg/_isolve/_gcrotmk.py +2 -1
  85. scipy/sparse/linalg/_propack/_cpropack.cpython-313-darwin.so +0 -0
  86. scipy/sparse/linalg/_propack/_dpropack.cpython-313-darwin.so +0 -0
  87. scipy/sparse/linalg/_propack/_spropack.cpython-313-darwin.so +0 -0
  88. scipy/sparse/linalg/_propack/_zpropack.cpython-313-darwin.so +0 -0
  89. scipy/sparse/linalg/tests/test_expm_multiply.py +10 -0
  90. scipy/sparse/linalg/tests/test_interface.py +35 -0
  91. scipy/sparse/linalg/tests/test_pydata_sparse.py +4 -0
  92. scipy/sparse/tests/test_base.py +12 -0
  93. scipy/sparse/tests/test_common1d.py +11 -6
  94. scipy/spatial/_ckdtree.cpython-313-darwin.so +0 -0
  95. scipy/spatial/_hausdorff.cpython-313-darwin.so +0 -0
  96. scipy/spatial/_qhull.cpython-313-darwin.so +0 -0
  97. scipy/spatial/_voronoi.cpython-313-darwin.so +0 -0
  98. scipy/spatial/tests/test_qhull.py +99 -0
  99. scipy/spatial/transform/_rotation.cpython-313-darwin.so +0 -0
  100. scipy/spatial/transform/tests/test_rotation.py +181 -10
  101. scipy/special/_ellip_harm_2.cpython-313-darwin.so +0 -0
  102. scipy/special/_logsumexp.py +21 -16
  103. scipy/special/_specfun.cpython-313-darwin.so +0 -0
  104. scipy/special/_special_ufuncs.cpython-313-darwin.so +0 -0
  105. scipy/special/_ufuncs.cpython-313-darwin.so +0 -0
  106. scipy/special/_ufuncs_cxx.cpython-313-darwin.so +0 -0
  107. scipy/special/cython_special.cpython-313-darwin.so +0 -0
  108. scipy/special/tests/test_hyp2f1.py +21 -0
  109. scipy/special/tests/test_logsumexp.py +14 -0
  110. scipy/special/xsf/hyp2f1.h +3 -3
  111. scipy/stats/_ansari_swilk_statistics.cpython-313-darwin.so +0 -0
  112. scipy/stats/_biasedurn.cpython-313-darwin.so +0 -0
  113. scipy/stats/_continuous_distns.py +25 -15
  114. scipy/stats/_mvn.cpython-313-darwin.so +0 -0
  115. scipy/stats/_qmc_cy.cpython-313-darwin.so +0 -0
  116. scipy/stats/_rcont/rcont.cpython-313-darwin.so +0 -0
  117. scipy/stats/_sobol.cpython-313-darwin.so +0 -0
  118. scipy/stats/_stats.cpython-313-darwin.so +0 -0
  119. scipy/stats/_unuran/unuran_wrapper.cpython-313-darwin.so +0 -0
  120. scipy/stats/tests/test_distributions.py +22 -0
  121. scipy/version.py +2 -2
  122. {scipy-1.15.2.dist-info → scipy-1.15.3.dist-info}/METADATA +2 -2
  123. {scipy-1.15.2.dist-info → scipy-1.15.3.dist-info}/RECORD +125 -125
  124. {scipy-1.15.2.dist-info → scipy-1.15.3.dist-info}/LICENSE.txt +0 -0
  125. {scipy-1.15.2.dist-info → scipy-1.15.3.dist-info}/WHEEL +0 -0
scipy/__config__.py CHANGED
@@ -59,7 +59,7 @@ CONFIG = _cleanup(
59
59
  },
60
60
  "pythran": {
61
61
  "version": r"0.17.0",
62
- "include directory": r"../../../../../../private/var/folders/db/x5_t5x655c9_rftmy0wp2t2w0000gn/T/pip-build-env-8n0tpddg/overlay/lib/python3.13/site-packages/pythran"
62
+ "include directory": r"../../../../../../private/var/folders/hk/wjxbksl168x1c7_ycgqjys980000gn/T/pip-build-env-vg4sjn7s/overlay/lib/python3.13/site-packages/pythran"
63
63
  },
64
64
  },
65
65
  "Machine Information": {
@@ -83,8 +83,8 @@ CONFIG = _cleanup(
83
83
  "found": bool("True".lower().replace('false', '')),
84
84
  "version": "0.3.28",
85
85
  "detection method": "pkgconfig",
86
- "include directory": r"/private/var/folders/db/x5_t5x655c9_rftmy0wp2t2w0000gn/T/cibw-run-7tr0a20r/cp313-macosx_x86_64/build/venv/lib/python3.13/site-packages/scipy_openblas32/include",
87
- "lib directory": r"/private/var/folders/db/x5_t5x655c9_rftmy0wp2t2w0000gn/T/cibw-run-7tr0a20r/cp313-macosx_x86_64/build/venv/lib/python3.13/site-packages/scipy_openblas32/lib",
86
+ "include directory": r"/private/var/folders/hk/wjxbksl168x1c7_ycgqjys980000gn/T/cibw-run-hu0ekim5/cp313-macosx_x86_64/build/venv/lib/python3.13/site-packages/scipy_openblas32/include",
87
+ "lib directory": r"/private/var/folders/hk/wjxbksl168x1c7_ycgqjys980000gn/T/cibw-run-hu0ekim5/cp313-macosx_x86_64/build/venv/lib/python3.13/site-packages/scipy_openblas32/lib",
88
88
  "openblas configuration": r"OpenBLAS 0.3.28 DYNAMIC_ARCH NO_AFFINITY Sandybridge MAX_THREADS=64",
89
89
  "pc file directory": r"/Users/runner/work/scipy/scipy",
90
90
  },
@@ -93,8 +93,8 @@ CONFIG = _cleanup(
93
93
  "found": bool("True".lower().replace('false', '')),
94
94
  "version": "0.3.28",
95
95
  "detection method": "pkgconfig",
96
- "include directory": r"/private/var/folders/db/x5_t5x655c9_rftmy0wp2t2w0000gn/T/cibw-run-7tr0a20r/cp313-macosx_x86_64/build/venv/lib/python3.13/site-packages/scipy_openblas32/include",
97
- "lib directory": r"/private/var/folders/db/x5_t5x655c9_rftmy0wp2t2w0000gn/T/cibw-run-7tr0a20r/cp313-macosx_x86_64/build/venv/lib/python3.13/site-packages/scipy_openblas32/lib",
96
+ "include directory": r"/private/var/folders/hk/wjxbksl168x1c7_ycgqjys980000gn/T/cibw-run-hu0ekim5/cp313-macosx_x86_64/build/venv/lib/python3.13/site-packages/scipy_openblas32/include",
97
+ "lib directory": r"/private/var/folders/hk/wjxbksl168x1c7_ycgqjys980000gn/T/cibw-run-hu0ekim5/cp313-macosx_x86_64/build/venv/lib/python3.13/site-packages/scipy_openblas32/lib",
98
98
  "openblas configuration": r"OpenBLAS 0.3.28 DYNAMIC_ARCH NO_AFFINITY Sandybridge MAX_THREADS=64",
99
99
  "pc file directory": r"/Users/runner/work/scipy/scipy",
100
100
  },
@@ -106,7 +106,7 @@ CONFIG = _cleanup(
106
106
  },
107
107
  },
108
108
  "Python Information": {
109
- "path": r"/private/var/folders/db/x5_t5x655c9_rftmy0wp2t2w0000gn/T/cibw-run-7tr0a20r/cp313-macosx_x86_64/build/venv/bin/python",
109
+ "path": r"/private/var/folders/hk/wjxbksl168x1c7_ycgqjys980000gn/T/cibw-run-hu0ekim5/cp313-macosx_x86_64/build/venv/bin/python",
110
110
  "version": "3.13",
111
111
  },
112
112
  }
scipy/_lib/_array_api.py CHANGED
@@ -593,3 +593,14 @@ def xp_float_to_complex(arr: Array, xp: ModuleType | None = None) -> Array:
593
593
  arr = xp.astype(arr, xp.complex128)
594
594
 
595
595
  return arr
596
+
597
+
598
+ def xp_default_dtype(xp):
599
+ """Query the namespace-dependent default floating-point dtype.
600
+ """
601
+ if is_torch(xp):
602
+ # historically, we allow pytorch to keep its default of float32
603
+ return xp.get_default_dtype()
604
+ else:
605
+ # we default to float64
606
+ return xp.float64
@@ -4,7 +4,7 @@ import pytest
4
4
  from scipy.conftest import array_api_compatible
5
5
  from scipy._lib._array_api import (
6
6
  _GLOBAL_CONFIG, array_namespace, _asarray, xp_copy, xp_assert_equal, is_numpy,
7
- np_compat,
7
+ np_compat, xp_default_dtype
8
8
  )
9
9
  from scipy._lib._array_api_no_0d import xp_assert_equal as xp_assert_equal_no_0d
10
10
 
@@ -185,3 +185,7 @@ class TestArrayAPI:
185
185
  # scalars-vs-0d passes (if values match) also with regular python objects
186
186
  xp_assert_equal_no_0d(0., xp.asarray(0.))
187
187
  xp_assert_equal_no_0d(42, xp.asarray(42))
188
+
189
+ @array_api_compatible
190
+ def test_default_dtype(self, xp):
191
+ assert xp_default_dtype(xp) == xp.asarray(1.).dtype
Binary file
Binary file
@@ -65,7 +65,7 @@ def norm(x):
65
65
  return np.linalg.norm(x) / x.size ** 0.5
66
66
 
67
67
 
68
- def select_initial_step(fun, t0, y0, t_bound,
68
+ def select_initial_step(fun, t0, y0, t_bound,
69
69
  max_step, f0, direction, order, rtol, atol):
70
70
  """Empirically select a good initial step.
71
71
 
@@ -80,7 +80,7 @@ def select_initial_step(fun, t0, y0, t_bound,
80
80
  y0 : ndarray, shape (n,)
81
81
  Initial value of the dependent variable.
82
82
  t_bound : float
83
- End-point of integration interval; used to ensure that t0+step<=tbound
83
+ End-point of integration interval; used to ensure that t0+step<=tbound
84
84
  and that fun is only evaluated in the interval [t0,tbound]
85
85
  max_step : float
86
86
  Maximum allowable step size.
@@ -112,7 +112,7 @@ def select_initial_step(fun, t0, y0, t_bound,
112
112
  interval_length = abs(t_bound - t0)
113
113
  if interval_length == 0.0:
114
114
  return 0.0
115
-
115
+
116
116
  scale = atol + np.abs(y0) * rtol
117
117
  d0 = norm(y0 / scale)
118
118
  d1 = norm(f0 / scale)
@@ -694,8 +694,15 @@ def solve_ivp(fun, t_span, y0, method='RK45', t_eval=None, dense_output=False,
694
694
  g = g_new
695
695
 
696
696
  if t_eval is None:
697
- ts.append(t)
698
- ys.append(y)
697
+ donot_append = (len(ts) > 1 and
698
+ ts[-1] == t and
699
+ dense_output)
700
+ if not donot_append:
701
+ ts.append(t)
702
+ ys.append(y)
703
+ else:
704
+ if len(interpolants) > 0:
705
+ interpolants.pop()
699
706
  else:
700
707
  # The value in t_eval equal to t will be included.
701
708
  if solver.direction > 0:
@@ -151,6 +151,25 @@ def compute_error(y, y_true, rtol, atol):
151
151
  e = (y - y_true) / (atol + rtol * np.abs(y_true))
152
152
  return np.linalg.norm(e, axis=0) / np.sqrt(e.shape[0])
153
153
 
154
+ def test_duplicate_timestamps():
155
+ def upward_cannon(t, y):
156
+ return [y[1], -9.80665]
157
+
158
+ def hit_ground(t, y):
159
+ return y[0]
160
+
161
+ hit_ground.terminal = True
162
+ hit_ground.direction = -1
163
+
164
+ sol = solve_ivp(upward_cannon, [0, np.inf], [0, 0.01],
165
+ max_step=0.05 * 0.001 / 9.80665,
166
+ events=hit_ground, dense_output=True)
167
+ assert_allclose(sol.sol(0.01), np.asarray([-0.00039033, -0.08806632]),
168
+ rtol=1e-5, atol=1e-8)
169
+ assert_allclose(sol.t_events, np.asarray([[0.00203943]]), rtol=1e-5, atol=1e-8)
170
+ assert_allclose(sol.y_events, [np.asarray([[ 0.0, -0.01 ]])], atol=1e-9)
171
+ assert sol.success
172
+ assert_equal(sol.status, 1)
154
173
 
155
174
  @pytest.mark.thread_unsafe
156
175
  def test_integration():
@@ -98,8 +98,9 @@ def tanhsinh(f, a, b, *, args=(), log=False, maxlevel=None, minlevel=2,
98
98
  Absolute termination tolerance (default: 0) and relative termination
99
99
  tolerance (default: ``eps**0.75``, where ``eps`` is the precision of
100
100
  the result dtype), respectively. Iteration will stop when
101
- ``res.error < atol + rtol * abs(res.df)``. The error estimate is as
102
- described in [1]_ Section 5. While not theoretically rigorous or
101
+ ``res.error < atol`` or ``res.error < res.integral * rtol``. The error
102
+ estimate is as described in [1]_ Section 5 but with a lower bound of
103
+ ``eps * res.integral``. While not theoretically rigorous or
103
104
  conservative, it is said to work well in practice. Must be non-negative
104
105
  and finite if `log` is False, and must be expressed as the log of a
105
106
  non-negative and finite number if `log` is True.
@@ -445,9 +446,9 @@ def tanhsinh(f, a, b, *, args=(), log=False, maxlevel=None, minlevel=2,
445
446
  stop[i] = True
446
447
  else:
447
448
  # Terminate if convergence criterion is met
448
- work.rerr, work.aerr = _estimate_error(work, xp)
449
- i = ((work.rerr < rtol) | (work.rerr + xp_real(work.Sn) < atol) if log
450
- else (work.rerr < rtol) | (work.rerr * xp.abs(work.Sn) < atol))
449
+ rerr, aerr = _estimate_error(work, xp)
450
+ i = (rerr < rtol) | (aerr < atol)
451
+ work.aerr = xp.reshape(xp.astype(aerr, work.dtype), work.Sn.shape)
451
452
  work.status[i] = eim._ECONVERGED
452
453
  stop[i] = True
453
454
 
@@ -772,22 +773,23 @@ def _estimate_error(work, xp):
772
773
  d2 = xp_real(special.logsumexp(xp.stack([work.Sn, Snm2 + work.pi*1j]), axis=0))
773
774
  d3 = log_e1 + xp.max(xp_real(work.fjwj), axis=-1)
774
775
  d4 = work.d4
775
- ds = xp.stack([d1 ** 2 / d2, 2 * d1, d3, d4])
776
+ d5 = log_e1 + xp.real(work.Sn)
777
+ temp = xp.where(d1 > -xp.inf, d1 ** 2 / d2, -xp.inf)
778
+ ds = xp.stack([temp, 2 * d1, d3, d4, d5])
776
779
  aerr = xp.max(ds, axis=0)
777
- rerr = xp.maximum(log_e1, aerr - xp_real(work.Sn))
780
+ rerr = aerr - xp.real(work.Sn)
778
781
  else:
779
782
  # Note: explicit computation of log10 of each of these is unnecessary.
780
783
  d1 = xp.abs(work.Sn - Snm1)
781
784
  d2 = xp.abs(work.Sn - Snm2)
782
785
  d3 = e1 * xp.max(xp.abs(work.fjwj), axis=-1)
783
786
  d4 = work.d4
784
- # If `d1` is 0, no need to warn. This does the right thing.
785
- # with np.errstate(divide='ignore'):
786
- ds = xp.stack([d1**(xp.log(d1)/xp.log(d2)), d1**2, d3, d4])
787
+ d5 = e1 * xp.abs(work.Sn)
788
+ temp = xp.where(d1 > 0, d1**(xp.log(d1)/xp.log(d2)), 0)
789
+ ds = xp.stack([temp, d1**2, d3, d4, d5])
787
790
  aerr = xp.max(ds, axis=0)
788
- rerr = xp.maximum(e1, aerr/xp.abs(work.Sn))
791
+ rerr = aerr/xp.abs(work.Sn)
789
792
 
790
- aerr = xp.reshape(xp.astype(aerr, work.dtype), work.Sn.shape)
791
793
  return rerr, aerr
792
794
 
793
795
 
Binary file
@@ -753,6 +753,16 @@ class TestTanhSinh:
753
753
  x[-1] = 1000
754
754
  _tanhsinh(np.sin, 1, x)
755
755
 
756
+ def test_gh_22681_finite_error(self, xp):
757
+ # gh-22681 noted a case in which the error was NaN on some platforms;
758
+ # check that this does in fact fail in CI.
759
+ a = complex(12, -10)
760
+ b = complex(12, 39)
761
+ def f(t):
762
+ return xp.sin(a * (1 - t) + b * t)
763
+ res = _tanhsinh(f, xp.asarray(0.), xp.asarray(1.), atol=0, rtol=0, maxlevel=10)
764
+ assert xp.isfinite(res.error)
765
+
756
766
 
757
767
  @array_api_compatible
758
768
  @pytest.mark.usefixtures("skip_xp_backends")
Binary file
Binary file
@@ -213,3 +213,20 @@ class TestInterpolativeDecomposition:
213
213
  B = A.copy()
214
214
  interp_decomp(A.T, eps, rand=rand)
215
215
  assert_array_equal(A, B)
216
+
217
+ def test_svd_aslinearoperator_shape_check(self):
218
+ # See gh-issue #22451
219
+ rng = np.random.default_rng(1744580941832515)
220
+ x = rng.uniform(size=[7, 5])
221
+ xl = aslinearoperator(x)
222
+ u, s, v = pymatrixid.svd(xl, 3)
223
+ assert_equal(u.shape, (7, 3))
224
+ assert_equal(s.shape, (3,))
225
+ assert_equal(v.shape, (5, 3))
226
+
227
+ x = rng.uniform(size=[4, 9])
228
+ xl = aslinearoperator(x)
229
+ u, s, v = pymatrixid.svd(xl, 2)
230
+ assert_equal(u.shape, (4, 2))
231
+ assert_equal(s.shape, (2,))
232
+ assert_equal(v.shape, (9, 2))
Binary file
@@ -12,4 +12,5 @@ from ._interpolation import * # noqa: F403
12
12
  from ._measurements import * # noqa: F403
13
13
  from ._morphology import * # noqa: F403
14
14
 
15
- __all__ = [s for s in dir() if not s.startswith('_')]
15
+ # '@' due to pytest bug, scipy/scipy#22236
16
+ __all__ = [s for s in dir() if not s.startswith(('_', '@'))]
@@ -6,6 +6,9 @@ import re
6
6
  import numpy as np
7
7
  import pytest
8
8
  from numpy.testing import suppress_warnings, assert_allclose, assert_array_equal
9
+ from hypothesis import strategies as st
10
+ from hypothesis import given
11
+ import hypothesis.extra.numpy as npst
9
12
  from pytest import raises as assert_raises
10
13
  from scipy import ndimage
11
14
  from scipy._lib._array_api import (
@@ -2904,3 +2907,14 @@ def test_gh_22333():
2904
2907
  expected = [58, 67, 87, 108, 163, 108, 108, 108, 87]
2905
2908
  actual = ndimage.median_filter(x, size=9, mode='constant')
2906
2909
  assert_array_equal(actual, expected)
2910
+
2911
+
2912
+ @given(x=npst.arrays(dtype=np.float64,
2913
+ shape=st.integers(min_value=1, max_value=1000)),
2914
+ size=st.integers(min_value=1, max_value=50),
2915
+ mode=st.sampled_from(["constant", "mirror", "wrap", "reflect",
2916
+ "nearest"]),
2917
+ )
2918
+ def test_gh_22586_crash_property(x, size, mode):
2919
+ # property-based test for median_filter resilience to hard crashing
2920
+ ndimage.median_filter(x, size=size, mode=mode)
Binary file
@@ -1,7 +1,7 @@
1
1
  import numpy as np
2
2
  import scipy._lib._elementwise_iterative_method as eim
3
3
  from scipy._lib._util import _RichResult
4
- from scipy._lib._array_api import array_namespace, xp_ravel
4
+ from scipy._lib._array_api import array_namespace, xp_ravel, xp_default_dtype
5
5
 
6
6
  _ELIMITS = -1 # used in _bracket_root
7
7
  _ESTOPONESIDE = 2 # used in _bracket_root
@@ -19,8 +19,17 @@ def _bracket_root_iv(func, xl0, xr0, xmin, xmax, factor, args, maxiter):
19
19
  if (not xp.isdtype(xl0.dtype, "numeric")
20
20
  or xp.isdtype(xl0.dtype, "complex floating")):
21
21
  raise ValueError('`xl0` must be numeric and real.')
22
+ if not xp.isdtype(xl0.dtype, "real floating"):
23
+ xl0 = xp.asarray(xl0, dtype=xp_default_dtype(xp))
24
+
25
+ # If xr0 is not supplied, fill with a dummy value for the sake of
26
+ # broadcasting. We need to wait until xmax has been validated to
27
+ # compute the default value.
28
+ xr0_not_supplied = False
29
+ if xr0 is None:
30
+ xr0 = xp.nan
31
+ xr0_not_supplied = True
22
32
 
23
- xr0 = xl0 + 1 if xr0 is None else xr0
24
33
  xmin = -xp.inf if xmin is None else xmin
25
34
  xmax = xp.inf if xmax is None else xmax
26
35
  factor = 2. if factor is None else factor
@@ -45,6 +54,12 @@ def _bracket_root_iv(func, xl0, xr0, xmin, xmax, factor, args, maxiter):
45
54
  if not xp.all(factor > 1):
46
55
  raise ValueError('All elements of `factor` must be greater than 1.')
47
56
 
57
+ # Calculate the default value of xr0 if a value has not been supplied.
58
+ # Be careful to ensure xr0 is not larger than xmax.
59
+ if xr0_not_supplied:
60
+ xr0 = xl0 + xp.minimum((xmax - xl0)/ 8, xp.asarray(1.0))
61
+ xr0 = xp.astype(xr0, xl0.dtype, copy=False)
62
+
48
63
  maxiter = xp.asarray(maxiter)
49
64
  message = '`maxiter` must be a non-negative integer.'
50
65
  if (not xp.isdtype(maxiter.dtype, "numeric") or maxiter.shape != tuple()
@@ -281,14 +296,22 @@ def _bracket_root(func, xl0, xr0=None, *, xmin=None, xmax=None, factor=None,
281
296
  # `work.status`.
282
297
  # Get the integer indices of the elements that can also stop
283
298
  also_stop = (work.active[i] + work.n) % (2*work.n)
284
- # Check whether they are still active.
285
- # To start, we need to find out where in `work.active` they would
286
- # appear if they are indeed there.
299
+ # Check whether they are still active. We want to find the indices
300
+ # in work.active where the associated values in work.active are
301
+ # contained in also_stop. xp.searchsorted let's us take advantage
302
+ # of work.active being sorted, but requires some hackery because
303
+ # searchsorted solves the separate but related problem of finding
304
+ # the indices where the values in also_stop should be added to
305
+ # maintain sorted order.
287
306
  j = xp.searchsorted(work.active, also_stop)
288
307
  # If the location exceeds the length of the `work.active`, they are
289
- # not there.
290
- j = j[j < work.active.shape[0]]
291
- # Check whether they are still there.
308
+ # not there. This happens when a value in also_stop is larger than
309
+ # the greatest value in work.active. This case needs special handling
310
+ # because we cannot simply check that also_stop == work.active[j].
311
+ mask = j < work.active.shape[0]
312
+ # Note that we also have to use the mask to filter also_stop to ensure
313
+ # that also_stop and j will still have the same shape.
314
+ j, also_stop = j[mask], also_stop[mask]
292
315
  j = j[also_stop == work.active[j]]
293
316
  # Now convert these to boolean indices to use with `work.status`.
294
317
  i = xp.zeros_like(stop)
@@ -407,6 +430,8 @@ def _bracket_minimum_iv(func, xm0, xl0, xr0, xmin, xmax, factor, args, maxiter):
407
430
  if (not xp.isdtype(xm0.dtype, "numeric")
408
431
  or xp.isdtype(xm0.dtype, "complex floating")):
409
432
  raise ValueError('`xm0` must be numeric and real.')
433
+ if not xp.isdtype(xm0.dtype, "real floating"):
434
+ xm0 = xp.asarray(xm0, dtype=xp_default_dtype(xp))
410
435
 
411
436
  xmin = -xp.inf if xmin is None else xmin
412
437
  xmax = xp.inf if xmax is None else xmax
@@ -457,8 +482,10 @@ def _bracket_minimum_iv(func, xm0, xl0, xr0, xmin, xmax, factor, args, maxiter):
457
482
  # of (xmin, xmax).
458
483
  if xl0_not_supplied:
459
484
  xl0 = xm0 - xp.minimum((xm0 - xmin)/16, xp.asarray(0.5))
485
+ xl0 = xp.astype(xl0, xm0.dtype, copy=False)
460
486
  if xr0_not_supplied:
461
487
  xr0 = xm0 + xp.minimum((xmax - xm0)/16, xp.asarray(0.5))
488
+ xr0 = xp.astype(xr0, xm0.dtype, copy=False)
462
489
 
463
490
  maxiter = xp.asarray(maxiter)
464
491
  message = '`maxiter` must be a non-negative integer.'
@@ -264,11 +264,13 @@ def _highs_wrapper(c, indptr, indices, data, lhs, rhs, lb, ub, integrality, opti
264
264
 
265
265
  # Lagrangians for bounds based on column statuses
266
266
  marg_bnds = np.zeros((2, numcol))
267
+ basis_col_status = basis.col_status
268
+ solution_col_dual = solution.col_dual
267
269
  for ii in range(numcol):
268
- if basis.col_status[ii] == _h.HighsBasisStatus.kLower:
269
- marg_bnds[0, ii] = solution.col_dual[ii]
270
- elif basis.col_status[ii] == _h.HighsBasisStatus.kUpper:
271
- marg_bnds[1, ii] = solution.col_dual[ii]
270
+ if basis_col_status[ii] == _h.HighsBasisStatus.kLower:
271
+ marg_bnds[0, ii] = solution_col_dual[ii]
272
+ elif basis_col_status[ii] == _h.HighsBasisStatus.kUpper:
273
+ marg_bnds[1, ii] = solution_col_dual[ii]
272
274
 
273
275
  res.update(
274
276
  {
@@ -23,13 +23,13 @@ from ._highspy._core import(
23
23
  HighsDebugLevel,
24
24
  ObjSense,
25
25
  HighsModelStatus,
26
- )
27
- from ._highspy._core.simplex_constants import (
28
- SimplexStrategy,
29
- SimplexEdgeWeightStrategy,
26
+ simplex_constants as s_c, # [1]
30
27
  )
31
28
  from scipy.sparse import csc_matrix, vstack, issparse
32
29
 
30
+ # [1]: Directly importing from "._highspy._core.simplex_constants"
31
+ # causes problems when reloading.
32
+ # See https://github.com/scipy/scipy/pull/22869 for details.
33
33
 
34
34
  def _highs_to_scipy_status_message(highs_status, highs_message):
35
35
  """Converts HiGHS status number/message to SciPy status number/message"""
@@ -293,13 +293,13 @@ def _linprog_highs(lp, solver, time_limit=None, presolve=True,
293
293
  simplex_dual_edge_weight_strategy,
294
294
  'simplex_dual_edge_weight_strategy',
295
295
  choices={'dantzig': \
296
- SimplexEdgeWeightStrategy.kSimplexEdgeWeightStrategyDantzig,
296
+ s_c.SimplexEdgeWeightStrategy.kSimplexEdgeWeightStrategyDantzig,
297
297
  'devex': \
298
- SimplexEdgeWeightStrategy.kSimplexEdgeWeightStrategyDevex,
298
+ s_c.SimplexEdgeWeightStrategy.kSimplexEdgeWeightStrategyDevex,
299
299
  'steepest-devex': \
300
- SimplexEdgeWeightStrategy.kSimplexEdgeWeightStrategyChoose,
300
+ s_c.SimplexEdgeWeightStrategy.kSimplexEdgeWeightStrategyChoose,
301
301
  'steepest': \
302
- SimplexEdgeWeightStrategy.kSimplexEdgeWeightStrategySteepestEdge,
302
+ s_c.SimplexEdgeWeightStrategy.kSimplexEdgeWeightStrategySteepestEdge,
303
303
  None: None})
304
304
 
305
305
  c, A_ub, b_ub, A_eq, b_eq, bounds, x0, integrality = lp
@@ -334,7 +334,7 @@ def _linprog_highs(lp, solver, time_limit=None, presolve=True,
334
334
  'primal_feasibility_tolerance': primal_feasibility_tolerance,
335
335
  'simplex_dual_edge_weight_strategy':
336
336
  simplex_dual_edge_weight_strategy_enum,
337
- 'simplex_strategy': SimplexStrategy.kSimplexStrategyDual,
337
+ 'simplex_strategy': s_c.SimplexStrategy.kSimplexStrategyDual,
338
338
  'ipm_iteration_limit': maxiter,
339
339
  'simplex_iteration_limit': maxiter,
340
340
  'mip_rel_gap': mip_rel_gap,
@@ -1409,9 +1409,10 @@ def _postsolve(x, postsolve_args, complete=False):
1409
1409
  x = rev(x)
1410
1410
 
1411
1411
  fun = x.dot(c)
1412
- slack = b_ub - A_ub.dot(x) # report slack for ORIGINAL UB constraints
1413
- # report residuals of ORIGINAL EQ constraints
1414
- con = b_eq - A_eq.dot(x)
1412
+ with np.errstate(invalid="ignore"):
1413
+ slack = b_ub - A_ub.dot(x) # report slack for ORIGINAL UB constraints
1414
+ # report residuals of ORIGINAL EQ constraints
1415
+ con = b_eq - A_eq.dot(x)
1415
1416
 
1416
1417
  return x, fun, slack, con
1417
1418
 
Binary file
@@ -352,6 +352,41 @@ class TestBracketRoot:
352
352
  xmin=1)
353
353
  assert not res.success
354
354
 
355
+ def test_bug_fixes(self):
356
+ # 1. Bug in double sided bracket search.
357
+ # Happened in some cases where there are terminations on one side
358
+ # after corresponding searches on other side failed due to reaching the
359
+ # boundary.
360
+
361
+ # https://github.com/scipy/scipy/pull/22560#discussion_r1962853839
362
+ def f(x, p):
363
+ return np.exp(x) - p
364
+
365
+ p = np.asarray([0.29, 0.35])
366
+ res = _bracket_root(f, xl0=-1, xmin=-np.inf, xmax=0, args=(p, ))
367
+
368
+ # https://github.com/scipy/scipy/pull/22560/files#r1962952517
369
+ def f(x, p, c):
370
+ return np.exp(x*c) - p
371
+
372
+ p = [0.32061201, 0.39175242, 0.40047535, 0.50527218, 0.55654373,
373
+ 0.11911647, 0.37507896, 0.66554191]
374
+ c = [1., -1., 1., 1., -1., 1., 1., 1.]
375
+ xl0 = [-7.63108551, 3.27840947, -8.36968526, -1.78124372,
376
+ 0.92201295, -2.48930123, -0.66733533, -0.44606749]
377
+ xr0 = [-6.63108551, 4.27840947, -7.36968526, -0.78124372,
378
+ 1.92201295, -1.48930123, 0., 0.]
379
+ xmin = [-np.inf, 0., -np.inf, -np.inf, 0., -np.inf, -np.inf,
380
+ -np.inf]
381
+ xmax = [0., np.inf, 0., 0., np.inf, 0., 0., 0.]
382
+
383
+ res = _bracket_root(f, xl0=xl0, xr0=xr0, xmin=xmin, xmax=xmax, args=(p, c))
384
+
385
+ # 2. Default xl0 + 1 for xr0 exceeds xmax.
386
+ # https://github.com/scipy/scipy/pull/22560#discussion_r1962947434
387
+ res = _bracket_root(lambda x: x + 0.25, xl0=-0.5, xmin=-np.inf, xmax=0)
388
+ assert res.success
389
+
355
390
 
356
391
  @pytest.mark.skip_xp_backends('array_api_strict', reason=array_api_strict_skip_reason)
357
392
  @pytest.mark.skip_xp_backends('jax.numpy', reason=jax_skip_reason)