diff --git a/.github/workflows/macos_meson.yml b/.github/workflows/macos_meson.yml index ad38a0822201..50927280f5eb 100644 --- a/.github/workflows/macos_meson.yml +++ b/.github/workflows/macos_meson.yml @@ -110,6 +110,7 @@ jobs: shell: bash -l {0} run: | conda activate scipy-dev + mamba install python=${{ matrix.python-version}} # optional test dependencies mamba install scikit-umfpack scikit-sparse @@ -166,13 +167,14 @@ jobs: git submodule update --init # for some reason gfortran is not on the path - GFORTRAN_LOC=$(brew --prefix gfortran)/bin/gfortran + GFORTRAN_LOC=$(which gfortran-13) ln -s $GFORTRAN_LOC gfortran export PATH=$PWD:$PATH # make sure we have openblas bash tools/wheels/cibw_before_build_macos.sh $PWD - export DYLD_LIBRARY_PATH=/usr/local/gfortran/lib:/opt/arm64-builds/lib + GFORTRAN_LIB=$(dirname `gfortran --print-file-name libgfortran.dylib`) + export DYLD_LIBRARY_PATH=$GFORTRAN_LIB export PKG_CONFIG_PATH=/opt/arm64-builds/lib/pkgconfig pip install click doit pydevtool rich_click meson cython pythran pybind11 ninja numpy diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index bbcce9460d68..e6aed7e66e1c 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -37,7 +37,7 @@ jobs: get_commit_message: name: Get commit message runs-on: ubuntu-latest - if: github.repository == 'scipy/scipy' + if: github.repository == 'tylerjereddy/scipy' outputs: message: ${{ steps.commit_message.outputs.message }} steps: @@ -77,13 +77,9 @@ jobs: # should also be able to do multi-archs on a single entry, e.g. # [windows-2019, win*, "AMD64 x86"]. However, those two require a different compiler setup # so easier to separate out here. - - [ubuntu-22.04, manylinux, x86_64] - - [ubuntu-22.04, musllinux, x86_64] - - [macos-11, macosx, x86_64] - [macos-14, macosx, arm64] - - [windows-2019, win, AMD64] - python: [["cp39", "3.9"], ["cp310", "3.10"], ["cp311", "3.11"], ["cp312", "3.12"]] + python: [["cp312", "3.12"]] # python[0] is used to specify the python versions made by cibuildwheel env: @@ -128,21 +124,16 @@ jobs: run: | if [[ ${{ matrix.buildplat[2] }} == 'arm64' ]]; then # macosx_arm64 - - # use homebrew gfortran + LIB_PATH=$LIB_PATH:/opt/arm64-builds/lib sudo xcode-select -s /Applications/Xcode_15.2.app - # for some reason gfortran is not on the path - GFORTRAN_LOC=$(brew --prefix gfortran)/bin/gfortran - ln -s $GFORTRAN_LOC gfortran - export PATH=$PWD:$PATH - echo "PATH=$PATH" >> "$GITHUB_ENV" # location of the gfortran's libraries - GFORTRAN_LIB=$(dirname `gfortran --print-file-name libgfortran.dylib`) + GFORTRAN_LIB="\$(dirname \$(gfortran --print-file-name libgfortran.dylib))" CIBW="MACOSX_DEPLOYMENT_TARGET=12.0\ MACOS_DEPLOYMENT_TARGET=12.0\ - LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH\ + LD_LIBRARY_PATH=$LIB_PATH:$LD_LIBRARY_PATH\ + SDKROOT=$(xcrun --sdk macosx --show-sdk-path)\ _PYTHON_HOST_PLATFORM=macosx-12.0-arm64\ PKG_CONFIG_PATH=/opt/arm64-builds/lib/pkgconfig" echo "CIBW_ENVIRONMENT_MACOS=$CIBW" >> "$GITHUB_ENV" @@ -152,8 +143,8 @@ jobs: echo "REPAIR_PATH=/opt/arm64-builds/lib" >> "$GITHUB_ENV" - CIBW="DYLD_LIBRARY_PATH=$GFORTRAN_LIB:/opt/arm64-builds/lib delocate-listdeps {wheel} &&\ - DYLD_LIBRARY_PATH=$GFORTRAN_LIB:/opt/arm64-builds/lib delocate-wheel --require-archs {delocate_archs} -w {dest_dir} {wheel}" + CIBW="DYLD_LIBRARY_PATH=$GFORTRAN_LIB:$LIB_PATH delocate-listdeps {wheel} &&\ + DYLD_LIBRARY_PATH=$GFORTRAN_LIB:$LIB_PATH delocate-wheel --require-archs {delocate_archs} -w {dest_dir} {wheel}" echo "CIBW_REPAIR_WHEEL_COMMAND_MACOS=$CIBW" >> "$GITHUB_ENV" else diff --git a/.gitignore b/.gitignore index cb5a15536fe2..e5a93377e346 100644 --- a/.gitignore +++ b/.gitignore @@ -94,6 +94,7 @@ build-install/ .doit.db.dir .doit.db.db .doit.db +doc/source/.jupyterlite.doit.db # Logs and databases # ###################### diff --git a/doc/source/release/1.13.0-notes.rst b/doc/source/release/1.13.0-notes.rst index 765fb4419b67..378f76d47cc8 100644 --- a/doc/source/release/1.13.0-notes.rst +++ b/doc/source/release/1.13.0-notes.rst @@ -140,13 +140,39 @@ Deprecated features - Complex dtypes in ``PchipInterpolator`` and ``Akima1DInterpolator`` have been deprecated and will raise an error in SciPy 1.15.0. If you are trying to use the real components of the passed array, use ``np.real`` on ``y``. - +- Non-integer values of ``n`` together with ``exact=True`` are deprecated for + `scipy.special.factorial`. + + +********************* +Expired Deprecations +********************* +There is an ongoing effort to follow through on long-standing deprecations. +The following previously deprecated features are affected: + +- ``scipy.signal.{lsim2,impulse2,step2}`` have been removed in favour of + ``scipy.signal.{lsim,impulse,step}``. +- Window functions can no longer be imported from the `scipy.signal` namespace and + instead should be accessed through either `scipy.signal.windows` or + `scipy.signal.get_window`. +- `scipy.sparse` no longer supports multi-Ellipsis indexing +- ``scipy.signal.{bspline,quadratic,cubic}`` have been removed in favour of alternatives + in `scipy.interpolate`. +- ``scipy.linalg.tri{,u,l}`` have been removed in favour of ``numpy.tri{,u,l}``. +- Non-integer arrays in `scipy.special.factorial` with ``exact=True`` now raise an + error. +- Functions from NumPy's main namespace which were exposed in SciPy's main + namespace, such as ``numpy.histogram`` exposed by ``scipy.histogram``, have + been removed from SciPy's main namespace. Please use the functions directly + from ``numpy``. This was originally performed for SciPy 1.12.0 however was missed from + the release notes so is included here for completeness. ****************************** Backwards incompatible changes ****************************** + ************* Other changes ************* diff --git a/doc/source/release/1.13.1-notes.rst b/doc/source/release/1.13.1-notes.rst index c9fa25703fa5..5800887d6c5f 100644 --- a/doc/source/release/1.13.1-notes.rst +++ b/doc/source/release/1.13.1-notes.rst @@ -5,19 +5,66 @@ SciPy 1.13.1 Release Notes .. contents:: SciPy 1.13.1 is a bug-fix release with no new features -compared to 1.13.0. +compared to 1.13.0. The version of OpenBLAS shipped with +the PyPI binaries has been increased to 0.3.27. Authors ======= * Name (commits) +* h-vetinari (1) +* Jake Bowhay (2) +* Evgeni Burovski (6) +* Sean Cheah (1) +* Lucas Colley (1) +* DWesl (2) +* Ralf Gommers (6) +* Ben Greiner (1) + +* Philip Loche (1) + +* Matti Picus (1) +* Tyler Reddy (23) +* Atsushi Sakai (1) +* Dan Schult (1) +* Scott Shambaugh (2) + +A total of 14 people contributed to this release. +People with a "+" by their names contributed a patch for the first time. +This list of names is automatically generated, and may not be fully complete. Issues closed for 1.13.1 ------------------------ +* `#20392 `__: DOC: optimize.root(method='lm') option +* `#20471 `__: BUG: \`TestEig.test_falker\` fails on windows + MKL as well as... +* `#20491 `__: BUG: Cannot find \`OpenBLAS\` on Cygwin +* `#20506 `__: BUG: special.spherical_in: derivative at \`z=0, n=1\` incorrect +* `#20512 `__: BUG: \`eigh\` fails for size 1 array with driver=evd +* `#20531 `__: BUG: warning from \`optimize.least_squares\` for astropy with... +* `#20555 `__: BUG: spatial: error in \`Rotation.align_vectors()\` with an infinite... +* `#20580 `__: BUG: scipy.special.factorial2 doesn't handle \`uint32\` dtypes Pull requests for 1.13.1 ------------------------ + +* `#20322 `__: BUG: sparse: align dok_array.pop() to dict.pop() for case with... +* `#20333 `__: BUG: sync pocketfft again +* `#20381 `__: REL, MAINT: prep for 1.13.1 +* `#20401 `__: DOC: optimize: fix wrong optional argument name in \`root(method="lm")\`. +* `#20435 `__: DOC: add missing deprecations from 1.13.0 release notes +* `#20437 `__: MAINT/DOC: fix syntax in 1.13.0 release notes +* `#20449 `__: DOC: remove spurious backtick from release notes +* `#20473 `__: BUG: linalg: fix ordering of complex conj gen eigenvalues +* `#20474 `__: TST: tolerance bumps for the conda-forge builds +* `#20484 `__: TST: compare absolute values of U and VT in pydata-sparse SVD... +* `#20505 `__: BUG: Include Python.h before system headers. +* `#20516 `__: BUG: linalg: fix eigh(1x1 array, driver='evd') f2py check +* `#20527 `__: BUG: \`spherical_in\` for \`n=0\` and \`z=0\` +* `#20530 `__: BLD: Fix error message for f2py generation fail +* `#20537 `__: BLD: Move Python-including files to start of source. +* `#20567 `__: REV: 1.13.x: revert changes to f2py and tempita handling in meson.build... +* `#20569 `__: update openblas to 0.3.27 +* `#20573 `__: BUG: Fix error with 180 degree rotation in Rotation.align_vectors()... +* `#20607 `__: BUG: handle uint arrays in factorial{,2,k} diff --git a/scipy/_lib/pocketfft b/scipy/_lib/pocketfft index 0bf2b51224e7..9367142748fc 160000 --- a/scipy/_lib/pocketfft +++ b/scipy/_lib/pocketfft @@ -1 +1 @@ -Subproject commit 0bf2b51224e7c4e973724bac67d2577c7831e27c +Subproject commit 9367142748fcc9696a1c9e5a99b76ed9897c9daa diff --git a/scipy/interpolate/tests/test_rbfinterp.py b/scipy/interpolate/tests/test_rbfinterp.py index 188d5e1d8ad9..74f28dadaf0f 100644 --- a/scipy/interpolate/tests/test_rbfinterp.py +++ b/scipy/interpolate/tests/test_rbfinterp.py @@ -2,7 +2,7 @@ import pytest import numpy as np from numpy.linalg import LinAlgError -from numpy.testing import assert_allclose, assert_array_equal +from numpy.testing import assert_allclose from scipy.stats.qmc import Halton from scipy.spatial import cKDTree from scipy.interpolate._rbfinterp import ( @@ -415,7 +415,7 @@ def test_pickleable(self): yitp1 = interp(xitp) yitp2 = pickle.loads(pickle.dumps(interp))(xitp) - assert_array_equal(yitp1, yitp2) + assert_allclose(yitp1, yitp2, atol=1e-16) class TestRBFInterpolatorNeighborsNone(_TestRBFInterpolator): diff --git a/scipy/interpolate/tests/test_rgi.py b/scipy/interpolate/tests/test_rgi.py index 5503b39dc67e..95b5e2253031 100644 --- a/scipy/interpolate/tests/test_rgi.py +++ b/scipy/interpolate/tests/test_rgi.py @@ -137,7 +137,7 @@ def test_derivatives(self): # 2nd derivatives of a linear function are zero assert_allclose(interp(sample, nu=(0, 1, 1, 0)), - [0, 0, 0], atol=1e-12) + [0, 0, 0], atol=2e-12) @parametrize_rgi_interp_methods def test_complex(self, method): @@ -983,7 +983,11 @@ def test_matrix_input(self, method): v1 = interpn((x, y), values, sample, method=method) v2 = interpn((x, y), np.asarray(values), sample, method=method) - assert_allclose(v1, v2) + if method == "quintic": + # https://github.com/scipy/scipy/issues/20472 + assert_allclose(v1, v2, atol=5e-5, rtol=2e-6) + else: + assert_allclose(v1, v2) def test_length_one_axis(self): # gh-5890, gh-9524 : length-1 axis is legal for method='linear'. diff --git a/scipy/linalg/_testutils.py b/scipy/linalg/_testutils.py index 6aa6b47296f7..f6d01d2b6e59 100644 --- a/scipy/linalg/_testutils.py +++ b/scipy/linalg/_testutils.py @@ -12,6 +12,8 @@ def __init__(self, data): self._data = data def __array__(self, dtype=None, copy=None): + if copy: + return self._data.copy() return self._data diff --git a/scipy/linalg/flapack_sym_herm.pyf.src b/scipy/linalg/flapack_sym_herm.pyf.src index af04e0d8d8d8..42a2ae217d8a 100644 --- a/scipy/linalg/flapack_sym_herm.pyf.src +++ b/scipy/linalg/flapack_sym_herm.pyf.src @@ -124,7 +124,7 @@ subroutine syevd(compute_v,lower,n,w,a,lda,work,lwork,iwork,liwork,info dimension(n),intent(out),depend(n) :: w integer optional,intent(in),depend(n,compute_v) :: lwork=max((compute_v?1+6*n+2*n*n:2*n+1),1) - check(lwork>=(compute_v?1+6*n+2*n*n:2*n+1)) :: lwork + check( (lwork>=((compute_v?1+6*n+2*n*n:2*n+1))) || ((n==1)&&(lwork>=1)) ) :: lwork dimension(lwork),intent(hide,cache),depend(lwork) :: work integer optional,intent(in),depend(n,compute_v) :: liwork = (compute_v?3+5*n:1) @@ -180,7 +180,7 @@ subroutine heevd(compute_v,lower,n,w,a,lda,work,lwork,iwork,liwork,rwo dimension(n),intent(out),depend(n) :: w integer optional,intent(in),depend(n,compute_v) :: lwork=max((compute_v?2*n+n*n:n+1),1) - check(lwork>=(compute_v?2*n+n*n:n+1)) :: lwork + check( (lwork>=(compute_v?2*n+n*n:n+1)) || ((n==1)&&(lwork>=1)) ) :: lwork dimension(lwork),intent(hide,cache),depend(lwork) :: work integer optional,intent(in),depend(n,compute_v) :: liwork = (compute_v?3+5*n:1) diff --git a/scipy/linalg/tests/test_decomp.py b/scipy/linalg/tests/test_decomp.py index 8722b3146820..73d5d46f8d26 100644 --- a/scipy/linalg/tests/test_decomp.py +++ b/scipy/linalg/tests/test_decomp.py @@ -230,8 +230,8 @@ def _check_gen_eig(self, A, B, atol_homog=1e-13, rtol_homog=1e-13): w_fin = -1j * np.real_if_close(1j*w_fin, tol=1e-10) wt_fin = -1j * np.real_if_close(1j*wt_fin, tol=1e-10) - perm = argsort(w_fin) - permt = argsort(wt_fin) + perm = argsort(abs(w_fin) + w_fin.imag) + permt = argsort(abs(wt_fin) + wt_fin.imag) assert_allclose(w_fin[perm], wt_fin[permt], atol=1e-7, rtol=1e-7, err_msg=msg) @@ -877,6 +877,17 @@ def test_various_drivers_standard(self, driver, dtype_): atol=1000*np.finfo(dtype_).eps, rtol=0.) + @pytest.mark.parametrize('driver', ("ev", "evd", "evr", "evx")) + def test_1x1_lwork(self, driver): + w, v = eigh([[1]], driver=driver) + assert_allclose(w, array([1.]), atol=1e-15) + assert_allclose(v, array([[1.]]), atol=1e-15) + + # complex case now + w, v = eigh([[1j]], driver=driver) + assert_allclose(w, array([0]), atol=1e-15) + assert_allclose(v, array([[1.]]), atol=1e-15) + @pytest.mark.parametrize('type', (1, 2, 3)) @pytest.mark.parametrize('driver', ("gv", "gvd", "gvx")) def test_various_drivers_generalized(self, driver, type): diff --git a/scipy/ndimage/_filters.py b/scipy/ndimage/_filters.py index a2907614d5ac..2345e5143081 100644 --- a/scipy/ndimage/_filters.py +++ b/scipy/ndimage/_filters.py @@ -1266,7 +1266,7 @@ def _min_or_max_filter(input, size, footprint, structure, output, mode, else: output[...] = input[...] else: - origins = _ni_support._normalize_sequence(origin, input.ndim) + origins = _ni_support._normalize_sequence(origin, num_axes) if num_axes < input.ndim: if footprint.ndim != num_axes: raise RuntimeError("footprint array has incorrect shape") @@ -1274,17 +1274,23 @@ def _min_or_max_filter(input, size, footprint, structure, output, mode, footprint, tuple(ax for ax in range(input.ndim) if ax not in axes) ) + # set origin = 0 for any axes not being filtered + origins_temp = [0,] * input.ndim + for o, ax in zip(origins, axes): + origins_temp[ax] = o + origins = origins_temp + fshape = [ii for ii in footprint.shape if ii > 0] if len(fshape) != input.ndim: raise RuntimeError('footprint array has incorrect shape.') for origin, lenf in zip(origins, fshape): if (lenf // 2 + origin < 0) or (lenf // 2 + origin >= lenf): - raise ValueError('invalid origin') + raise ValueError("invalid origin") if not footprint.flags.contiguous: footprint = footprint.copy() if structure is not None: if len(structure.shape) != input.ndim: - raise RuntimeError('structure array has incorrect shape') + raise RuntimeError("structure array has incorrect shape") if num_axes != structure.ndim: structure = numpy.expand_dims( structure, diff --git a/scipy/ndimage/src/nd_image.c b/scipy/ndimage/src/nd_image.c index 5f31e5acc911..0190855ac032 100644 --- a/scipy/ndimage/src/nd_image.c +++ b/scipy/ndimage/src/nd_image.c @@ -999,6 +999,11 @@ static PyObject *NI_ValueIndices(PyObject *self, PyObject *args) case NPY_UINT32: CASE_VALUEINDICES_SET_MINMAX(npy_uint32); break; case NPY_INT64: CASE_VALUEINDICES_SET_MINMAX(npy_int64); break; case NPY_UINT64: CASE_VALUEINDICES_SET_MINMAX(npy_uint64); break; + default: + switch(arrType) { + case NPY_UINT: CASE_VALUEINDICES_SET_MINMAX(npy_uint); break; + case NPY_INT: CASE_VALUEINDICES_SET_MINMAX(npy_int); break; + } } NI_ITERATOR_NEXT(ndiIter, arrData); } @@ -1016,6 +1021,11 @@ static PyObject *NI_ValueIndices(PyObject *self, PyObject *args) case NPY_UINT32: CASE_VALUEINDICES_MAKEHISTOGRAM(npy_uint32); break; case NPY_INT64: CASE_VALUEINDICES_MAKEHISTOGRAM(npy_int64); break; case NPY_UINT64: CASE_VALUEINDICES_MAKEHISTOGRAM(npy_uint64); break; + default: + switch(arrType) { + case NPY_INT: CASE_VALUEINDICES_MAKEHISTOGRAM(npy_int); break; + case NPY_UINT: CASE_VALUEINDICES_MAKEHISTOGRAM(npy_uint); break; + } } } @@ -1047,6 +1057,11 @@ static PyObject *NI_ValueIndices(PyObject *self, PyObject *args) case NPY_UINT32: CASE_VALUEINDICES_MAKE_VALUEOBJ_FROMOFFSET(npy_uint32, ii); break; case NPY_INT64: CASE_VALUEINDICES_MAKE_VALUEOBJ_FROMOFFSET(npy_int64, ii); break; case NPY_UINT64: CASE_VALUEINDICES_MAKE_VALUEOBJ_FROMOFFSET(npy_uint64, ii); break; + default: + switch(arrType) { + case NPY_INT: CASE_VALUEINDICES_MAKE_VALUEOBJ_FROMOFFSET(npy_int, ii); break; + case NPY_UINT: CASE_VALUEINDICES_MAKE_VALUEOBJ_FROMOFFSET(npy_uint, ii); break; + } } /* Create a tuple of index arrays */ t = PyTuple_New(ndim); @@ -1093,6 +1108,11 @@ static PyObject *NI_ValueIndices(PyObject *self, PyObject *args) case NPY_UINT32: CASE_VALUEINDICES_GET_VALUEOFFSET(npy_uint32); break; case NPY_INT64: CASE_VALUEINDICES_GET_VALUEOFFSET(npy_int64); break; case NPY_UINT64: CASE_VALUEINDICES_GET_VALUEOFFSET(npy_uint64); break; + default: + switch(arrType) { + case NPY_INT: CASE_VALUEINDICES_GET_VALUEOFFSET(npy_int); break; + case NPY_UINT: CASE_VALUEINDICES_GET_VALUEOFFSET(npy_uint); break; + } } if (ignoreValIsNone || (!valueIsIgnore)) { diff --git a/scipy/ndimage/tests/test_filters.py b/scipy/ndimage/tests/test_filters.py index 6401a69f8627..aeeeb9af98a8 100644 --- a/scipy/ndimage/tests/test_filters.py +++ b/scipy/ndimage/tests/test_filters.py @@ -8,6 +8,7 @@ assert_array_almost_equal, assert_array_equal, assert_almost_equal, suppress_warnings, assert_) +import numpy as np import pytest from pytest import raises as assert_raises @@ -792,6 +793,32 @@ def test_filter_axes_kwargs(self, filter_func, size0, size, kwargs, axes): expected = filter_func(array, *args, size_3d, **kwargs) assert_allclose(output, expected) + @pytest.mark.parametrize("filter_func, kwargs", + [(ndimage.minimum_filter, {}), + (ndimage.maximum_filter, {}), + (ndimage.median_filter, {}), + (ndimage.rank_filter, {"rank": 1}), + (ndimage.percentile_filter, {"percentile": 30})]) + def test_filter_weights_subset_axes_origins(self, filter_func, kwargs): + axes = (-2, -1) + origins = (0, 1) + array = np.arange(6 * 8 * 12, dtype=np.float64).reshape(6, 8, 12) + axes = np.array(axes) + + # weights with ndim matching len(axes) + footprint = np.ones((3, 5), dtype=bool) + footprint[0, 1] = 0 # make non-separable + + output = filter_func( + array, footprint=footprint, axes=axes, origin=origins, **kwargs) + + output0 = filter_func( + array, footprint=footprint, axes=axes, origin=0, **kwargs) + + # output has origin shift on last axis relative to output0, so + # expect shifted arrays to be equal. + np.testing.assert_array_equal(output[:, :, 1:], output0[:, :, :-1]) + @pytest.mark.parametrize( 'filter_func, args', [(ndimage.gaussian_filter, (1.0,)), # args = (sigma,) diff --git a/scipy/ndimage/tests/test_measurements.py b/scipy/ndimage/tests/test_measurements.py index 135e9a72c941..a55b1a601434 100644 --- a/scipy/ndimage/tests/test_measurements.py +++ b/scipy/ndimage/tests/test_measurements.py @@ -10,6 +10,7 @@ assert_equal, suppress_warnings, ) +import pytest from pytest import raises as assert_raises import scipy.ndimage as ndimage @@ -1407,3 +1408,12 @@ def test_watershed_ift09(self): expected = [[1, 1], [1, 1]] assert_allclose(out, expected) + + +@pytest.mark.parametrize("dt", [np.intc, np.uintc]) +def test_gh_19423(dt): + rng = np.random.default_rng(123) + max_val = 8 + image = rng.integers(low=0, high=max_val, size=(10, 12)).astype(dtype=dt) + val_idx = ndimage.value_indices(image) + assert len(val_idx.keys()) == max_val diff --git a/scipy/optimize/_nonlin.py b/scipy/optimize/_nonlin.py index c3c429fd30da..cbaa3d4ced44 100644 --- a/scipy/optimize/_nonlin.py +++ b/scipy/optimize/_nonlin.py @@ -12,6 +12,7 @@ import scipy.sparse.linalg import scipy.sparse from scipy.linalg import get_blas_funcs +from scipy._lib._util import copy_if_needed from scipy._lib._util import getfullargspec_no_self as _getfullargspec from ._linesearch import scalar_search_wolfe1, scalar_search_armijo @@ -701,7 +702,7 @@ def __array__(self, dtype=None, copy=None): def collapse(self): """Collapse the low-rank matrix to a full-rank one.""" - self.collapsed = np.array(self) + self.collapsed = np.array(self, copy=copy_if_needed) self.cs = None self.ds = None self.alpha = None diff --git a/scipy/optimize/_root.py b/scipy/optimize/_root.py index 613ccd82a32e..186f2993896e 100644 --- a/scipy/optimize/_root.py +++ b/scipy/optimize/_root.py @@ -280,9 +280,9 @@ def _root_leastsq(fun, x0, args=(), jac=None, maxiter : int The maximum number of calls to the function. If zero, then 100*(N+1) is the maximum where N is the number of elements in x0. - epsfcn : float + eps : float A suitable step length for the forward-difference approximation of - the Jacobian (for Dfun=None). If epsfcn is less than the machine + the Jacobian (for Dfun=None). If `eps` is less than the machine precision, it is assumed that the relative errors in the functions are of the order of the machine precision. factor : float diff --git a/scipy/sparse/_bsr.py b/scipy/sparse/_bsr.py index 8702fcdc9b45..97de7149baca 100644 --- a/scipy/sparse/_bsr.py +++ b/scipy/sparse/_bsr.py @@ -105,9 +105,9 @@ def __init__(self, arg1, shape=None, dtype=None, copy=False, blocksize=None): except Exception as e: raise ValueError("unrecognized form for" " %s_matrix constructor" % self.format) from e - arg1 = self._coo_container( - arg1, dtype=dtype - ).tobsr(blocksize=blocksize) + if isinstance(self, sparray) and arg1.ndim != 2: + raise ValueError(f"BSR arrays don't support {arg1.ndim}D input. Use 2D") + arg1 = self._coo_container(arg1, dtype=dtype).tobsr(blocksize=blocksize) self.indptr, self.indices, self.data, self._shape = ( arg1.indptr, arg1.indices, arg1.data, arg1._shape ) diff --git a/scipy/sparse/_compressed.py b/scipy/sparse/_compressed.py index dd73fc27b9bf..d8ae861c2040 100644 --- a/scipy/sparse/_compressed.py +++ b/scipy/sparse/_compressed.py @@ -7,7 +7,7 @@ import numpy as np from scipy._lib._util import _prune_array, copy_if_needed -from ._base import _spbase, issparse, SparseEfficiencyWarning +from ._base import _spbase, issparse, SparseEfficiencyWarning, sparray from ._data import _data_matrix, _minmax_mixin from . import _sparsetools from ._sparsetools import (get_csr_submatrix, csr_sample_offsets, csr_todense, @@ -55,6 +55,7 @@ def __init__(self, arg1, shape=None, dtype=None, copy=False): coo = self._coo_container(arg1, shape=shape, dtype=dtype) arrays = coo._coo_to_compressed(self._swap) self.indptr, self.indices, self.data, self._shape = arrays + self.sum_duplicates() elif len(arg1) == 3: # (data, indices, indptr) format (data, indices, indptr) = arg1 @@ -84,6 +85,10 @@ def __init__(self, arg1, shape=None, dtype=None, copy=False): except Exception as e: msg = f"unrecognized {self.format}_matrix constructor usage" raise ValueError(msg) from e + if isinstance(self, sparray) and arg1.ndim < 2 and self.format == "csc": + raise ValueError( + f"CSC arrays don't support {arg1.ndim}D input. Use 2D" + ) coo = self._coo_container(arg1, dtype=dtype) arrays = coo._coo_to_compressed(self._swap) self.indptr, self.indices, self.data, self._shape = arrays diff --git a/scipy/sparse/_coo.py b/scipy/sparse/_coo.py index f8c5039f94ed..ccf536b2b178 100644 --- a/scipy/sparse/_coo.py +++ b/scipy/sparse/_coo.py @@ -79,7 +79,7 @@ def __init__(self, arg1, shape=None, dtype=None, copy=False): if not is_array: M = np.atleast_2d(M) if M.ndim != 2: - raise TypeError('expected dimension <= 2 array or matrix') + raise TypeError(f'expected 2D array or matrix, not {M.ndim}D') self._shape = check_shape(M.shape, allow_1d=is_array) if shape is not None: diff --git a/scipy/sparse/_dia.py b/scipy/sparse/_dia.py index 26512832b81d..3ef22f4596d8 100644 --- a/scipy/sparse/_dia.py +++ b/scipy/sparse/_dia.py @@ -70,6 +70,8 @@ def __init__(self, arg1, shape=None, dtype=None, copy=False): except Exception as e: raise ValueError("unrecognized form for" " %s_matrix constructor" % self.format) from e + if isinstance(self, sparray) and arg1.ndim != 2: + raise ValueError(f"DIA arrays don't support {arg1.ndim}D input. Use 2D") A = self._coo_container(arg1, dtype=dtype, shape=shape).todia() self.data = A.data self.offsets = A.offsets diff --git a/scipy/sparse/_dok.py b/scipy/sparse/_dok.py index c5d7cd60242a..5e5992007465 100644 --- a/scipy/sparse/_dok.py +++ b/scipy/sparse/_dok.py @@ -89,8 +89,8 @@ def __delitem__(self, key, /): def clear(self): return self._dict.clear() - def pop(self, key, default=None, /): - return self._dict.pop(key, default) + def pop(self, /, *args): + return self._dict.pop(*args) def __reversed__(self): raise TypeError("reversed is not defined for dok_array type") diff --git a/scipy/sparse/_lil.py b/scipy/sparse/_lil.py index b55900103861..e267935fc980 100644 --- a/scipy/sparse/_lil.py +++ b/scipy/sparse/_lil.py @@ -57,13 +57,14 @@ def __init__(self, arg1, shape=None, dtype=None, copy=False): A = self._ascontainer(arg1) except TypeError as e: raise TypeError('unsupported matrix type') from e - else: - A = self._csr_container(A, dtype=dtype).tolil() + if isinstance(self, sparray) and A.ndim != 2: + raise ValueError(f"LIL arrays don't support {A.ndim}D input. Use 2D") + A = self._csr_container(A, dtype=dtype).tolil() - self._shape = check_shape(A.shape) - self.dtype = A.dtype - self.rows = A.rows - self.data = A.data + self._shape = check_shape(A.shape) + self.dtype = A.dtype + self.rows = A.rows + self.data = A.data def __iadd__(self,other): self[:,:] = self + other diff --git a/scipy/sparse/linalg/_eigen/tests/test_svds.py b/scipy/sparse/linalg/_eigen/tests/test_svds.py index 7d467b7684b4..587d0eb6ede9 100644 --- a/scipy/sparse/linalg/_eigen/tests/test_svds.py +++ b/scipy/sparse/linalg/_eigen/tests/test_svds.py @@ -266,7 +266,7 @@ def test_svds_parameter_k_which(self, k, which): else: res = svds(A, k=k, which=which, solver=self.solver, random_state=0) - _check_svds(A, k, *res, which=which, atol=8e-10) + _check_svds(A, k, *res, which=which, atol=1e-9, rtol=2e-13) @pytest.mark.filterwarnings("ignore:Exited", reason="Ignore LOBPCG early exit.") diff --git a/scipy/sparse/linalg/tests/test_pydata_sparse.py b/scipy/sparse/linalg/tests/test_pydata_sparse.py index d66a4688735a..b42448d0ef1a 100644 --- a/scipy/sparse/linalg/tests/test_pydata_sparse.py +++ b/scipy/sparse/linalg/tests/test_pydata_sparse.py @@ -121,8 +121,8 @@ def test_svds(matrices): u, s, vt = splin.svds(A_sparse, k=2, v0=v0) assert_allclose(s, s0) - assert_allclose(u, u0) - assert_allclose(vt, vt0) + assert_allclose(np.abs(u), np.abs(u0)) + assert_allclose(np.abs(vt), np.abs(vt0)) def test_lobpcg(matrices): diff --git a/scipy/sparse/tests/test_base.py b/scipy/sparse/tests/test_base.py index 7757384551c5..eb5efa9e1040 100644 --- a/scipy/sparse/tests/test_base.py +++ b/scipy/sparse/tests/test_base.py @@ -292,8 +292,8 @@ def check(dtype): datsp = self.datsp_dtypes[dtype] assert_raises(ValueError, bool, datsp) - assert_(self.spcreator([1])) - assert_(not self.spcreator([0])) + assert_(self.spcreator([[1]])) + assert_(not self.spcreator([[0]])) if isinstance(self, TestDOK): pytest.skip("Cannot create a rank <= 2 DOK matrix.") @@ -3504,7 +3504,7 @@ def test_minmax(self): assert_equal(X.max(), -1) # and a fully sparse matrix - Z = self.spcreator(np.zeros(1)) + Z = self.spcreator(np.zeros((1, 1))) assert_equal(Z.min(), 0) assert_equal(Z.max(), 0) assert_equal(Z.max().dtype, Z.dtype) @@ -3784,8 +3784,8 @@ def sparse_test_class(getset=True, slicing=True, slicing_assign=True, continue old_cls = names.get(name) if old_cls is not None: - raise ValueError("Test class {} overloads test {} defined in {}".format( - cls.__name__, name, old_cls.__name__)) + raise ValueError(f"Test class {cls.__name__} overloads test " + f"{name} defined in {old_cls.__name__}") names[name] = cls return type("TestBase", bases, {}) @@ -3851,6 +3851,10 @@ def test_constructor4(self): dense = array([[2**63 + 1, 0], [0, 1]], dtype=np.uint64) assert_array_equal(dense, csr.toarray()) + # with duplicates (should sum the duplicates) + csr = csr_matrix(([1,1,1,1], ([0,2,2,0], [0,1,1,0]))) + assert csr.nnz == 2 + def test_constructor5(self): # infer dimensions from arrays indptr = array([0,1,3,3]) @@ -4039,8 +4043,8 @@ def test_scalar_idx_dtype(self): def test_binop_explicit_zeros(self): # Check that binary ops don't introduce spurious explicit zeros. # See gh-9619 for context. - a = csr_matrix([0, 1, 0]) - b = csr_matrix([1, 1, 0]) + a = csr_matrix([[0, 1, 0]]) + b = csr_matrix([[1, 1, 0]]) assert (a + b).nnz == 2 assert a.multiply(b).nnz == 1 @@ -4092,6 +4096,10 @@ def test_constructor4(self): csc = csc_matrix((data,ij),(4,3)) assert_array_equal(arange(12).reshape(4, 3), csc.toarray()) + # with duplicates (should sum the duplicates) + csc = csc_matrix(([1,1,1,1], ([0,2,2,0], [0,1,1,0]))) + assert csc.nnz == 2 + def test_constructor5(self): # infer dimensions from arrays indptr = array([0,1,3,3]) @@ -4506,6 +4514,13 @@ def test_todok_duplicates(self): dok = coo.todok() assert_array_equal(dok.toarray(), coo.toarray()) + def test_tocompressed_duplicates(self): + coo = coo_matrix(([1,1,1,1], ([0,2,2,0], [0,1,1,0]))) + csr = coo.tocsr() + assert_equal(csr.nnz + 2, coo.nnz) + csc = coo.tocsc() + assert_equal(csc.nnz + 2, coo.nnz) + def test_eliminate_zeros(self): data = array([1, 0, 0, 0, 2, 0, 3, 0]) row = array([0, 0, 0, 1, 1, 1, 1, 1]) diff --git a/scipy/sparse/tests/test_common1d.py b/scipy/sparse/tests/test_common1d.py index a80d26d73929..555314517f9c 100644 --- a/scipy/sparse/tests/test_common1d.py +++ b/scipy/sparse/tests/test_common1d.py @@ -5,6 +5,9 @@ import numpy as np import scipy as sp +from scipy.sparse import ( + bsr_array, csc_array, dia_array, lil_array, + ) from scipy.sparse._sputils import supported_dtypes, matrix from scipy._lib._util import ComplexWarning @@ -31,6 +34,15 @@ def datsp_math_dtypes(dat1d): } +# Test init with 1D dense input +# sparrays which do not plan to support 1D +@pytest.mark.parametrize("spcreator", [bsr_array, csc_array, dia_array, lil_array]) +def test_no_1d_support_in_init(spcreator): + with pytest.raises(ValueError, match="arrays don't support 1D input"): + spcreator([0, 1, 2, 3]) + + +# Main tests class @pytest.mark.parametrize("spcreator", spcreators) class TestCommon1D: """test common functionality shared by 1D sparse formats""" @@ -54,6 +66,10 @@ def test_neg(self, spcreator): A = np.array([-1, 0, 17, 0, -5, 0, 1, -4, 0, 0, 0, 0], 'd') assert np.array_equal(-A, (-spcreator(A)).toarray()) + def test_1d_supported_init(self, spcreator): + A = spcreator([0, 1, 2, 3]) + assert A.ndim == 1 + def test_reshape_1d_tofrom_row_or_column(self, spcreator): # add a dimension 1d->2d x = spcreator([1, 0, 7, 0, 0, 0, 0, -3, 0, 0, 0, 5]) diff --git a/scipy/sparse/tests/test_dok.py b/scipy/sparse/tests/test_dok.py index 65db3b6ddecd..8823ce8a2dbc 100644 --- a/scipy/sparse/tests/test_dok.py +++ b/scipy/sparse/tests/test_dok.py @@ -83,6 +83,13 @@ def test_pop(d, Asp): assert Asp.pop((0, 1)) == 1 assert d.items() == Asp.items() + assert Asp.pop((22, 21), None) is None + assert Asp.pop((22, 21), "other") == "other" + with pytest.raises(KeyError, match="(22, 21)"): + Asp.pop((22, 21)) + with pytest.raises(TypeError, match="got an unexpected keyword argument"): + Asp.pop((22, 21), default=5) + def test_popitem(d, Asp): assert d.popitem() == Asp.popitem() assert d.items() == Asp.items() diff --git a/scipy/spatial/_qhull.pyx b/scipy/spatial/_qhull.pyx index d6d7ce6361b1..143315aba74e 100644 --- a/scipy/spatial/_qhull.pyx +++ b/scipy/spatial/_qhull.pyx @@ -361,7 +361,8 @@ cdef class _Qhull: "qhull: did not free %d bytes (%d pieces)" % (totlong, curlong)) - self._messages.close() + if self._messages is not None: + self._messages.close() @cython.final def close(self): @@ -387,7 +388,8 @@ cdef class _Qhull: "qhull: did not free %d bytes (%d pieces)" % (totlong, curlong)) - self._messages.close() + if self._messages is not None: + self._messages.close() @cython.final def get_points(self): @@ -1802,6 +1804,8 @@ class Delaunay(_QhullUser): if np.ma.isMaskedArray(points): raise ValueError('Input points cannot be a masked array') points = np.ascontiguousarray(points, dtype=np.double) + if points.ndim != 2: + raise ValueError("Input points array must have 2 dimensions.") if qhull_options is None: if not incremental: @@ -2592,6 +2596,8 @@ class Voronoi(_QhullUser): if np.ma.isMaskedArray(points): raise ValueError('Input points cannot be a masked array') points = np.ascontiguousarray(points, dtype=np.double) + if points.ndim != 2: + raise ValueError("Input points array must have 2 dimensions.") if qhull_options is None: if not incremental: diff --git a/scipy/spatial/tests/test_qhull.py b/scipy/spatial/tests/test_qhull.py index dcf55e8f05b5..9b51ba5ce6d0 100644 --- a/scipy/spatial/tests/test_qhull.py +++ b/scipy/spatial/tests/test_qhull.py @@ -1176,3 +1176,11 @@ def test_cube(self): assert set(a) == set(b) # facet orientation can differ assert_allclose(hs.dual_points, qhalf_points) + + +@pytest.mark.parametrize("diagram_type", [Voronoi, qhull.Delaunay]) +def test_gh_20623(diagram_type): + rng = np.random.default_rng(123) + invalid_data = rng.random((4, 10, 3)) + with pytest.raises(ValueError, match="dimensions"): + diagram_type(invalid_data) diff --git a/scipy/spatial/transform/_rotation.pyx b/scipy/spatial/transform/_rotation.pyx index 14132d1b66e2..c855a27ca7ca 100644 --- a/scipy/spatial/transform/_rotation.pyx +++ b/scipy/spatial/transform/_rotation.pyx @@ -3431,13 +3431,32 @@ cdef class Rotation: # We first find the minimum angle rotation between the primary # vectors. cross = np.cross(b_pri[0], a_pri[0]) - theta = atan2(_norm3(cross), np.dot(a_pri[0], b_pri[0])) - if theta < 1e-3: # small angle Taylor series approximation + cross_norm = _norm3(cross) + theta = atan2(cross_norm, _dot3(a_pri[0], b_pri[0])) + tolerance = 1e-3 # tolerance for small angle approximation (rad) + R_flip = cls.identity() + if (np.pi - theta) < tolerance: + # Near pi radians, the Taylor series appoximation of x/sin(x) + # diverges, so for numerical stability we flip pi and then + # rotate back by the small angle pi - theta + if cross_norm == 0: + # For antiparallel vectors, cross = [0, 0, 0] so we need to + # manually set an arbitrary orthogonal axis of rotation + i = np.argmin(np.abs(a_pri[0])) + r = np.zeros(3) + r[i - 1], r[i - 2] = a_pri[0][i - 2], -a_pri[0][i - 1] + else: + r = cross # Shortest angle orthogonal axis of rotation + R_flip = Rotation.from_rotvec(r / np.linalg.norm(r) * np.pi) + theta = np.pi - theta + cross = -cross + if abs(theta) < tolerance: + # Small angle Taylor series approximation for numerical stability theta2 = theta * theta r = cross * (1 + theta2 / 6 + theta2 * theta2 * 7 / 360) else: r = cross * theta / np.sin(theta) - R_pri = cls.from_rotvec(r) + R_pri = cls.from_rotvec(r) * R_flip if N == 1: # No secondary vectors, so we are done diff --git a/scipy/spatial/transform/tests/test_rotation.py b/scipy/spatial/transform/tests/test_rotation.py index 8a1176d4a261..2b2356da4a84 100644 --- a/scipy/spatial/transform/tests/test_rotation.py +++ b/scipy/spatial/transform/tests/test_rotation.py @@ -1417,6 +1417,32 @@ def test_align_vectors_parallel(): assert_allclose(R.apply(b[0]), a[0], atol=atol) +def test_align_vectors_antiparallel(): + # Test exact 180 deg rotation + atol = 1e-12 + as_to_test = np.array([[[1, 0, 0], [0, 1, 0]], + [[0, 1, 0], [1, 0, 0]], + [[0, 0, 1], [0, 1, 0]]]) + bs_to_test = [[-a[0], a[1]] for a in as_to_test] + for a, b in zip(as_to_test, bs_to_test): + R, _ = Rotation.align_vectors(a, b, weights=[np.inf, 1]) + assert_allclose(R.magnitude(), np.pi, atol=atol) + assert_allclose(R.apply(b[0]), a[0], atol=atol) + + # Test exact rotations near 180 deg + Rs = Rotation.random(100, random_state=0) + dRs = Rotation.from_rotvec(Rs.as_rotvec()*1e-4) # scale down to small angle + a = [[ 1, 0, 0], [0, 1, 0]] + b = [[-1, 0, 0], [0, 1, 0]] + as_to_test = [] + for dR in dRs: + as_to_test.append([dR.apply(a[0]), a[1]]) + for a in as_to_test: + R, _ = Rotation.align_vectors(a, b, weights=[np.inf, 1]) + R2, _ = Rotation.align_vectors(a, b, weights=[1e10, 1]) + assert_allclose(R.as_matrix(), R2.as_matrix(), atol=atol) + + def test_align_vectors_primary_only(): atol = 1e-12 mats_a = Rotation.random(100, random_state=0).as_matrix() diff --git a/scipy/special/_basic.py b/scipy/special/_basic.py index 36fe74a5427d..f82dc5f586c6 100644 --- a/scipy/special/_basic.py +++ b/scipy/special/_basic.py @@ -2931,7 +2931,8 @@ def corr(k, r): return np.power(k, -r / k) / gamma(r / k + 1) * r for r in np.unique(n_mod_k): if r == 0: continue - result[n_mod_k == r] *= corr(k, r) + # cast to int because uint types break on `-r` + result[n_mod_k == r] *= corr(k, int(r)) return result diff --git a/scipy/special/_spherical_bessel.pxd b/scipy/special/_spherical_bessel.pxd index 1ae0982bbec2..b06ba27df036 100644 --- a/scipy/special/_spherical_bessel.pxd +++ b/scipy/special/_spherical_bessel.pxd @@ -343,7 +343,10 @@ cdef inline double spherical_in_d_real(long n, double x) noexcept nogil: return spherical_in_real(1, x) else: if x == 0: - return 0 + if n == 1: + return 1 / 3. + else: + return 0 return (spherical_in_real(n - 1, x) - (n + 1)*spherical_in_real(n, x)/x) diff --git a/scipy/special/tests/test_basic.py b/scipy/special/tests/test_basic.py index 19aa763ca55f..d941ef16254e 100644 --- a/scipy/special/tests/test_basic.py +++ b/scipy/special/tests/test_basic.py @@ -2171,9 +2171,13 @@ def _check(res, nucleus): _check(special.factorialk(n, 3, exact=exact), exp_nucleus[3]) @pytest.mark.parametrize("exact", [True, False]) + @pytest.mark.parametrize("dtype", [ + None, int, np.int8, np.int16, np.int32, np.int64, + np.uint8, np.uint16, np.uint32, np.uint64 + ]) @pytest.mark.parametrize("dim", range(0, 5)) - def test_factorialx_array_dimension(self, dim, exact): - n = np.array(5, ndmin=dim) + def test_factorialx_array_dimension(self, dim, dtype, exact): + n = np.array(5, dtype=dtype, ndmin=dim) exp = {1: 120, 2: 15, 3: 10} assert_allclose(special.factorial(n, exact=exact), np.array(exp[1], ndmin=dim)) diff --git a/scipy/special/tests/test_spherical_bessel.py b/scipy/special/tests/test_spherical_bessel.py index 7ddfe0083d6d..847bb3b49103 100644 --- a/scipy/special/tests/test_spherical_bessel.py +++ b/scipy/special/tests/test_spherical_bessel.py @@ -286,9 +286,10 @@ def df(self, n, z): return spherical_in(n, z, derivative=True) def test_spherical_in_d_zero(self): - n = np.array([1, 2, 3, 7, 15]) + n = np.array([0, 1, 2, 3, 7, 15]) + spherical_in(n, 0, derivative=False) assert_allclose(spherical_in(n, 0, derivative=True), - np.zeros(5)) + np.array([0, 1/3, 0, 0, 0, 0])) class TestSphericalKnDerivatives(SphericalDerivativesTestCase): diff --git a/scipy/stats/_continuous_distns.py b/scipy/stats/_continuous_distns.py index 13af050097f4..c9481c2e0cda 100644 --- a/scipy/stats/_continuous_distns.py +++ b/scipy/stats/_continuous_distns.py @@ -692,12 +692,10 @@ def _sf(self, x, a, b): return _boost._beta_sf(x, a, b) def _isf(self, x, a, b): - with np.errstate(over='ignore'): # see gh-17432 - return _boost._beta_isf(x, a, b) + return sc.betainccinv(a, b, x) def _ppf(self, q, a, b): - with np.errstate(over='ignore'): # see gh-17432 - return _boost._beta_ppf(q, a, b) + return sc.betaincinv(a, b, q) def _stats(self, a, b): return ( diff --git a/scipy/stats/_discrete_distns.py b/scipy/stats/_discrete_distns.py index 169222855fdd..807245b3f19d 100644 --- a/scipy/stats/_discrete_distns.py +++ b/scipy/stats/_discrete_distns.py @@ -1658,7 +1658,7 @@ def _stats(self, alpha): np.inf) g1 = np.where(alpha <= 2, np.nan, g1) g2 = np.where(alpha > 4, - alpha + 3 + ((alpha**3 - 49 * alpha - 22) / + alpha + 3 + ((11 * alpha**3 - 49 * alpha - 22) / (alpha * (alpha - 4) * (alpha - 3))), np.inf) g2 = np.where(alpha <= 2, np.nan, g2) diff --git a/scipy/stats/_distr_params.py b/scipy/stats/_distr_params.py index c70299a5abdb..2b86298d592f 100644 --- a/scipy/stats/_distr_params.py +++ b/scipy/stats/_distr_params.py @@ -142,7 +142,7 @@ ['poisson', (0.6,)], ['randint', (7, 31)], ['skellam', (15, 8)], - ['zipf', (6.5,)], + ['zipf', (6.6,)], ['zipfian', (0.75, 15)], ['zipfian', (1.25, 10)], ['yulesimon', (11.0,)], diff --git a/scipy/stats/tests/test_discrete_basic.py b/scipy/stats/tests/test_discrete_basic.py index bce36b97c1f4..012e68a8da2d 100644 --- a/scipy/stats/tests/test_discrete_basic.py +++ b/scipy/stats/tests/test_discrete_basic.py @@ -91,7 +91,9 @@ def test_moments(distname, arg): check_mean_expect(distfn, arg, m, distname) check_var_expect(distfn, arg, m, v, distname) check_skew_expect(distfn, arg, m, v, s, distname) - if distname not in ['zipf', 'yulesimon', 'betanbinom']: + with np.testing.suppress_warnings() as sup: + if distname in ['zipf', 'betanbinom']: + sup.filter(RuntimeWarning) check_kurt_expect(distfn, arg, m, v, k, distname) # frozen distr moments diff --git a/scipy/stats/tests/test_distributions.py b/scipy/stats/tests/test_distributions.py index 6d91bb8a6c33..4623540b81a9 100644 --- a/scipy/stats/tests/test_distributions.py +++ b/scipy/stats/tests/test_distributions.py @@ -13,7 +13,7 @@ from numpy.testing import (assert_equal, assert_array_equal, assert_almost_equal, assert_array_almost_equal, assert_allclose, assert_, assert_warns, - assert_array_less, suppress_warnings, IS_PYPY) + assert_array_less, suppress_warnings) import pytest from pytest import raises as assert_raises @@ -4454,7 +4454,7 @@ def test_endpoints(self): assert_equal(stats.beta.pdf(1, a, b), 5) assert_equal(stats.beta.pdf(1-1e-310, a, b), 5) - @pytest.mark.xfail(IS_PYPY, reason="Does not convert boost warning") + @pytest.mark.xfail(reason="Does not warn on special codepath") def test_boost_eval_issue_14606(self): q, a, b = 0.995, 1.0e11, 1.0e13 with pytest.warns(RuntimeWarning): diff --git a/tools/generate_f2pymod.py b/tools/generate_f2pymod.py index f49be2ce628b..74bfc4064d94 100644 --- a/tools/generate_f2pymod.py +++ b/tools/generate_f2pymod.py @@ -289,9 +289,9 @@ def main(): cwd=os.getcwd()) out, err = p.communicate() if not (p.returncode == 0): - raise RuntimeError(f"Writing {args.outfile} with f2py failed!\n" - f"{out}\n" - r"{err}") + raise RuntimeError(f"Processing {fname_pyf} with f2py failed!\n" + f"{out.decode()}\n" + f"{err.decode()}") if __name__ == "__main__": diff --git a/tools/lint.toml b/tools/lint.toml index 2f04d356026f..6012d021dcfb 100644 --- a/tools/lint.toml +++ b/tools/lint.toml @@ -11,7 +11,8 @@ target-version = "py39" # and `B028` which checks that warnings include the `stacklevel` keyword. # `B028` added in gh-19623. select = ["E", "F", "PGH004", "UP", "B028"] -ignore = ["E741"] +# UP031 should be enabled once someone fixes the errors. +ignore = ["E741", "UP031", "UP032"] # Allow unused variables when underscore-prefixed. dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" diff --git a/tools/openblas_support.py b/tools/openblas_support.py index 90fe56af7eb1..d06efbdc3f57 100644 --- a/tools/openblas_support.py +++ b/tools/openblas_support.py @@ -13,8 +13,8 @@ from urllib.request import urlopen, Request from urllib.error import HTTPError -OPENBLAS_V = '0.3.26.dev' -OPENBLAS_LONG = 'v0.3.26-382-gb1e8ba50' +OPENBLAS_V = '0.3.27' +OPENBLAS_LONG = 'v0.3.27' BASE_LOC = 'https://anaconda.org/multibuild-wheels-staging/openblas-libs' NIGHTLY_BASE_LOC = ( 'https://anaconda.org/scientific-python-nightly-wheels/openblas-libs' diff --git a/tools/wheels/cibw_before_build_macos.sh b/tools/wheels/cibw_before_build_macos.sh index 1a664a309083..c6bf42fa0671 100644 --- a/tools/wheels/cibw_before_build_macos.sh +++ b/tools/wheels/cibw_before_build_macos.sh @@ -76,4 +76,6 @@ if [[ $PLATFORM == "macosx-arm64" ]]; then hdiutil attach -mountpoint /Volumes/gfortran gfortran.dmg sudo installer -pkg /Volumes/gfortran/gfortran.pkg -target / type -p gfortran + which gfortran + gfortran --version fi