scipy 1.15.2__cp313-cp313t-macosx_12_0_arm64.whl → 1.15.3__cp313-cp313t-macosx_12_0_arm64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. scipy/__config__.py +6 -6
  2. scipy/_lib/_array_api.py +11 -0
  3. scipy/_lib/_ccallback_c.cpython-313t-darwin.so +0 -0
  4. scipy/_lib/_test_ccallback.cpython-313t-darwin.so +0 -0
  5. scipy/_lib/_test_deprecation_call.cpython-313t-darwin.so +0 -0
  6. scipy/_lib/_test_deprecation_def.cpython-313t-darwin.so +0 -0
  7. scipy/_lib/messagestream.cpython-313t-darwin.so +0 -0
  8. scipy/_lib/tests/test_array_api.py +5 -1
  9. scipy/cluster/_hierarchy.cpython-313t-darwin.so +0 -0
  10. scipy/cluster/_optimal_leaf_ordering.cpython-313t-darwin.so +0 -0
  11. scipy/cluster/_vq.cpython-313t-darwin.so +0 -0
  12. scipy/fftpack/convolve.cpython-313t-darwin.so +0 -0
  13. scipy/integrate/_dop.cpython-313t-darwin.so +0 -0
  14. scipy/integrate/_ivp/common.py +3 -3
  15. scipy/integrate/_ivp/ivp.py +9 -2
  16. scipy/integrate/_ivp/tests/test_ivp.py +19 -0
  17. scipy/integrate/_lsoda.cpython-313t-darwin.so +0 -0
  18. scipy/integrate/_odepack.cpython-313t-darwin.so +0 -0
  19. scipy/integrate/_quadpack.cpython-313t-darwin.so +0 -0
  20. scipy/integrate/_tanhsinh.py +14 -12
  21. scipy/integrate/_test_odeint_banded.cpython-313t-darwin.so +0 -0
  22. scipy/integrate/_vode.cpython-313t-darwin.so +0 -0
  23. scipy/integrate/tests/test_tanhsinh.py +10 -0
  24. scipy/interpolate/_bspl.cpython-313t-darwin.so +0 -0
  25. scipy/interpolate/_dfitpack.cpython-313t-darwin.so +0 -0
  26. scipy/interpolate/_dierckx.cpython-313t-darwin.so +0 -0
  27. scipy/interpolate/_interpnd.cpython-313t-darwin.so +0 -0
  28. scipy/interpolate/_ppoly.cpython-313t-darwin.so +0 -0
  29. scipy/interpolate/_rgi_cython.cpython-313t-darwin.so +0 -0
  30. scipy/io/_test_fortran.cpython-313t-darwin.so +0 -0
  31. scipy/io/matlab/_mio5_utils.cpython-313t-darwin.so +0 -0
  32. scipy/io/matlab/_mio_utils.cpython-313t-darwin.so +0 -0
  33. scipy/io/matlab/_streams.cpython-313t-darwin.so +0 -0
  34. scipy/linalg/_cythonized_array_utils.cpython-313t-darwin.so +0 -0
  35. scipy/linalg/_decomp_interpolative.cpython-313t-darwin.so +0 -0
  36. scipy/linalg/_decomp_lu_cython.cpython-313t-darwin.so +0 -0
  37. scipy/linalg/_decomp_update.cpython-313t-darwin.so +0 -0
  38. scipy/linalg/_fblas.cpython-313t-darwin.so +0 -0
  39. scipy/linalg/_flapack.cpython-313t-darwin.so +0 -0
  40. scipy/linalg/_matfuncs_expm.cpython-313t-darwin.so +0 -0
  41. scipy/linalg/_matfuncs_sqrtm_triu.cpython-313t-darwin.so +0 -0
  42. scipy/linalg/_solve_toeplitz.cpython-313t-darwin.so +0 -0
  43. scipy/linalg/cython_blas.cpython-313t-darwin.so +0 -0
  44. scipy/linalg/cython_lapack.cpython-313t-darwin.so +0 -0
  45. scipy/linalg/tests/test_interpolative.py +17 -0
  46. scipy/ndimage/_cytest.cpython-313t-darwin.so +0 -0
  47. scipy/ndimage/_nd_image.cpython-313t-darwin.so +0 -0
  48. scipy/ndimage/_ndimage_api.py +2 -1
  49. scipy/ndimage/_ni_label.cpython-313t-darwin.so +0 -0
  50. scipy/ndimage/_rank_filter_1d.cpython-313t-darwin.so +0 -0
  51. scipy/ndimage/tests/test_filters.py +14 -0
  52. scipy/odr/__odrpack.cpython-313t-darwin.so +0 -0
  53. scipy/optimize/_bglu_dense.cpython-313t-darwin.so +0 -0
  54. scipy/optimize/_bracket.py +35 -8
  55. scipy/optimize/_cobyla.cpython-313t-darwin.so +0 -0
  56. scipy/optimize/_cython_nnls.cpython-313t-darwin.so +0 -0
  57. scipy/optimize/_highspy/_highs_wrapper.py +6 -4
  58. scipy/optimize/_lbfgsb.cpython-313t-darwin.so +0 -0
  59. scipy/optimize/_linprog_highs.py +9 -9
  60. scipy/optimize/_linprog_util.py +4 -3
  61. scipy/optimize/_lsq/givens_elimination.cpython-313t-darwin.so +0 -0
  62. scipy/optimize/_minpack.cpython-313t-darwin.so +0 -0
  63. scipy/optimize/_moduleTNC.cpython-313t-darwin.so +0 -0
  64. scipy/optimize/_slsqp.cpython-313t-darwin.so +0 -0
  65. scipy/optimize/_trlib/_trlib.cpython-313t-darwin.so +0 -0
  66. scipy/optimize/cython_optimize/_zeros.cpython-313t-darwin.so +0 -0
  67. scipy/optimize/tests/test_bracket.py +35 -0
  68. scipy/signal/_peak_finding_utils.cpython-313t-darwin.so +0 -0
  69. scipy/signal/_short_time_fft.py +34 -6
  70. scipy/signal/_signaltools.py +4 -1
  71. scipy/signal/_sosfilt.cpython-313t-darwin.so +0 -0
  72. scipy/signal/_upfirdn_apply.cpython-313t-darwin.so +0 -0
  73. scipy/signal/tests/_scipy_spectral_test_shim.py +3 -11
  74. scipy/signal/tests/test_short_time_fft.py +10 -2
  75. scipy/signal/tests/test_signaltools.py +5 -0
  76. scipy/sparse/_base.py +2 -15
  77. scipy/sparse/_compressed.py +0 -3
  78. scipy/sparse/_csparsetools.cpython-313t-darwin.so +0 -0
  79. scipy/sparse/_dia.py +0 -3
  80. scipy/sparse/csgraph/_flow.cpython-313t-darwin.so +0 -0
  81. scipy/sparse/csgraph/_matching.cpython-313t-darwin.so +0 -0
  82. scipy/sparse/csgraph/_min_spanning_tree.cpython-313t-darwin.so +0 -0
  83. scipy/sparse/csgraph/_reordering.cpython-313t-darwin.so +0 -0
  84. scipy/sparse/csgraph/_shortest_path.cpython-313t-darwin.so +0 -0
  85. scipy/sparse/csgraph/_tools.cpython-313t-darwin.so +0 -0
  86. scipy/sparse/csgraph/_traversal.cpython-313t-darwin.so +0 -0
  87. scipy/sparse/linalg/_dsolve/_superlu.cpython-313t-darwin.so +0 -0
  88. scipy/sparse/linalg/_eigen/arpack/_arpack.cpython-313t-darwin.so +0 -0
  89. scipy/sparse/linalg/_eigen/arpack/arpack.py +5 -3
  90. scipy/sparse/linalg/_expm_multiply.py +8 -3
  91. scipy/sparse/linalg/_interface.py +12 -8
  92. scipy/sparse/linalg/_isolve/_gcrotmk.py +2 -1
  93. scipy/sparse/linalg/_propack/_cpropack.cpython-313t-darwin.so +0 -0
  94. scipy/sparse/linalg/_propack/_dpropack.cpython-313t-darwin.so +0 -0
  95. scipy/sparse/linalg/_propack/_spropack.cpython-313t-darwin.so +0 -0
  96. scipy/sparse/linalg/_propack/_zpropack.cpython-313t-darwin.so +0 -0
  97. scipy/sparse/linalg/tests/test_expm_multiply.py +10 -0
  98. scipy/sparse/linalg/tests/test_interface.py +35 -0
  99. scipy/sparse/linalg/tests/test_pydata_sparse.py +4 -0
  100. scipy/sparse/tests/test_base.py +12 -0
  101. scipy/sparse/tests/test_common1d.py +11 -6
  102. scipy/spatial/_ckdtree.cpython-313t-darwin.so +0 -0
  103. scipy/spatial/_hausdorff.cpython-313t-darwin.so +0 -0
  104. scipy/spatial/_qhull.cpython-313t-darwin.so +0 -0
  105. scipy/spatial/_voronoi.cpython-313t-darwin.so +0 -0
  106. scipy/spatial/tests/test_qhull.py +99 -0
  107. scipy/spatial/transform/_rotation.cpython-313t-darwin.so +0 -0
  108. scipy/spatial/transform/tests/test_rotation.py +181 -10
  109. scipy/special/_comb.cpython-313t-darwin.so +0 -0
  110. scipy/special/_ellip_harm_2.cpython-313t-darwin.so +0 -0
  111. scipy/special/_logsumexp.py +21 -16
  112. scipy/special/_specfun.cpython-313t-darwin.so +0 -0
  113. scipy/special/_special_ufuncs.cpython-313t-darwin.so +0 -0
  114. scipy/special/_test_internal.cpython-313t-darwin.so +0 -0
  115. scipy/special/_ufuncs.cpython-313t-darwin.so +0 -0
  116. scipy/special/_ufuncs_cxx.cpython-313t-darwin.so +0 -0
  117. scipy/special/cython_special.cpython-313t-darwin.so +0 -0
  118. scipy/special/tests/test_hyp2f1.py +21 -0
  119. scipy/special/tests/test_logsumexp.py +14 -0
  120. scipy/special/xsf/hyp2f1.h +3 -3
  121. scipy/stats/_ansari_swilk_statistics.cpython-313t-darwin.so +0 -0
  122. scipy/stats/_biasedurn.cpython-313t-darwin.so +0 -0
  123. scipy/stats/_continuous_distns.py +25 -15
  124. scipy/stats/_levy_stable/levyst.cpython-313t-darwin.so +0 -0
  125. scipy/stats/_mvn.cpython-313t-darwin.so +0 -0
  126. scipy/stats/_qmc_cy.cpython-313t-darwin.so +0 -0
  127. scipy/stats/_rcont/rcont.cpython-313t-darwin.so +0 -0
  128. scipy/stats/_sobol.cpython-313t-darwin.so +0 -0
  129. scipy/stats/_stats.cpython-313t-darwin.so +0 -0
  130. scipy/stats/_unuran/unuran_wrapper.cpython-313t-darwin.so +0 -0
  131. scipy/stats/tests/test_distributions.py +22 -0
  132. scipy/version.py +2 -2
  133. {scipy-1.15.2.dist-info → scipy-1.15.3.dist-info}/METADATA +2 -2
  134. {scipy-1.15.2.dist-info → scipy-1.15.3.dist-info}/RECORD +136 -136
  135. {scipy-1.15.2.dist-info → scipy-1.15.3.dist-info}/LICENSE.txt +0 -0
  136. {scipy-1.15.2.dist-info → scipy-1.15.3.dist-info}/WHEEL +0 -0
