diff --git a/doc/API.rst.txt b/doc/API.rst.txt index f0b7b51f4546..538554dd8906 100644 --- a/doc/API.rst.txt +++ b/doc/API.rst.txt @@ -125,6 +125,8 @@ change is made. * `scipy.optimize` + - `scipy.optimize.cython_optimize` + * `scipy.signal` - `scipy.signal.windows` diff --git a/doc/release/1.9.1-notes.rst b/doc/release/1.9.1-notes.rst index 5fc910facbdd..f061897e1af8 100644 --- a/doc/release/1.9.1-notes.rst +++ b/doc/release/1.9.1-notes.rst @@ -5,15 +5,44 @@ SciPy 1.9.1 Release Notes .. contents:: SciPy 1.9.1 is a bug-fix release with no new features -compared to 1.9.0. +compared to 1.9.0. Notably, some important meson build +fixes are included. Authors ======= +* Anirudh Dagar (1) +* Ralf Gommers (4) +* Matt Haberland (2) +* Tyler Reddy (10) +* Atsushi Sakai (1) +* Eli Schwartz (1) +* Warren Weckesser (1) + +A total of 7 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.9.1 ----------------------- +* `#14517 `__: scipy/linalg/tests/test_decomp.py::TestSchur::test_sort test... +* `#16765 `__: DOC: \`scipy.stats.skew\` no longer returns 0 on constant input +* `#16787 `__: BUG: Can't build 1.10 with mingw-w64 toolchain and numpy 1.21.6... +* `#16813 `__: BUG: scipy.interpolate interp1d extrapolate behaviour change... +* `#16878 `__: BUG: optimize.milp fails to execute when given exactly 3 constraints + Pull requests for 1.9.1 ----------------------- + +* `#16736 `__: REL: prep for SciPy 1.9.1 +* `#16749 `__: BLD: install missing \`.pxd\` files, and update TODOs/FIXMEs... +* `#16750 `__: BLD: make OpenBLAS detection work with CMake +* `#16760 `__: BLD: use a bit more idiomatic approach to constructing paths... +* `#16768 `__: DOC: stats.skew/kurtosis: returns NaN when input has only one... +* `#16794 `__: BLD/REL: on Windows use numpy 1.22.3 as the version to build... +* `#16822 `__: BUG/TST: linalg: Check the results of 'schur' more carefully. +* `#16825 `__: BUG: interpolate: fix "previous" and "next" extrapolate logic... +* `#16862 `__: BUG, DOC: Fix sphinx autosummary generation for \`odr\` and \`czt\` +* `#16881 `__: MAINT: optimize.milp: fix input validation when three constraints... diff --git a/doc/source/conf.py b/doc/source/conf.py index 578a9e3e482e..ad17e99c8d75 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -261,6 +261,16 @@ autosummary_generate = True +# maps functions with a name same as a class name that is indistinguishable +# Ex: scipy.signal.czt and scipy.signal.CZT or scipy.odr.odr and scipy.odr.ODR +# Otherwise, the stubs are overwritten when the name is same for +# OS (like MacOS) which has a filesystem that ignores the case +# See https://github.com/sphinx-doc/sphinx/pull/7927 +autosummary_filename_map = { + "scipy.odr.odr": "odr-function", + "scipy.signal.czt": "czt-function", +} + # ----------------------------------------------------------------------------- # Autodoc diff --git a/environment.yml b/environment.yml index e33f3c9b8fa1..cf99b7a0d74d 100644 --- a/environment.yml +++ b/environment.yml @@ -45,4 +45,4 @@ dependencies: - rich-click - click - doit>=0.36.0 - - pydevtool==0.2.0 + - pydevtool diff --git a/meson.build b/meson.build index b23afa7a5ae9..6b254f0ba0ee 100644 --- a/meson.build +++ b/meson.build @@ -10,7 +10,7 @@ project( default_options: [ 'buildtype=debugoptimized', 'c_std=c99', - 'cpp_std=c++14', # TODO: use c++11 if 14 is not available + 'cpp_std=c++14', # TODO: the below -Wno flags are all needed to silence warnings in # f2py-generated code. This should be fixed in f2py itself. 'c_args=-Wno-unused-function -Wno-conversion -Wno-misleading-indentation -Wno-incompatible-pointer-types', diff --git a/pyproject.toml b/pyproject.toml index 1cf1a1d6f33e..f0ede132507d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,24 +37,32 @@ requires = [ # loongarch64 requires numpy>=1.22.0 "numpy==1.22.0; platform_machine=='loongarch64'", + # On Windows we need to avoid 1.21.6, 1.22.0 and 1.22.1 because they were + # built with vc142. 1.22.3 is the first version that has 32-bit Windows + # wheels *and* was built with vc141. So use that: + "numpy==1.22.3; python_version=='3.10' and platform_system=='Windows' and platform_python_implementation != 'PyPy'", + # default numpy requirements "numpy==1.18.5; python_version=='3.8' and (platform_machine!='arm64' or platform_system!='Darwin') and platform_machine!='aarch64' and platform_machine!='loongarch64' and platform_python_implementation != 'PyPy'", "numpy==1.19.3; python_version=='3.9' and (platform_machine!='arm64' or platform_system!='Darwin') and platform_machine!='loongarch64' and platform_python_implementation != 'PyPy'", # Note that 1.21.3 was the first version with a complete set of 3.10 wheels, - # however macOS was broken and it's safe to build against 1.21.6 on all platforms + # however macOS was broken and it's safe C API/ABI-wise to build against 1.21.6 # (see oldest-supported-numpy issues gh-28 and gh-45) - "numpy==1.21.6; python_version=='3.10' and platform_machine!='loongarch64' and platform_python_implementation != 'PyPy'", + "numpy==1.21.6; python_version=='3.10' and (platform_machine!='win32' and platform_machine!='loongarch64') and platform_python_implementation != 'PyPy'", + "numpy==1.23.2; python_version=='3.11' and platform_python_implementation != 'PyPy'", # For Python versions which aren't yet officially supported, # we specify an unpinned NumPy which allows source distributions # to be used and allows wheels to be used as soon as they # become available. - "numpy; python_version>='3.11'", + "numpy; python_version>='3.12'", "numpy; python_version>='3.8' and platform_python_implementation=='PyPy'", ] [project] name = "SciPy" +# TODO: add `license-files` once PEP 639 is accepted (see meson-python#88) +# at that point, no longer include them in `py3.install_sources()` license = {file = "LICENSE.txt"} description = "Fundamental algorithms for scientific computing in Python" maintainers = [ diff --git a/scipy/_lib/_uarray/meson.build b/scipy/_lib/_uarray/meson.build index 88986370b23f..dd66899fc654 100644 --- a/scipy/_lib/_uarray/meson.build +++ b/scipy/_lib/_uarray/meson.build @@ -1,8 +1,3 @@ -# TODO: add_data_files('license') - -# TODO: this used -std=c++14 if available, add c++11 otherwise -# can we now rely on c++14 unconditionally? - py3.extension_module('_uarray', ['_uarray_dispatch.cxx', 'vectorcall.cxx'], cpp_args: ['-Wno-terminate', '-Wno-unused-function'], @@ -15,6 +10,7 @@ py3.extension_module('_uarray', python_sources = [ '__init__.py', '_backend.py', + 'LICENSE' ] py3.install_sources( diff --git a/scipy/fft/_pocketfft/meson.build b/scipy/fft/_pocketfft/meson.build index b3be2c206ab5..4147c8c7853d 100644 --- a/scipy/fft/_pocketfft/meson.build +++ b/scipy/fft/_pocketfft/meson.build @@ -16,7 +16,6 @@ else pocketfft_threads += ['-DPOCKETFFT_NO_MULTITHREADING'] endif -#TODO: add the equivalent of set_cxx_flags_hook py3.extension_module('pypocketfft', 'pypocketfft.cxx', cpp_args: pocketfft_threads, @@ -32,6 +31,7 @@ python_sources = [ '__init__.py', 'basic.py', 'helper.py', + 'LICENSE.md', 'realtransforms.py' ] diff --git a/scipy/integrate/meson.build b/scipy/integrate/meson.build index d0765b6d19bc..651890758473 100644 --- a/scipy/integrate/meson.build +++ b/scipy/integrate/meson.build @@ -190,8 +190,6 @@ py3.extension_module('_test_multivariate', subdir: 'scipy/integrate' ) -# FIXME: something is wrong with the signature file, subroutines are not used -# _test_odeint_banded _test_odeint_banded_module = custom_target('_test_odeint_banded_module', output: ['_test_odeint_bandedmodule.c', '_test_odeint_banded-f2pywrappers.f'], input: 'tests/banded5x5.pyf', diff --git a/scipy/interpolate/_interpolate.py b/scipy/interpolate/_interpolate.py index 59a1e1592c38..f5013cc7a1e6 100644 --- a/scipy/interpolate/_interpolate.py +++ b/scipy/interpolate/_interpolate.py @@ -519,7 +519,8 @@ def __init__(self, x, y, kind='linear', axis=-1, self._call = self.__class__._call_previousnext if _do_extrapolate(fill_value): self._check_and_update_bounds_error_for_extrapolation() - fill_value = (np.nan, self.y.max(axis=axis)) + # assume y is sorted by x ascending order here. + fill_value = (np.nan, np.take(self.y, -1, axis)) elif kind == 'next': self._side = 'right' self._ind = 1 @@ -528,7 +529,8 @@ def __init__(self, x, y, kind='linear', axis=-1, self._call = self.__class__._call_previousnext if _do_extrapolate(fill_value): self._check_and_update_bounds_error_for_extrapolation() - fill_value = (self.y.min(axis=axis), np.nan) + # assume y is sorted by x ascending order here. + fill_value = (np.take(self.y, 0, axis), np.nan) else: # Check if we can delegate to numpy.interp (2x-10x faster). np_types = (np.float_, np.int_) diff --git a/scipy/interpolate/tests/test_interpolate.py b/scipy/interpolate/tests/test_interpolate.py index 3031650a747c..22a479f7d2f0 100644 --- a/scipy/interpolate/tests/test_interpolate.py +++ b/scipy/interpolate/tests/test_interpolate.py @@ -122,6 +122,28 @@ def setup_method(self): self.y235 = np.arange(30.).reshape((2, 3, 5)) self.y325 = np.arange(30.).reshape((3, 2, 5)) + # Edge updated test matrix 1 + # array([[ 30, 1, 2, 3, 4, 5, 6, 7, 8, -30], + # [ 30, 11, 12, 13, 14, 15, 16, 17, 18, -30]]) + self.y210_edge_updated = np.arange(20.).reshape((2, 10)) + self.y210_edge_updated[:, 0] = 30 + self.y210_edge_updated[:, -1] = -30 + + # Edge updated test matrix 2 + # array([[ 30, 30], + # [ 2, 3], + # [ 4, 5], + # [ 6, 7], + # [ 8, 9], + # [ 10, 11], + # [ 12, 13], + # [ 14, 15], + # [ 16, 17], + # [-30, -30]]) + self.y102_edge_updated = np.arange(20.).reshape((10, 2)) + self.y102_edge_updated[0, :] = 30 + self.y102_edge_updated[-1, :] = -30 + self.fill_value = -100.0 def test_validation(self): @@ -386,6 +408,36 @@ def test_previous(self): bounds_error=True) assert_raises(ValueError, interp1d, self.x10, self.y10, **opts) + # Tests for gh-16813 + interpolator1D = interp1d([0, 1, 2], + [0, 1, -1], kind="previous", + fill_value='extrapolate', + assume_sorted=True) + assert_allclose(interpolator1D([-2, -1, 0, 1, 2, 3, 5]), + [np.nan, np.nan, 0, 1, -1, -1, -1]) + + interpolator1D = interp1d([2, 0, 1], # x is not ascending + [-1, 0, 1], kind="previous", + fill_value='extrapolate', + assume_sorted=False) + assert_allclose(interpolator1D([-2, -1, 0, 1, 2, 3, 5]), + [np.nan, np.nan, 0, 1, -1, -1, -1]) + + interpolator2D = interp1d(self.x10, self.y210_edge_updated, + kind="previous", + fill_value='extrapolate') + assert_allclose(interpolator2D([-1, -2, 5, 8, 12, 25]), + [[np.nan, np.nan, 5, 8, -30, -30], + [np.nan, np.nan, 15, 18, -30, -30]]) + + interpolator2DAxis0 = interp1d(self.x10, self.y102_edge_updated, + kind="previous", + axis=0, fill_value='extrapolate') + assert_allclose(interpolator2DAxis0([-2, 5, 12]), + [[np.nan, np.nan], + [10, 11], + [-30, -30]]) + def test_next(self): # Check the actual implementation of next interpolation. interp10 = interp1d(self.x10, self.y10, kind='next') @@ -425,6 +477,36 @@ def test_next(self): bounds_error=True) assert_raises(ValueError, interp1d, self.x10, self.y10, **opts) + # Tests for gh-16813 + interpolator1D = interp1d([0, 1, 2], + [0, 1, -1], kind="next", + fill_value='extrapolate', + assume_sorted=True) + assert_allclose(interpolator1D([-2, -1, 0, 1, 2, 3, 5]), + [0, 0, 0, 1, -1, np.nan, np.nan]) + + interpolator1D = interp1d([2, 0, 1], # x is not ascending + [-1, 0, 1], kind="next", + fill_value='extrapolate', + assume_sorted=False) + assert_allclose(interpolator1D([-2, -1, 0, 1, 2, 3, 5]), + [0, 0, 0, 1, -1, np.nan, np.nan]) + + interpolator2D = interp1d(self.x10, self.y210_edge_updated, + kind="next", + fill_value='extrapolate') + assert_allclose(interpolator2D([-1, -2, 5, 8, 12, 25]), + [[30, 30, 5, 8, np.nan, np.nan], + [30, 30, 15, 18, np.nan, np.nan]]) + + interpolator2DAxis0 = interp1d(self.x10, self.y102_edge_updated, + kind="next", + axis=0, fill_value='extrapolate') + assert_allclose(interpolator2DAxis0([-2, 5, 12]), + [[30, 30], + [10, 11], + [np.nan, np.nan]]) + def test_zero(self): # Check the actual implementation of zero-order spline interpolation. interp10 = interp1d(self.x10, self.y10, kind='zero') diff --git a/scipy/linalg/meson.build b/scipy/linalg/meson.build index ff0e8394cf90..4cb4a3634220 100644 --- a/scipy/linalg/meson.build +++ b/scipy/linalg/meson.build @@ -25,10 +25,10 @@ cython_linalg = custom_target('cython_linalg', ], input: '_generate_pyx.py', command: [py3, '@INPUT@', '-o', '@OUTDIR@'], - # FIXME - we only want to install the .pxd files! See comments for - # `pxd_files` further down. + # TODO - we only want to install the .pxd files! See comments for + # `pxd_files` further down. install: true, - install_dir: py3.get_install_dir() + 'scipy/linalg' + install_dir: py3.get_install_dir() / 'scipy/linalg' ) # pyx -> c, pyx -> cpp generators, depending on __init__.py here. @@ -166,7 +166,6 @@ py3.extension_module('_interpolative', '-Wno-tabs', '-Wno-conversion', '-Wno-argument-mismatch', '-Wno-unused-dummy-argument', '-Wno-maybe-uninitialized' ], - # TODO: add -fallow-argument-mismatch for gfortran >= 10, see gh-11842 include_directories: [inc_np, inc_f2py], dependencies: [py3_dep, lapack], install: true, @@ -332,7 +331,7 @@ py3.install_sources( # https://mesonbuild.com/Installing.html says is for build targets to # use: # `custom_target(..., install: true, install_dir: ...) -# # should use `py3.get_install_dir() + 'scipy/linalg'` ? +# # should use `py3.get_install_dir() / 'scipy/linalg'` ? # see https://github.com/mesonbuild/meson/issues/3206 # # For the below code to work, the script generating the files should use @@ -350,7 +349,7 @@ py3.install_sources( # output : ['cython_blas2.pxd', 'cython_lapack2.pxd'], # command : ['cp', '@INPUT0@', '@OUTPUT0@', '&&', 'cp', '@INPUT1@', '@OUTPUT1@'], # install : true, -# install_dir: py3.get_install_dir() + 'scipy/linalg' +# install_dir: py3.get_install_dir() / 'scipy/linalg' #) subdir('tests') diff --git a/scipy/linalg/tests/test_decomp.py b/scipy/linalg/tests/test_decomp.py index 93d62e9d7b2e..bfee385e6cd2 100644 --- a/scipy/linalg/tests/test_decomp.py +++ b/scipy/linalg/tests/test_decomp.py @@ -1899,84 +1899,43 @@ def test_check_finite(self): class TestSchur: + def check_schur(self, a, t, u, rtol, atol): + # Check that the Schur decomposition is correct. + assert_allclose(u @ t @ u.conj().T, a, rtol=rtol, atol=atol, + err_msg="Schur decomposition does not match 'a'") + # The expected value of u @ u.H - I is all zeros, so test + # with absolute tolerance only. + assert_allclose(u @ u.conj().T - np.eye(len(u)), 0, rtol=0, atol=atol, + err_msg="u is not unitary") + def test_simple(self): a = [[8, 12, 3], [2, 9, 3], [10, 3, 6]] t, z = schur(a) - assert_array_almost_equal(z @ t @ z.conj().T, a) + self.check_schur(a, t, z, rtol=1e-14, atol=5e-15) tc, zc = schur(a, 'complex') assert_(np.any(ravel(iscomplex(zc))) and np.any(ravel(iscomplex(tc)))) - assert_array_almost_equal(zc @ tc @ zc.conj().T, a) + self.check_schur(a, tc, zc, rtol=1e-14, atol=5e-15) tc2, zc2 = rsf2csf(tc, zc) - assert_array_almost_equal(zc2 @ tc2 @ zc2.conj().T, a) - - def test_sort(self): + self.check_schur(a, tc2, zc2, rtol=1e-14, atol=5e-15) + + @pytest.mark.parametrize( + 'sort, expected_diag', + [('lhp', [-np.sqrt(2), -0.5, np.sqrt(2), 0.5]), + ('rhp', [np.sqrt(2), 0.5, -np.sqrt(2), -0.5]), + ('iuc', [-0.5, 0.5, np.sqrt(2), -np.sqrt(2)]), + ('ouc', [np.sqrt(2), -np.sqrt(2), -0.5, 0.5]), + (lambda x: x >= 0.0, [np.sqrt(2), 0.5, -np.sqrt(2), -0.5])] + ) + def test_sort(self, sort, expected_diag): + # The exact eigenvalues of this matrix are + # -sqrt(2), sqrt(2), -1/2, 1/2. a = [[4., 3., 1., -1.], [-4.5, -3.5, -1., 1.], [9., 6., -4., 4.5], [6., 4., -3., 3.5]] - s, u, sdim = schur(a, sort='lhp') - assert_array_almost_equal([[0.1134, 0.5436, 0.8316, 0.], - [-0.1134, -0.8245, 0.5544, 0.], - [-0.8213, 0.1308, 0.0265, -0.5547], - [-0.5475, 0.0872, 0.0177, 0.8321]], - u, 3) - assert_array_almost_equal([[-1.4142, 0.1456, -11.5816, -7.7174], - [0., -0.5000, 9.4472, -0.7184], - [0., 0., 1.4142, -0.1456], - [0., 0., 0., 0.5]], - s, 3) - assert_equal(2, sdim) - - s, u, sdim = schur(a, sort='rhp') - assert_array_almost_equal([[0.4862, -0.4930, 0.1434, -0.7071], - [-0.4862, 0.4930, -0.1434, -0.7071], - [0.6042, 0.3944, -0.6924, 0.], - [0.4028, 0.5986, 0.6924, 0.]], - u, 3) - assert_array_almost_equal([[1.4142, -0.9270, 4.5368, -14.4130], - [0., 0.5, 6.5809, -3.1870], - [0., 0., -1.4142, 0.9270], - [0., 0., 0., -0.5]], - s, 3) - assert_equal(2, sdim) - - s, u, sdim = schur(a, sort='iuc') - assert_array_almost_equal([[0.5547, 0., -0.5721, -0.6042], - [-0.8321, 0., -0.3814, -0.4028], - [0., 0.7071, -0.5134, 0.4862], - [0., 0.7071, 0.5134, -0.4862]], - u, 3) - assert_array_almost_equal([[-0.5000, 0.0000, -6.5809, -4.0974], - [0., 0.5000, -3.3191, -14.4130], - [0., 0., 1.4142, 2.1573], - [0., 0., 0., -1.4142]], - s, 3) - assert_equal(2, sdim) - - s, u, sdim = schur(a, sort='ouc') - assert_array_almost_equal([[0.4862, -0.5134, 0.7071, 0.], - [-0.4862, 0.5134, 0.7071, 0.], - [0.6042, 0.5721, 0., -0.5547], - [0.4028, 0.3814, 0., 0.8321]], - u, 3) - assert_array_almost_equal([[1.4142, -2.1573, 14.4130, 4.0974], - [0., -1.4142, 3.3191, 6.5809], - [0., 0., -0.5000, 0.], - [0., 0., 0., 0.5000]], - s, 3) - assert_equal(2, sdim) - - s, u, sdim = schur(a, sort=lambda x: x >= 0.0) - assert_array_almost_equal([[0.4862, -0.4930, 0.1434, -0.7071], - [-0.4862, 0.4930, -0.1434, -0.7071], - [0.6042, 0.3944, -0.6924, 0.], - [0.4028, 0.5986, 0.6924, 0.]], - u, 3) - assert_array_almost_equal([[1.4142, -0.9270, 4.5368, -14.4130], - [0., 0.5, 6.5809, -3.1870], - [0., 0., -1.4142, 0.9270], - [0., 0., 0., -0.5]], - s, 3) + t, u, sdim = schur(a, sort=sort) + self.check_schur(a, t, u, rtol=1e-14, atol=5e-15) + assert_allclose(np.diag(t), expected_diag, rtol=1e-12) assert_equal(2, sdim) def test_sort_errors(self): diff --git a/scipy/meson.build b/scipy/meson.build index a1e97b2ddaa9..086a44779d25 100644 --- a/scipy/meson.build +++ b/scipy/meson.build @@ -117,8 +117,18 @@ numpy_nodepr_api = '-DNPY_NO_DEPRECATED_API=NPY_1_9_API_VERSION' # For MKL and for auto-detecting one of multiple libs, we'll need a custom # dependency in Meson (like is done for scalapack) - see # https://github.com/mesonbuild/meson/issues/2835 -blas = dependency(get_option('blas')) -lapack = dependency(get_option('lapack')) +blas_name = get_option('blas') +lapack_name = get_option('lapack') +# pkg-config uses a lower-case name while CMake uses a capitalized name, so try +# that too to make the fallback detection with CMake work +if blas_name == 'openblas' + blas_name = ['openblas', 'OpenBLAS'] +endif +if lapack_name == 'openblas' + lapack_name = ['openblas', 'OpenBLAS'] +endif +blas = dependency(blas_name) +lapack = dependency(lapack_name) if blas.name() == 'mkl' or lapack.name() == 'mkl' or get_option('use-g77-abi') g77_abi_wrappers = files([ @@ -137,11 +147,9 @@ generate_config = custom_target( output: '__config__.py', input: '../tools/config_utils.py', command: [py3, '@INPUT@', '@OUTPUT@'], - install_dir: py3.get_install_dir() + '/scipy' + install_dir: py3.get_install_dir() / 'scipy' ) -#FIXME: the git revision is Unknown; script works when invoked directly, but -# not when it's run by Ninja. See https://github.com/rgommers/scipy/pull/57 generate_version = custom_target( 'generate-version', install: true, @@ -150,13 +158,16 @@ generate_version = custom_target( output: 'version.py', input: '../tools/version_utils.py', command: [py3, '@INPUT@', '--source-root', '@SOURCE_ROOT@'], - install_dir: py3.get_install_dir() + '/scipy' + install_dir: py3.get_install_dir() / 'scipy' ) python_sources = [ '__init__.py', '_distributor_init.py', - 'conftest.py' + 'conftest.py', + 'linalg.pxd', + 'optimize.pxd', + 'special.pxd' ] py3.install_sources( @@ -176,14 +187,16 @@ _cython_tree = custom_target('_cython_tree', output: [ '__init__.py', 'linalg.pxd', + 'optimize.pxd', 'special.pxd' ], input: [ '__init__.py', 'linalg.pxd', + 'optimize.pxd', 'special.pxd' ], - command: [copier, '@INPUT@', '@OUTDIR@'] + command: [copier, '@INPUT@', '@OUTDIR@'], ) cython_tree = declare_dependency(sources: _cython_tree) diff --git a/scipy/optimize/_constraints.py b/scipy/optimize/_constraints.py index d4db74270912..2bcd270ce1e4 100644 --- a/scipy/optimize/_constraints.py +++ b/scipy/optimize/_constraints.py @@ -4,7 +4,7 @@ from ._differentiable_functions import ( VectorFunction, LinearVectorFunction, IdentityVectorFunction) from ._optimize import OptimizeWarning -from warnings import warn +from warnings import warn, catch_warnings, simplefilter from numpy.testing import suppress_warnings from scipy.sparse import issparse @@ -165,11 +165,18 @@ def _input_validation(self): def __init__(self, A, lb=-np.inf, ub=np.inf, keep_feasible=False): if not issparse(A): - self.A = np.atleast_2d(A) + # In some cases, if the constraint is not valid, this emits a + # VisibleDeprecationWarning about ragged nested sequences + # before eventually causing an error. `scipy.optimize.milp` would + # prefer that this just error out immediately so it can handle it + # rather than concerning the user. + with catch_warnings(): + simplefilter("error") + self.A = np.atleast_2d(A).astype(np.float64) else: self.A = A - self.lb = np.atleast_1d(lb) - self.ub = np.atleast_1d(ub) + self.lb = np.atleast_1d(lb).astype(np.float64) + self.ub = np.atleast_1d(ub).astype(np.float64) self.keep_feasible = np.atleast_1d(keep_feasible).astype(bool) self._input_validation() diff --git a/scipy/optimize/_highs/meson.build b/scipy/optimize/_highs/meson.build index 53420ddba024..6cb930a92cf1 100644 --- a/scipy/optimize/_highs/meson.build +++ b/scipy/optimize/_highs/meson.build @@ -262,9 +262,6 @@ _highs_wrapper = py3.extension_module('_highs_wrapper', '-Wno-format-truncation', '-Wno-non-virtual-dtor', '-Wno-class-memaccess', - # Pass despite __STDC_FORMAT_MACROS redefinition warning. - # Remove after merge of https://github.com/ERGO-Code/HiGHS/pull/674 - '-Wp,-w', Wno_unused_but_set, highs_define_macros, cython_c_args, diff --git a/scipy/optimize/_milp.py b/scipy/optimize/_milp.py index 041cf7c3e849..99836bd9697f 100644 --- a/scipy/optimize/_milp.py +++ b/scipy/optimize/_milp.py @@ -43,7 +43,7 @@ def _constraints_to_components(constraints): # argument could be a single tuple representing a LinearConstraint try: constraints = [LinearConstraint(*constraints)] - except TypeError: + except (TypeError, ValueError, np.VisibleDeprecationWarning): # argument was not a tuple representing a LinearConstraint pass diff --git a/scipy/optimize/cython_optimize/meson.build b/scipy/optimize/cython_optimize/meson.build index d91e547cfea4..af03d9c71de3 100644 --- a/scipy/optimize/cython_optimize/meson.build +++ b/scipy/optimize/cython_optimize/meson.build @@ -1,4 +1,5 @@ # Needed to trick Cython, it won't do a relative import outside a package +# (see https://github.com/mesonbuild/meson/issues/8961) _dummy_init_cyoptimize = custom_target('_dummy_init_cyoptimize', output: [ '__init__.py', @@ -13,8 +14,6 @@ _dummy_init_cyoptimize = custom_target('_dummy_init_cyoptimize', command: [copier, '@INPUT@', '@OUTDIR@'] ) -# FIXME: generated .pyx which has relative cimport in it doesn't work yet (see -# https://github.com/mesonbuild/meson/issues/8961) _zeros_pyx = custom_target('_zeros_pyx', output: '_zeros.pyx', input: '_zeros.pyx.in', diff --git a/scipy/optimize/meson.build b/scipy/optimize/meson.build index 0bb8bfe07458..e4718ac4e13e 100644 --- a/scipy/optimize/meson.build +++ b/scipy/optimize/meson.build @@ -316,6 +316,7 @@ py3.install_sources([ '_tstutils.py', '_zeros_py.py', 'cobyla.py', + 'cython_optimize.pxd', 'lbfgsb.py', 'linesearch.py', 'minpack.py', diff --git a/scipy/optimize/tests/test_milp.py b/scipy/optimize/tests/test_milp.py index e66354f0181d..f179029e0b60 100644 --- a/scipy/optimize/tests/test_milp.py +++ b/scipy/optimize/tests/test_milp.py @@ -270,3 +270,28 @@ def test_infeasible_prob_16609(): res = milp(c, integrality=integrality, bounds=bounds, constraints=constraints) np.testing.assert_equal(res.status, 2) + + +def test_three_constraints_16878(): + # `milp` failed when exactly three constraints were passed + # Ensure that this is no longer the case. + rng = np.random.default_rng(5123833489170494244) + A = rng.integers(0, 5, size=(6, 6)) + bl = np.full(6, fill_value=-np.inf) + bu = np.full(6, fill_value=10) + constraints = [LinearConstraint(A[:2], bl[:2], bu[:2]), + LinearConstraint(A[2:4], bl[2:4], bu[2:4]), + LinearConstraint(A[4:], bl[4:], bu[4:])] + constraints2 = [(A[:2], bl[:2], bu[:2]), + (A[2:4], bl[2:4], bu[2:4]), + (A[4:], bl[4:], bu[4:])] + lb = np.zeros(6) + ub = np.ones(6) + variable_bounds = Bounds(lb, ub) + c = -np.ones(6) + res1 = milp(c, bounds=variable_bounds, constraints=constraints) + res2 = milp(c, bounds=variable_bounds, constraints=constraints2) + ref = milp(c, bounds=variable_bounds, constraints=(A, bl, bu)) + assert res1.success and res2.success + assert_allclose(res1.x, ref.x) + assert_allclose(res2.x, ref.x) diff --git a/scipy/sparse/linalg/_eigen/lobpcg/tests/test_lobpcg.py b/scipy/sparse/linalg/_eigen/lobpcg/tests/test_lobpcg.py index d1b1817898d2..5c24bb826576 100644 --- a/scipy/sparse/linalg/_eigen/lobpcg/tests/test_lobpcg.py +++ b/scipy/sparse/linalg/_eigen/lobpcg/tests/test_lobpcg.py @@ -351,8 +351,8 @@ def test_tolerance_float32(): A = A.astype(np.float32) X = rnd.standard_normal((n, m)) X = X.astype(np.float32) - eigvals, _ = lobpcg(A, X, tol=1e-5, maxiter=50, verbosityLevel=0) - assert_allclose(eigvals, -np.arange(1, 1 + m), atol=1.5e-5) + eigvals, _ = lobpcg(A, X, tol=1.25e-5, maxiter=50, verbosityLevel=0) + assert_allclose(eigvals, -np.arange(1, 1 + m), atol=2e-5, rtol=1e-5) def test_random_initial_float32(): diff --git a/scipy/special/meson.build b/scipy/special/meson.build index 60aa384ae52b..72beba8764f0 100644 --- a/scipy/special/meson.build +++ b/scipy/special/meson.build @@ -347,7 +347,7 @@ cython_special = custom_target('cython_special', input: ['_generate_pyx.py', 'functions.json', '_add_newdocs.py'], command: [py3, '@INPUT0@', '-o', '@OUTDIR@'], install: true, - install_dir: py3.get_install_dir() + '/scipy/special' + install_dir: py3.get_install_dir() / 'scipy/special' ) # pyx -> c, pyx -> cpp generators, depending on copied pxi, pxd files. @@ -494,7 +494,7 @@ foreach npz_file: npz_files '--use-timestamp', npz_file[2], '-o', '@OUTDIR@' ], install: true, - install_dir: py3.get_install_dir() + '/scipy/special/tests/data' + install_dir: py3.get_install_dir() / 'scipy/special/tests/data' ) endforeach diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index a78dea60595c..be4b0e1b7320 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -1283,8 +1283,8 @@ def skew(a, axis=0, bias=True, nan_policy='propagate'): Returns ------- skewness : ndarray - The skewness of values along an axis, returning 0 where all values are - equal. + The skewness of values along an axis, returning NaN where all values + are equal. Notes ----- @@ -1392,8 +1392,8 @@ def kurtosis(a, axis=0, fisher=True, bias=True, nan_policy='propagate'): Returns ------- kurtosis : array - The kurtosis of values along an axis. If all values are equal, - return -3 for Fisher's definition and 0 for Pearson's definition. + The kurtosis of values along an axis, returning NaN where all values + are equal. References ---------- diff --git a/scipy/stats/_unuran/meson.build b/scipy/stats/_unuran/meson.build index 0d55b18f3a0b..338aa3763200 100644 --- a/scipy/stats/_unuran/meson.build +++ b/scipy/stats/_unuran/meson.build @@ -164,7 +164,7 @@ unuran_include_dirs = [ '../../_lib/unuran/unuran/src/tests' ] -unuran_version = '16:0:0' # TODO: grab from configure.ac file +unuran_version = '16:0:0' # taken from `_lib/unuran/unuran/configure.ac` unuran_defines = [ '-DHAVE_ALARM=1', diff --git a/tools/version_utils.py b/tools/version_utils.py index 9bb5e14aa222..ba088dd0cb64 100644 --- a/tools/version_utils.py +++ b/tools/version_utils.py @@ -98,7 +98,8 @@ def _minimal_ext_cmd(cmd): # point from the current branch (assuming a full `git clone`, it may be # less if `--depth` was used - commonly the default in CI): prev_version_tag = '^v{}.{}.0'.format(MAJOR, MINOR - 2) - out = _minimal_ext_cmd(['git', 'rev-list', 'HEAD', prev_version_tag, + out = _minimal_ext_cmd(['git', '--git-dir', git_dir, + 'rev-list', 'HEAD', prev_version_tag, '--count']) COMMIT_COUNT = out.strip().decode('ascii') COMMIT_COUNT = '0' if not COMMIT_COUNT else COMMIT_COUNT