@@ -1181,6 +1181,105 @@ class Test_HalfspaceIntersection:
1181
1181
 
1182
1182
  assert_allclose(hs.dual_points, qhalf_points)
1183
1183
 
1184
+ @pytest.mark.parametrize("k", range(1,4))
1185
+ def test_halfspace_batch(self, k):
1186
+ # Test that we can add halfspaces a few at a time
1187
+ big_square = np.array([[ 1., 0., -2.],
1188
+ [-1., 0., -2.],
1189
+ [ 0., 1., -2.],
1190
+ [ 0., -1., -2.]])
1191
+
1192
+ small_square = np.array([[ 1., 0., -1.],
1193
+ [-1., 0., -1.],
1194
+ [ 0., 1., -1.],
1195
+ [ 0., -1., -1.]])
1196
+
1197
+ hs = qhull.HalfspaceIntersection(big_square,
1198
+ np.array([0.3141, 0.2718]),
1199
+ incremental=True)
1200
+
1201
+ hs.add_halfspaces(small_square[0:k,:])
1202
+ hs.add_halfspaces(small_square[k:4,:])
1203
+ hs.close()
1204
+
1205
+ # Check the intersections are correct (they are the corners of the small square)
1206
+ expected_intersections = np.array([[1., 1.],
1207
+ [1., -1.],
1208
+ [-1., 1.],
1209
+ [-1., -1.]])
1210
+ actual_intersections = hs.intersections
1211
+ # They may be in any order, so just check that under some permutation
1212
+ # expected=actual.
1213
+
1214
+ ind1 = np.lexsort((actual_intersections[:, 1], actual_intersections[:, 0]))
1215
+ ind2 = np.lexsort((expected_intersections[:, 1], expected_intersections[:, 0]))
1216
+ assert_allclose(actual_intersections[ind1], expected_intersections[ind2])
1217
+
1218
+
1219
+ @pytest.mark.parametrize("halfspaces", [
1220
+ (np.array([-0.70613882, -0.45589431, 0.04178256])),
1221
+ (np.array([[-0.70613882, -0.45589431, 0.04178256],
1222
+ [0.70807342, -0.45464871, -0.45969769],
1223
+ [0., 0.76515026, -0.35614825]])),
1224
+ ])
1225
+ def test_gh_19865(self, halfspaces):
1226
+ # starting off with a feasible interior point and
1227
+ # adding halfspaces for which it is no longer feasible
1228
+ # should result in an error rather than a problematic
1229
+ # intersection polytope
1230
+ initial_square = np.array(
1231
+ [[1, 0, -1], [0, 1, -1], [-1, 0, -1], [0, -1, -1]]
1232
+ )
1233
+ incremental_intersector = qhull.HalfspaceIntersection(initial_square,
1234
+ np.zeros(2),
1235
+ incremental=True)
1236
+ with pytest.raises(qhull.QhullError, match="feasible.*-0.706.*"):
1237
+ incremental_intersector.add_halfspaces(halfspaces)
1238
+
1239
+
1240
+ def test_gh_19865_3d(self):
1241
+ # 3d case where closed half space is enforced for
1242
+ # feasibility
1243
+ halfspaces = np.array([[1, 1, 1, -1], # doesn't exclude origin
1244
+ [-1, -1, -1, -1], # doesn't exclude origin
1245
+ [1, 0, 0, 0]]) # the origin is on the line
1246
+ initial_cube = np.array([[1, 0, 0, -1],
1247
+ [-1, 0, 0, -1],
1248
+ [0, 1, 0, -1],
1249
+ [0, -1, 0, -1],
1250
+ [0, 0, 1, -1],
1251
+ [0, 0, -1, -1]])
1252
+ incremental_intersector = qhull.HalfspaceIntersection(initial_cube,
1253
+ np.zeros(3),
1254
+ incremental=True)
1255
+ with pytest.raises(qhull.QhullError, match="feasible.*[1 0 0 0]"):
1256
+ incremental_intersector.add_halfspaces(halfspaces)
1257
+
1258
+
1259
+ def test_2d_add_halfspace_input(self):
1260
+ # incrementally added halfspaces should respect the 2D
1261
+ # array shape requirement
1262
+ initial_square = np.array(
1263
+ [[1, 0, -1], [0, 1, -1], [-1, 0, -1], [0, -1, -1]]
1264
+ )
1265
+ incremental_intersector = qhull.HalfspaceIntersection(initial_square,
1266
+ np.zeros(2),
1267
+ incremental=True)
1268
+ with pytest.raises(ValueError, match="2D array"):
1269
+ incremental_intersector.add_halfspaces(np.ones((4, 4, 4)))
1270
+
1271
+ def test_1d_add_halfspace_input(self):
1272
+ # we do allow 1D `halfspaces` input to add_halfspaces()
1273
+ initial_square = np.array(
1274
+ [[1, 0, -1], [0, 1, -1], [-1, 0, -1], [0, -1, -1]]
1275
+ )
1276
+ incremental_intersector = qhull.HalfspaceIntersection(initial_square,
1277
+ np.zeros(2),
1278
+ incremental=True)
1279
+ assert_allclose(incremental_intersector.dual_vertices, np.arange(4))
1280
+ incremental_intersector.add_halfspaces(np.array([2, 2, -1]))
1281
+ assert_allclose(incremental_intersector.dual_vertices, np.arange(5))
1282
+
1184
1283
 
1185
1284
  @pytest.mark.parametrize("diagram_type", [Voronoi, qhull.Delaunay])
1186
1285
  def test_gh_20623(diagram_type):
@@ -168,10 +168,6 @@ def test_from_quat_wrong_shape():
168
168
  [[4, 5, 6, 7]]
169
169
  ]))
170
170
 
171
- # 0-length 2d array
172
- with pytest.raises(ValueError, match='Expected `quat` to have shape'):
173
- Rotation.from_quat(np.array([]).reshape((0, 4)))
174
-
175
171
 
176
172
  def test_zero_norms_from_quat():
177
173
  x = np.array([
@@ -1611,18 +1607,23 @@ def test_slerp_rot_is_rotation():
1611
1607
  t = np.array([0, 1])
1612
1608
  Slerp(t, r)
1613
1609
 
1610
+ SLERP_EXCEPTION_MESSAGE = "must be a sequence of at least 2 rotations"
1614
1611
 
1615
1612
  def test_slerp_single_rot():
1616
- msg = "must be a sequence of at least 2 rotations"
1617
- with pytest.raises(ValueError, match=msg):
1618
- r = Rotation.from_quat([1, 2, 3, 4])
1613
+ r = Rotation.from_quat([1, 2, 3, 4])
1614
+ with pytest.raises(ValueError, match=SLERP_EXCEPTION_MESSAGE):
1619
1615
  Slerp([1], r)
1620
1616
 
1621
1617
 
1618
+ def test_slerp_rot_len0():
1619
+ r = Rotation.random()
1620
+ with pytest.raises(ValueError, match=SLERP_EXCEPTION_MESSAGE):
1621
+ Slerp([], r)
1622
+
1623
+
1622
1624
  def test_slerp_rot_len1():
1623
- msg = "must be a sequence of at least 2 rotations"
1624
- with pytest.raises(ValueError, match=msg):
1625
- r = Rotation.from_quat([[1, 2, 3, 4]])
1625
+ r = Rotation.random(1)
1626
+ with pytest.raises(ValueError, match=SLERP_EXCEPTION_MESSAGE):
1626
1627
  Slerp([1], r)
1627
1628
 
1628
1629
 
@@ -2015,3 +2016,173 @@ def test_compare_as_davenport_as_euler():
2015
2016
  eul = rot.as_euler(seq)
2016
2017
  dav = rot.as_davenport(ax, order)
2017
2018
  assert_allclose(eul, dav, rtol=1e-12)
2019
+
2020
+
2021
+ def test_zero_rotation_construction():
2022
+ r = Rotation.random(num=0)
2023
+ assert len(r) == 0
2024
+
2025
+ r_ide = Rotation.identity(num=0)
2026
+ assert len(r_ide) == 0
2027
+
2028
+ r_get = Rotation.random(num=3)[[]]
2029
+ assert len(r_get) == 0
2030
+
2031
+ r_quat = Rotation.from_quat(np.zeros((0, 4)))
2032
+ assert len(r_quat) == 0
2033
+
2034
+ r_matrix = Rotation.from_matrix(np.zeros((0, 3, 3)))
2035
+ assert len(r_matrix) == 0
2036
+
2037
+ r_euler = Rotation.from_euler("xyz", np.zeros((0, 3)))
2038
+ assert len(r_euler) == 0
2039
+
2040
+ r_vec = Rotation.from_rotvec(np.zeros((0, 3)))
2041
+ assert len(r_vec) == 0
2042
+
2043
+ r_dav = Rotation.from_davenport(np.eye(3), "extrinsic", np.zeros((0, 3)))
2044
+ assert len(r_dav) == 0
2045
+
2046
+ r_mrp = Rotation.from_mrp(np.zeros((0, 3)))
2047
+ assert len(r_mrp) == 0
2048
+
2049
+
2050
+ def test_zero_rotation_representation():
2051
+ r = Rotation.random(num=0)
2052
+ assert r.as_quat().shape == (0, 4)
2053
+ assert r.as_matrix().shape == (0, 3, 3)
2054
+ assert r.as_euler("xyz").shape == (0, 3)
2055
+ assert r.as_rotvec().shape == (0, 3)
2056
+ assert r.as_mrp().shape == (0, 3)
2057
+ assert r.as_davenport(np.eye(3), "extrinsic").shape == (0, 3)
2058
+
2059
+
2060
+ def test_zero_rotation_array_rotation():
2061
+ r = Rotation.random(num=0)
2062
+
2063
+ v = np.array([1, 2, 3])
2064
+ v_rotated = r.apply(v)
2065
+ assert v_rotated.shape == (0, 3)
2066
+
2067
+ v0 = np.zeros((0, 3))
2068
+ v0_rot = r.apply(v0)
2069
+ assert v0_rot.shape == (0, 3)
2070
+
2071
+ v2 = np.ones((2, 3))
2072
+ with pytest.raises(
2073
+ ValueError, match="Expected equal numbers of rotations and vectors"):
2074
+ r.apply(v2)
2075
+
2076
+
2077
+ def test_zero_rotation_multiplication():
2078
+ r = Rotation.random(num=0)
2079
+
2080
+ r_single = Rotation.random()
2081
+ r_mult_left = r * r_single
2082
+ assert len(r_mult_left) == 0
2083
+
2084
+ r_mult_right = r_single * r
2085
+ assert len(r_mult_right) == 0
2086
+
2087
+ r0 = Rotation.random(0)
2088
+ r_mult = r * r0
2089
+ assert len(r_mult) == 0
2090
+
2091
+ msg_rotation_error = "Expected equal number of rotations"
2092
+ r2 = Rotation.random(2)
2093
+ with pytest.raises(ValueError, match=msg_rotation_error):
2094
+ r0 * r2
2095
+
2096
+ with pytest.raises(ValueError, match=msg_rotation_error):
2097
+ r2 * r0
2098
+
2099
+
2100
+ def test_zero_rotation_concatentation():
2101
+ r = Rotation.random(num=0)
2102
+
2103
+ r0 = Rotation.concatenate([r, r])
2104
+ assert len(r0) == 0
2105
+
2106
+ r1 = r.concatenate([Rotation.random(), r])
2107
+ assert len(r1) == 1
2108
+
2109
+ r3 = r.concatenate([Rotation.random(3), r])
2110
+ assert len(r3) == 3
2111
+
2112
+ r4 = r.concatenate([r, Rotation.random(4)])
2113
+ assert len(r4) == 4
2114
+
2115
+
2116
+ def test_zero_rotation_power():
2117
+ r = Rotation.random(num=0)
2118
+ for pp in [-1.5, -1, 0, 1, 1.5]:
2119
+ pow0 = r**pp
2120
+ assert len(pow0) == 0
2121
+
2122
+
2123
+ def test_zero_rotation_inverse():
2124
+ r = Rotation.random(num=0)
2125
+ r_inv = r.inv()
2126
+ assert len(r_inv) == 0
2127
+
2128
+
2129
+ def test_zero_rotation_magnitude():
2130
+ r = Rotation.random(num=0)
2131
+ magnitude = r.magnitude()
2132
+ assert magnitude.shape == (0,)
2133
+
2134
+
2135
+ def test_zero_rotation_mean():
2136
+ r = Rotation.random(num=0)
2137
+ with pytest.raises(ValueError, match="Mean of an empty rotation set is undefined."):
2138
+ r.mean()
2139
+
2140
+
2141
+ def test_zero_rotation_approx_equal():
2142
+ r = Rotation.random(0)
2143
+ assert r.approx_equal(Rotation.random(0)).shape == (0,)
2144
+ assert r.approx_equal(Rotation.random()).shape == (0,)
2145
+ assert Rotation.random().approx_equal(r).shape == (0,)
2146
+
2147
+ approx_msg = "Expected equal number of rotations"
2148
+ r3 = Rotation.random(2)
2149
+ with pytest.raises(ValueError, match=approx_msg):
2150
+ r.approx_equal(r3)
2151
+
2152
+ with pytest.raises(ValueError, match=approx_msg):
2153
+ r3.approx_equal(r)
2154
+
2155
+
2156
+ def test_zero_rotation_get_set():
2157
+ r = Rotation.random(0)
2158
+
2159
+ r_get = r[[]]
2160
+ assert len(r_get) == 0
2161
+
2162
+ r_slice = r[:0]
2163
+ assert len(r_slice) == 0
2164
+
2165
+ with pytest.raises(IndexError):
2166
+ r[[0]]
2167
+
2168
+ with pytest.raises(IndexError):
2169
+ r[[True]]
2170
+
2171
+ with pytest.raises(IndexError):
2172
+ r[0] = Rotation.random()
2173
+
2174
+
2175
+ def test_boolean_indexes():
2176
+ r = Rotation.random(3)
2177
+
2178
+ r0 = r[[False, False, False]]
2179
+ assert len(r0) == 0
2180
+
2181
+ r1 = r[[False, True, False]]
2182
+ assert len(r1) == 1
2183
+
2184
+ r3 = r[[True, True, True]]
2185
+ assert len(r3) == 3
2186
+
2187
+ with pytest.raises(IndexError):
2188
+ r[[True, True]]
Binary file
@@ -5,9 +5,9 @@ from scipy._lib._array_api import (
5
5
  array_namespace,
6
6
  xp_size,
7
7
  xp_broadcast_promote,
8
- xp_real,
9
8
  xp_copy,
10
9
  xp_float_to_complex,
10
+ is_complex,
11
11
  )
12
12
  from scipy._lib import array_api_extra as xpx
13
13
 
@@ -219,25 +219,30 @@ def _logsumexp(a, b, axis, return_sign, xp):
219
219
  s = xp.where(s == 0, s, s/m)
220
220
 
221
221
  # Separate sign/magnitude information
222
- sgn = None
223
- if return_sign:
224
- # Use the numpy>=2.0 convention for sign.
225
- # When all array libraries agree, this can become sng = xp.sign(s).
226
- sgn = _sign(s + 1, xp=xp) * _sign(m, xp=xp)
227
-
228
- if xp.isdtype(s.dtype, "real floating"):
229
- # The log functions need positive arguments
230
- s = xp.where(s < -1, -s - 2, s)
231
- m = xp.abs(m)
232
- else:
233
- # `a_max` can have a sign component for complex input
234
- j = xp.asarray(1j, dtype=a_max.dtype)
235
- sgn = sgn * xp.exp(xp.imag(a_max) * j)
222
+ # Originally, this was only performed if `return_sign=True`.
223
+ # However, this is also needed if any elements of `m < 0` or `s < -1`.
224
+ # An improvement would be to perform the calculations only on these entries.
225
+
226
+ # Use the numpy>=2.0 convention for sign.
227
+ # When all array libraries agree, this can become sng = xp.sign(s).
228
+ sgn = _sign(s + 1, xp=xp) * _sign(m, xp=xp)
229
+
230
+ if xp.isdtype(s.dtype, "real floating"):
231
+ # The log functions need positive arguments
232
+ s = xp.where(s < -1, -s - 2, s)
233
+ m = xp.abs(m)
234
+ else:
235
+ # `a_max` can have a sign component for complex input
236
+ sgn = sgn * xp.exp(xp.imag(a_max) * xp.asarray(1.0j, dtype=a_max.dtype))
236
237
 
237
238
  # Take log and undo shift
238
239
  out = xp.log1p(s) + xp.log(m) + a_max
239
240
 
240
- out = xp_real(out) if return_sign else out
241
+ if return_sign:
242
+ if is_complex(out, xp):
243
+ out = xp.real(out)
244
+ elif xp.isdtype(out.dtype, 'real floating'):
245
+ out[sgn < 0] = xp.nan
241
246
 
242
247
  return out, sgn
243
248
 
@@ -2543,3 +2543,24 @@ class TestHyp2f1:
2543
2543
  if mark.name == 'parametrize'
2544
2544
  for case in mark.args[1]
2545
2545
  ]
2546
+
2547
+ class TestHyp2f1ExtremeInputs:
2548
+
2549
+ @pytest.mark.parametrize("a", [1.0, 2.0, 3.0, -np.inf, np.inf])
2550
+ @pytest.mark.parametrize("b", [3.0, 4.0, 5.0, -np.inf, np.inf])
2551
+ @pytest.mark.parametrize("c", [3.0, 5.0, 6.0, 7.0])
2552
+ @pytest.mark.parametrize("z", [4.0 + 1.0j])
2553
+ def test_inf_a_b(self, a, b, c, z):
2554
+ if np.any(np.isinf(np.asarray([a, b]))):
2555
+ assert(np.isnan(hyp2f1(a, b, c, z)))
2556
+
2557
+ def test_large_a_b(self):
2558
+ assert(np.isnan(hyp2f1(10**7, 1.0, 3.0, 4.0 + 1.0j)))
2559
+ assert(np.isnan(hyp2f1(-10**7, 1.0, 3.0, 4.0 + 1.0j)))
2560
+
2561
+ assert(np.isnan(hyp2f1(1.0, 10**7, 3.0, 4.0 + 1.0j)))
2562
+ assert(np.isnan(hyp2f1(1.0, -10**7, 3.0, 4.0 + 1.0j)))
2563
+
2564
+ # Already correct in main but testing for surety
2565
+ assert(np.isnan(hyp2f1(np.inf, 1.0, 3.0, 4.0)))
2566
+ assert(np.isnan(hyp2f1(1.0, np.inf, 3.0, 4.0)))
@@ -257,6 +257,20 @@ class TestLogSumExp:
257
257
  xp_assert_close(xp.real(res), xp.real(ref))
258
258
  xp_assert_close(xp.imag(res), xp.imag(ref), atol=0, rtol=1e-15)
259
259
 
260
+ def test_gh22903(self, xp):
261
+ # gh-22903 reported that `logsumexp` produced NaN where the weight associated
262
+ # with the max magnitude element was negative and `return_sign=False`, even if
263
+ # the net result should be the log of a positive number.
264
+
265
+ # result is log of positive number
266
+ a = xp.asarray([3.06409428, 0.37251854, 3.87471931])
267
+ b = xp.asarray([1.88190708, 2.84174795, -0.85016884])
268
+ xp_assert_close(logsumexp(a, b=b), logsumexp(a, b=b, return_sign=True)[0])
269
+
270
+ # result is log of negative number
271
+ b = xp.asarray([1.88190708, 2.84174795, -3.85016884])
272
+ xp_assert_close(logsumexp(a, b=b), xp.asarray(xp.nan))
273
+
260
274
 
261
275
  class TestSoftmax:
262
276
  def test_softmax_fixtures(self):
@@ -553,7 +553,7 @@ XSF_HOST_DEVICE inline std::complex<double> hyp2f1(double a, double b, double c,
553
553
  * the series at a or b of smaller magnitude. This is to ensure proper
554
554
  * handling of situations like a < c < b <= 0, a, b, c all non-positive
555
555
  * integers, where terminating at a would lead to a term of the form 0 / 0. */
556
- std::uint64_t max_degree;
556
+ double max_degree;
557
557
  if (a_neg_int || b_neg_int) {
558
558
  if (a_neg_int && b_neg_int) {
559
559
  max_degree = a > b ? std::abs(a) : std::abs(b);
@@ -562,7 +562,7 @@ XSF_HOST_DEVICE inline std::complex<double> hyp2f1(double a, double b, double c,
562
562
  } else {
563
563
  max_degree = std::abs(b);
564
564
  }
565
- if (max_degree <= UINT64_MAX) {
565
+ if (max_degree <= (double) UINT64_MAX) {
566
566
  auto series_generator = detail::HypergeometricSeriesGenerator(a, b, c, z);
567
567
  return detail::series_eval_fixed_length(series_generator, std::complex<double>{0.0, 0.0}, max_degree + 1);
568
568
  } else {
@@ -583,7 +583,7 @@ XSF_HOST_DEVICE inline std::complex<double> hyp2f1(double a, double b, double c,
583
583
  * (DLMF 15.8.1) */
584
584
  if (c_minus_a_neg_int || c_minus_b_neg_int) {
585
585
  max_degree = c_minus_b_neg_int ? std::abs(c - b) : std::abs(c - a);
586
- if (max_degree <= UINT64_MAX) {
586
+ if (max_degree <= (double) UINT64_MAX) {
587
587
  result = std::pow(1.0 - z, c - a - b);
588
588
  auto series_generator = detail::HypergeometricSeriesGenerator(c - a, c - b, c, z);
589
589
  result *=
@@ -3432,29 +3432,39 @@ class genextreme_gen(rv_continuous):
3432
3432
  g3 = g(3)
3433
3433
  g4 = g(4)
3434
3434
  g2mg12 = np.where(abs(c) < 1e-7, (c*np.pi)**2.0/6.0, g2-g1**2.0)
3435
- gam2k = np.where(abs(c) < 1e-7, np.pi**2.0/6.0,
3436
- sc.expm1(sc.gammaln(2.0*c+1.0)-2*sc.gammaln(c + 1.0))/c**2.0)
3435
+ def gam2k_f(c):
3436
+ return sc.expm1(sc.gammaln(2.0*c+1.0)-2*sc.gammaln(c + 1.0))/c**2.0
3437
+ gam2k = _lazywhere(abs(c) >= 1e-7, (c,), f=gam2k_f, fillvalue=np.pi**2.0/6.0)
3437
3438
  eps = 1e-14
3438
- gamk = np.where(abs(c) < eps, -_EULER, sc.expm1(sc.gammaln(c + 1))/c)
3439
+ def gamk_f(c):
3440
+ return sc.expm1(sc.gammaln(c + 1))/c
3441
+ gamk = _lazywhere(abs(c) >= eps, (c,), f=gamk_f, fillvalue=-_EULER)
3439
3442
 
3443
+ # mean
3440
3444
  m = np.where(c < -1.0, np.nan, -gamk)
3445
+
3446
+ # variance
3441
3447
  v = np.where(c < -0.5, np.nan, g1**2.0*gam2k)
3442
3448
 
3443
3449
  # skewness
3444
- sk1 = _lazywhere(c >= -1./3,
3445
- (c, g1, g2, g3, g2mg12),
3446
- lambda c, g1, g2, g3, g2mg12:
3447
- np.sign(c)*(-g3 + (g2 + 2*g2mg12)*g1)/g2mg12**1.5,
3448
- fillvalue=np.nan)
3449
- sk = np.where(abs(c) <= eps**0.29, 12*np.sqrt(6)*_ZETA3/np.pi**3, sk1)
3450
+ def sk1_eval(c, *args):
3451
+ def sk1_eval_f(c, g1, g2, g3, g2mg12):
3452
+ return np.sign(c)*(-g3 + (g2 + 2*g2mg12)*g1)/g2mg12**1.5
3453
+ return _lazywhere(c >= -1./3, (c,)+args, f=sk1_eval_f, fillvalue=np.nan)
3454
+
3455
+ sk_fill = 12*np.sqrt(6)*_ZETA3/np.pi**3
3456
+ args = (g1, g2, g3, g2mg12)
3457
+ sk = _lazywhere(abs(c) > eps**0.29, (c,)+args, f=sk1_eval, fillvalue=sk_fill)
3450
3458
 
3451
3459
  # kurtosis
3452
- ku1 = _lazywhere(c >= -1./4,
3453
- (g1, g2, g3, g4, g2mg12),
3454
- lambda g1, g2, g3, g4, g2mg12:
3455
- (g4 + (-4*g3 + 3*(g2 + g2mg12)*g1)*g1)/g2mg12**2,
3456
- fillvalue=np.nan)
3457
- ku = np.where(abs(c) <= (eps)**0.23, 12.0/5.0, ku1-3.0)
3460
+ def ku1_eval(c, *args):
3461
+ def ku1_eval_f(g1, g2, g3, g4, g2mg12):
3462
+ return (g4 + (-4*g3 + 3*(g2 + g2mg12)*g1)*g1)/g2mg12**2 - 3
3463
+ return _lazywhere(c >= -1./4, args, ku1_eval_f, fillvalue=np.nan)
3464
+
3465
+ args = (g1, g2, g3, g4, g2mg12)
3466
+ ku = _lazywhere(abs(c) > eps**0.23, (c,)+args, f=ku1_eval, fillvalue=12.0/5.0)
3467
+
3458
3468
  return m, v, sk, ku
3459
3469
 
3460
3470
  def _fitstart(self, data):
Binary file
Binary file
Binary file
Binary file
@@ -9251,6 +9251,28 @@ def test_genextreme_give_no_warnings():
9251
9251
  assert_equal(number_of_warnings_thrown, 0)
9252
9252
 
9253
9253
 
9254
+ def test_moments_gh22400():
9255
+ # Regression test for gh-22400
9256
+ # Check for correct results at c=0 with no warnings. While we're at it,
9257
+ # check that NaN and sufficiently negative input produce NaNs, and output
9258
+ # with `c=1` also agrees with reference values.
9259
+ res = np.asarray(stats.genextreme.stats([0.0, np.nan, 1, -1.5], moments='mvsk'))
9260
+
9261
+ # Reference values for c=0 (Wikipedia)
9262
+ mean = np.euler_gamma
9263
+ var = np.pi**2 / 6
9264
+ skew = 12 * np.sqrt(6) * special.zeta(3) / np.pi**3
9265
+ kurt = 12 / 5
9266
+ ref_0 = [mean, var, skew, kurt]
9267
+ ref_1 = ref_3 = [np.nan]*4
9268
+ ref_2 = [0, 1, -2, 6] # Wolfram Alpha, MaxStableDistribution[0, 1, -1]
9269
+
9270
+ assert_allclose(res[:, 0], ref_0, rtol=1e-14)
9271
+ assert_equal(res[:, 1], ref_1)
9272
+ assert_allclose(res[:, 2], ref_2, rtol=1e-14)
9273
+ assert_equal(res[:, 3], ref_3)
9274
+
9275
+
9254
9276
  def test_genextreme_entropy():
9255
9277
  # regression test for gh-5181
9256
9278
  euler_gamma = 0.5772156649015329
scipy/version.py CHANGED
@@ -2,10 +2,10 @@
2
2
  """
3
3
  Module to expose more detailed version info for the installed `scipy`
4
4
  """
5
- version = "1.15.2"
5
+ version = "1.15.3"
6
6
  full_version = version
7
7
  short_version = version.split('.dev')[0]
8
- git_revision = "0f1fd4a7268b813fa2b844ca6038e4dfdf90084a"
8
+ git_revision = "e29dcb65a2040f04819b426a04b60d44a8f69c04"
9
9
  release = 'dev' not in version and '+' not in version
10
10
 
11
11
  if not release:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: scipy
3
- Version: 1.15.2
3
+ Version: 1.15.3
4
4
  Summary: Fundamental algorithms for scientific computing in Python
5
5
  Maintainer-Email: SciPy Developers <scipy-dev@python.org>
6
6
  License: Copyright (c) 2001-2002 Enthought, Inc. 2003-2024, SciPy Developers.
@@ -990,7 +990,7 @@ Requires-Dist: numpydoc; extra == "doc"
990
990
  Requires-Dist: jupytext; extra == "doc"
991
991
  Requires-Dist: myst-nb; extra == "doc"
992
992
  Requires-Dist: pooch; extra == "doc"
993
- Requires-Dist: jupyterlite-sphinx>=0.16.5; extra == "doc"
993
+ Requires-Dist: jupyterlite-sphinx>=0.19.1; extra == "doc"
994
994
  Requires-Dist: jupyterlite-pyodide-kernel; extra == "doc"
995
995
  Provides-Extra: dev
996
996
  Requires-Dist: mypy==1.10.0; extra == "dev"